log4j
[TOC]
1. 日志框架的基本组件
无论是JDK还是log4j(版本1)提供的日志框架,都主要包含以下组件:
Logger:在程序中创建于实例化,负责日志信息的捕获与记录,在log4j中,如 Logger.getLogger(“service”), 会创建一个name为service的logger对象,如果LoggerManager已经发现有了这样一个为service的logger,直接返回在LoggerManager中管理的logger对象。logger对象用于不同级别的日志信息捕获,通过logger.info(), logger.warn()等。
Appender: Appender组件负责接收Logger捕获的日志事件,并将这些日志事件输出到不同的目的地,如打印在控制台、输出到文件、邮件、日志服务器等。在Java的日志框架中,称之为Handler。
Layout: Layout组件负责在Appender输出到目的地之前对日志进行格式化,如格式化成 Json、XML、HTML、普通文本等形式。在 Java 的日志框架中称之为 Formatter。
Filter:对日志事件进行过滤。
2. 常见的Appender
ConsoleAppender
FileAppender
RollingFileAppender
DailyRollingFileAppender
……
3. 常用的Layout
PatternLayout 通过ConversionPattern以占位符的形式进行格式化日志消息
4. 日志的级别
5. Logger的Hierarchy(层级)
Logger是有层级的,在初始化时,log4j就会生成一个根Logger对象,名字为root
, 当在程序中调用:
1 | Logger rootLogger1 = Logger.getLogger(); |
得到同一个根Logger对象,之后,在程序中定义的所有Logger对象都属于根Logger对象的子Logger,通过 subLogger.getParent()
可以获取其父Logger对象。但这种层级不仅仅是两层,可能是多层,如下定义的Logger对象:
1 | Logger logger1 = Logger.getLogger("com"); |
log4j通过点来定义层级,所以上面的三个Logger对象的层级关系如下图所示:
每一个下层的Logger对象的上层都是它的祖先Logger对象,当调用getParent方法时得到其直接父Logger对象,如 logger2.getParent()得到logger1对象。
5.1 Logger日志级别继承
每一个Logger对象都可以设置一个默认日志信息级别,要打印的日志级别大于这个默认的级别才会被Appender处理。如果没有显示设置这个默认级别(配置文件和程序中都没有设置),那么在进行判断时,会调用其父Logger的日志级别,如果直接父Logger对象也没有设置,继续向上找到某一个祖先Logger对象,并且其默认的日志级别不为空。也就间接起到了继承祖先Logger的日志级别。
5.2 Appender继承
默认情况下,每一个处于Logger中间层级的Logger对象,==会继承其所有祖先Logger对象定义的所有Appenders==, 假设上述的4个Logger对象,每一个Logger对象都定义了一个ConsoleAppender(输出到控制台),logger3的默认日志级别设置为INFO时,logger3.info("message")
会在控制台打印出4条message,这是因为它继承了每一个祖先Logger对象的Appender(com.candy, com, root), 再加上原本定义的一个Appender, 一共有4个ConsoleAppender,所以会在控制台打印出4条。
这就相当于对于这个子Logger对象,为其定义了4个ConsoleAppender, 这些Appender与其祖先的日志默认级别不再有关系,只要是满足子Logger对象的默认级别的,就会交由这4个ConsoleAppender处理。当然,每个Appender也可以通过设置Threshold
来指定该Appender要处理的最低日志级别。
设置不继承其父Logger对象的Appenders
如果不想继承其祖先Logger对象的Appenders, 可以通过设置配置文件log4j.additivity.loggername=false
或者程序中logger.setAdditivity(false)
来关闭这种继承。这样,这个Logger对象的日志信息只会在它自己定义的Appender中输出了。
其实,要理解这种继承,直接去看源码会比较清晰的看到具体的处理过程是怎么样的!!!
log4j 配置文件及相关注释
1 | # 定义日志目录变量,引用使用 ${logdir} |
6. 捕获异常日志
在日志打印的过程中,如果程序中发生了异常,并且对异常进行了捕获,在进行异常日志打印的时候,是可以传入一个Throwable对象的, 日志在进行记录的时候也会打印栈跟踪信息:
1 | /** |
7. 未捕获异常日志
如果没有进行捕获,又想要将异常的堆栈信息保留在日志文件里,Thread类中有两个方法,我们可以用它来为未捕获的异常指定一个处理(ExceptionHandler
), 通过 setDefaultUncaughtExceptionHandler 可以让你在任何线程上处理任何异常。setUncaughtExceptionHandler可以让你针对一个指定的线程设定一个不同的处理方法。而ThreadGroup则允许你设定一个处理方法。
1 | /* 为未捕获的异常记录异常信息、栈跟踪信息 */ |
8. 参考文件
【1】http://www.importnew.com/16331.html
【2】http://www.loggly.com/ultimate-guide/logging/java-logging-basics/
【3】http://tutorials.jenkov.com/java-logging/logger-hierarchy.html
【4】https://www.tutorialspoint.com/log4j/log4j_useful_resources.htm