log4j

[TOC]

1. 日志框架的基本组件

无论是JDK还是log4j(版本1)提供的日志框架,都主要包含以下组件:

log

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
2
3
Logger rootLogger1 = Logger.getLogger();
// 或者是通过名字调用
Logger rootLogger2 = Logger.getLoger("root")

得到同一个根Logger对象,之后,在程序中定义的所有Logger对象都属于根Logger对象的子Logger,通过 subLogger.getParent() 可以获取其父Logger对象。但这种层级不仅仅是两层,可能是多层,如下定义的Logger对象:

1
2
3
Logger logger1 = Logger.getLogger("com"); 
Logger logger2 = Logger.getLogger("com.candy");
Logger logger3 = Logger.getLogger("com.candy.service");

log4j通过点来定义层级,所以上面的三个Logger对象的层级关系如下图所示:

logHierarchy

每一个下层的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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# 定义日志目录变量,引用使用 ${logdir}
# logdir=directory.of.log

### set log levels ###
log4j.rootLogger= DEBUG,stdout,D

### 输出到控制台 ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.Threshold=DEBUG
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%c - %m%n
#log4j.appender.stdout.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%X{user}] [%-5p] %c - %m%n

# %X{key} MDC 属性获取
# %c logger name
# %C 类的全路径
# %-5p 长度最小5位,左对齐
# %M method 方法名
# %m message 日志消息
# %n 换行
# %F 所属的文件
# %l 相当于 %C.%M(%F:%L),如 log.demo.basic.BasicLog4JDemo.logForComponents(BasicLog4JDemo.java:78)

### 输出到日志文件 ###
log4j.appender.D=org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File=logs/log.log
log4j.appender.D.Encoding=UTF-8
log4j.appender.D.Append=true
log4j.appender.D.Threshold=INFO
log4j.appender.D.ImmediateFlush=true
# 控制何时进行日志rolling以及开始新的日志文件
log4j.appender.D.DatePattern='.' yyyy-MM-dd
log4j.appender.D.layout=org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%-5p] %m%n

# RollingFileAppender 配置
# log4j.appender.D.MaxFileSize=1KB
# log4j.appender.D.MaxBackupIndex=2


# 为name为service的logger设置日志级别及appender
log4j.logger.service=INFO,S
log4j.appender.S=org.apache.log4j.FileAppender
log4j.appender.S.File=logs/service.log
log4j.appender.S.Encoding=UTF-8
log4j.appender.S.Append=true
log4j.appender.S.Threshold=INFO
log4j.appender.S.ImmediateFlush=true
log4j.appender.S.layout=org.apache.log4j.PatternLayout
log4j.appender.S.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%-5p] %m%n


# 为name为viewer的logger设置日志级别及appender
log4j.logger.viewer=INFO,V
log4j.appender.V=org.apache.log4j.FileAppender
log4j.appender.V.File=logs/viewer.log
log4j.appender.V.Encoding=UTF-8
log4j.appender.V.Append=true
# 最终是否在祖先Logger的appender中输出主要取决于祖先Logger对象的某个appender的Threshold设置的日志级别
log4j.appender.V.Threshold=INFO
log4j.appender.V.ImmediateFlush=true
log4j.appender.V.layout=org.apache.log4j.PatternLayout
log4j.appender.V.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%-5p] %m%n

6. 捕获异常日志

在日志打印的过程中,如果程序中发生了异常,并且对异常进行了捕获,在进行异常日志打印的时候,是可以传入一个Throwable对象的, 日志在进行记录的时候也会打印栈跟踪信息:

1
2
3
4
5
  /**
* Log a message object with the ERROR level including
* the stack trace of the {@link Throwable} t passed * * as parameter.
*/
public void error(Object message, Throwable t) {...}

7. 未捕获异常日志

如果没有进行捕获,又想要将异常的堆栈信息保留在日志文件里,Thread类中有两个方法,我们可以用它来为未捕获的异常指定一个处理(ExceptionHandler), 通过 setDefaultUncaughtExceptionHandler 可以让你在任何线程上处理任何异常。setUncaughtExceptionHandler可以让你针对一个指定的线程设定一个不同的处理方法。而ThreadGroup则允许你设定一个处理方法。

1
2
3
4
5
6
/* 为未捕获的异常记录异常信息、栈跟踪信息 */
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
Logger.getLogger(BasicLog4JDemo.class).log(Priority.FATAL, t + " An exception occured: ", e);
};
});

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

【5】https://blog.csdn.net/azheng270/article/details/2173430/