log4j1.x教程和实战

log4j 1.x 官网:https://logging.apache.org/log4j/1.2/

2015年8月5日,log4j项目管理委员会宣布Log4j 1.x已到使用寿命。有关公告的全文,请参阅Apache Blog。建议Log4j 1的用户升级到Apache Log4j 2

Log4j使用

Log4j中有三个主要的组件,它们分别是 Logger、Appender和Layout。

  • logger:可以定义多个logger,不同的logger之间可以有继承关系;一个logger可以指定一种日志级别和多个appender(可以同时将日志输出到控制台和文件)
  • appender:指定日志信息存放到什么地方,log4j支持多种appender(console、file...),不同类型的appender可以设置不同的策略;
  • layout:指定日志输出的格式;

1、Logger(日志记录器):

1.1)日志级别:

log4j定义了以下几种级别:ALL < DEBUG < INFO < WARN < ERROR < FATAL < OFF 级别越低输出的信息越多(生产环境推荐为info)

Logger可以设置一种级别,输出那些级别高于或等于它的级别的信息;如果没有设置级别,那么它将会继承最近的祖先的级别

1.2)Logger的继承特性:

在log4j.properties中可以配置多个Logger,其他所有Logger都继承rootLogger。可以通过如下方式设置rootLogger和自定义Logger的level、Appender:

  • log4j.rootLogger = [level],appenderName1,appenderName2,...
  • log4j.logger.packageName = [level],appenderName1,appenderName2,...

rootLogger必须配置,自定义的Logger可以不用配。

这里的packageName可以是任何的字符串,只是我们在代码中通常使用如下方式获取Logger,所以在log4j.properties中使用包名是最简单和科学的了。

static Logger logger = LoggerFactory.getLogger(Test.class);

1)继承关系:

假设:

  • 在log4j.properties中配置了:log4j.logger.aty.log.service.A=DEBUG, append1
  • aty.log.service.A类使用如下方式获取Logger:Logger logger = LoggerFactory.getLogger(A.class);

那么log4j会在配置文件中先查找名称是"aty.log.service.A"的Logger,如果没有找到,向上查找名称是"aty.log.service"的Logger,如果还没有找到那么继续向上查找(aty.log、aty的Logger),查找的最顶层就是rootLogger。

这就是log4j中logger的继承关系。

2)additivity:

默认情况下子Logger会继承父Logger的appender、level,也就是说子Logger会在父Logger的appender里输出。若是additivity设为false(默认是true),则子Logger只会在自己的appender里输出,而不会在父Logger的appender里输出。

2、Appender:

2.1)log4j定义了一下几种常用的appender:

  • org.apache.log4j.ConsoleAppender:控制台
  • org.apache.log4j.FileAppender:文件
  • org.apache.log4j.DailyRollingFileAppender:按时间滚动文件
  • org.apache.log4j.RollingFileAppender:按大小滚动文件
  • org.apache.log4j.WriterAppender:将日志信息以流格式发送到任意指定的地方

注意:为了本地开发测试方便,在开发环境可以只配置一个ConsoleAppender,但在生产环境建议去掉ConsoleAppender

2.2)DailyRollingFileAppender策略:

可以根据datePattern设置日志的滚动周期:

  • '.'yyyy-MM: 每月
  • '.'yyyy-ww: 每周
  • '.'yyyy-MM-dd: 每天
  • '.'yyyy-MM-dd-a: 每天两次
  • '.'yyyy-MM-dd-HH: 每小时
  • '.'yyyy-MM-dd-HH-mm: 每分钟

2.3)RollingFileAppender策略:

可以指定如下属性设置日志的滚动大小:

  • maxBackupIndex:保留最大文件个数;
  • maxFileSize:最大文件大小;

2.4)FileAppender缓冲:

默认FileAppender、DailyRollingFileAppender、RollingFileAppender会立即刷到文件中,高并发场景下性能较低下,log4j提供了如下参数来设置:

  • ImmediateFlush:默认是true,表示每一条打印日志请求都会被立即输出,设置为false,会使用java.io.OutputStreamWriter的缓存,缓存大小为1024字节
  • BufferedIO:BufferedIO=true表示使用java.io.BufferedWriter缓存,缓存大小可以通过BufferSize来设置

官方解释了使用 Buffer 会带来 10% ~ 20% 的性能提升,但同时会带来两个问题:

  • 当程序关闭的时候,由于一部分日志还在内存中导致的日志丢失问题
  • 由于日志无法实时输出,给问题排查带来了困难(类似异步输出)

JVM 支持关闭回调,需要在JVM退出的时候执行一些操作,可以在系统启动之后,注册ShutdownHook,如下:

Runtime.getRuntime().addShutdownHook(new Thread(()->{
    LogManager.shutdown();
}));

2.5)AsyncAppender:

AsyncAppender采用生产者消费者模型,异步地将Logging Event送到对应的Appender中。

  • 生产者:外部应用了Log4j的实时线程,实时将Logging Event传送进AsyncAppender里
  • 中转:Buffer和DiscardSummary
  • 消费者:Dispatcher线程和appenders

1)工作原理:

  1. Logging Event进入AsyncAppender,AsyncAppender会调用append方法,在append方法中会去把logging Event填入Buffer中,当消费能力不如生产能力时,AsyncAppender会把超出Buffer容量的Logging Event放到DiscardSummary中,作为消费速度一旦跟不上生成速度,中转buffer的溢出处理的一种方案。
  2. AsyncAppender有个线程类Dispatcher,它是一个简单的线程类,实现了Runnable接口。它是AsyncAppender的后台线程。

2)Dispatcher所要做的工作是:

  1. 锁定Buffer,让其他要对Buffer进行操作的线程阻塞。
  2. 看Buffer的容量是否满了,如果满了就将Buffer中的Logging Event全部取出,并清空Buffer和DiscardSummary;如果没满则等待Buffer填满Logging Event,然后notify Disaptcher线程。
  3. 将取出的所有Logging Event交给对应appender进行后面的日志信息推送。

注意log4j1.x的异步appender存在性能问题log4j 2 由于采用了LMAX Disruptor,性能超过原来AsyncAppender几个数量级,支持每秒并发写入1800万条日志

2.6)threshold配置:

一个Logger可以指定多个Appender,如果想把某些级别的日志单独通过一个appender输出到指定文件,可以为该appender设置Threshold属性。同Logger的级别一样,Appender上通过Threshold设置级别,也表示该appender输出大于、等于该级别的日志

当然这里有个提前Logger也可以配置级别,只有Appender上Threshold设置的级别要大于Logger上设置的级别时才有意义。

比如:Logger上设置INFO,某个appender的Threshold设置为ERROR。

3、Layout:

3.1)log4j定义了一下几种常用的布局:

  • org.apache.log4j.HTMLLayout:以HTML表格形式布局
  • org.apache.log4j.PatternLayout:自定义输出样式
  • org.apache.log4j.SimpleLayout:简单输出(包含日志级别和信息字符串)
  • org.apache.log4j.TTCCLayout:包含日志产生的时间、线程、类别等信息

3.2)PatternLayou参数:

Log4J采用类似C语言中的printf函数的打印格式格式化日志信息的,具体如下:

  • %p: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL,
  • %d: 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921。有 %d{DATE}, %d{ABSOLUTE}, %d{HH:mm:ss,SSS}, %d{ddMMyyyy HH:mm:ss,SSS},其中SSS是毫秒。
  • %r: 输出自应用启动到输出该log信息耗费的毫秒数
  • %c: 输出日志信息所属的类目,通常就是所在类的全名
  • %t: 输出产生该日志事件的线程名
  • %l: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)
  • %x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。
  • %%: 输出一个”%”字符
  • %F: 输出日志消息产生时所在的文件名称
  • %L: 输出代码中的行号
  • %m: 输出代码中指定的消息,产生的日志具体信息
  • %n: 输出一个回车换行符,Windows平台为”\r\n”,Unix平台为”\n”输出日志信息换行

4、其他:

4.1)filter:

5、配置

log4j通过配置文件的方式进行设置,目前支持两种格式的配置文件:

  • xml文件:复杂,支持复杂过滤器filter
  • properties文件(推荐):简单,但不支持复杂过滤器filter

在log4j的源码LogManager.java中可以看出来,在JVM加载log4j时,默认先会在会classpath下寻找log4j.xml配置文件,若没有会寻找log4j.properties文件。

注意:除了上面默认的寻找log4j配置文件外,在程序中也可以手动加载指定的log4j配置文件。

5.1)手动加载log4j.properties:

有些情况下,我们需要手动加载log4j的配置文件,有以下几种方式:

  • BasicConfigurator.configure (): 自动快速地使用缺省Log4j环境
  • PropertyConfigurator.configure (String configFilename) :读取properties配置文件
  • DOMConfigurator.configure ( String filename ) :读取XML形式的配置文件
  • -Dlog4j.configuration=<FILE_PATH> :JVM系统参数指定配置文件
public class App {
    static final Logger logger = Logger.getLogger(App.class);

    public static void main(String... str){
       PropertyConfigurator.configure("/data/path/log4j.properties");

       logger.info("hello world");
    }
}

5.2)web工程下log4j的使用:

1)在web.xml中配置servlet,同时设定load-on-startup为0,指定log4j的配置文件路径

<servlet>
   <servlet-name>InitLog4jServlet</servlet-name>
   <servlet-class>indiv.dulk.wechat.init.InitLog4jServlet</servlet-class>
   <init-param>
       <param-name>propertiesLocation</param-name>
       <param-value>/WEB-INF/classes/log4j.properties</param-value>
   </init-param>
   <load-on-startup>0</load-on-startup>
</servlet>

2)这个servlet配置log4j就是读出配置文件,然后调用configure函数

public class InitLog4jServlet extends HttpServlet{
  @Override
  public void init() throws ServletException {
      System.out.println("*** Log4jInitServlet正在初始化log4j日志设置信息 ***");
      String log4jLocation = getInitParameter("propertiesLocation");
      if (log4jLocation == null) {
          System.err.println("*** 没有找到log4j的properties配置文件,使用缺省log4j环境初始化 ***");
          BasicConfigurator.configure();
      } else {
          String webAppPath = getServletContext().getRealPath("/");
          String log4jRealLocation = webAppPath + log4jLocation;
          File log4jFile = new File(log4jRealLocation);
          if (log4jFile.exists()) {
              System.out.println("*** 使用" + log4jRealLocation + "进行log4j环境初始化 ***");
              PropertyConfigurator.configure(log4jRealLocation);
          } else {
              System.err.println("*** " + log4jRealLocation + "文件未找到,使用缺省log4j环境初始化 ***");
              BasicConfigurator.configure();
          }
      }
  }
}

5.3)spring集成log4j:

Spring提供了一个Log4jConfigListener,通过web.xml配置从指定位置加载log4j配置文件,以及动态修改log4j配置文件的扫描时间。

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <!-- Spring -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:api-root-context.xml</param-value>
	</context-param>
	<!--log4j-->
	<context-param>
		<param-name>log4jExposeWebAppRoot</param-name>
		<param-value>false</param-value>
	</context-param>
    <context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>classpath:conf/log4j.properties</param-value>
    </context-param>
    <context-param>
        <param-name>log4jRefreshInterval</param-name>
        <param-value>60000</param-value>
    </context-param>

	
	<listener>
		<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
	</listener>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
</web-app>

:Log4jConfigListener必须要在Spring的Listener之前。

:如果log4j.properties在classpath下,那么可以不用设置log4jConfigLocation

参考:

https://www.cnblogs.com/deng-cc/p/6739419.html

实战

1、各级别日志不重复的打印到各日志文件:

使用Threshold可以将某个级别的日志单独打印到某个文件,但是仍然会有重复问题(比如:info.log中会包含ERROR级别的日志信息)。推荐使用log4j提供的filter

详情见:https://blog.csdn.net/liuxiao723846/article/details/69295428

2、某类日志单独输出到指定文件:

我们在系统中,经常要将某一类的日志打到特定的文件中,这样可以避免各种日志混杂在一起。利用log4j的继承特性,很容易实现这一点。

log4j.rootLogger = info,stdout,infolog,errorlog
log4j.logger.cn.nuc.edu = info,mylog

log4j.additivity.cn.nuc.edu = false

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%d{MM-dd HH:mm:ss}] [%p] [%c:%L] %m%n

log4j.appender.infolog = org.apache.log4j.DailyRollingFileAppender
log4j.appender.infolog.Threshold = INFO
log4j.appender.infolog.File = /data/logs/logtest/test.log
log4j.appender.infolog.layout = org.apache.log4j.PatternLayout
log4j.appender.infolog.layout.ConversionPattern = [%d{MM-dd HH:mm:ss}] [%p] [%c:%L] %m%n
log4j.appender.infolog.filter.infoFilter = org.apache.log4j.varia.LevelRangeFilter
log4j.appender.infolog.filter.infoFilter.LevelMin = INFO
log4j.appender.infolog.filter.infoFilter.LevelMax = INFO

log4j.appender.errorlog = org.apache.log4j.DailyRollingFileAppender
log4j.appender.errorlog.Threshold = ERROR
log4j.appender.errorlog.File = /data/logs/logtest/test_error.log
log4j.appender.errorlog.layout = org.apache.log4j.PatternLayout
log4j.appender.errorlog.layout.ConversionPattern = [%d{MM-dd HH:mm:ss}] [%p] [%c:%L] %m%n

log4j.appender.mylog = org.apache.log4j.DailyRollingFileAppender
log4j.appender.mylog.Threshold = INFO
log4j.appender.mylog.File = /data/logs/logtest/my.log
log4j.appender.mylog.layout = org.apache.log4j.PatternLayout
log4j.appender.mylog.layout.ConversionPattern = [%d{MM-dd HH:mm:ss}] [%p] [%c:%L] %m%n

cn.nuc.edu包下的类,使用static Logger logger = LoggerFactory.getLogger(Test.class); 来获取Logger后,那么这些日志将输出在my.log里。

3、Log4j1.x性能瓶颈:

https://mp.weixin.qq.com/s/ZTTKW9Mz5M93d6Dw59E2xQ

猜你喜欢

转载自blog.csdn.net/liuxiao723846/article/details/110929347
今日推荐