优雅灵活记录Java日志 By log4j2
log4j是一个被广泛使用的Java日志记录框架,通过使用该框架,我们可以在自己的项目中根据自身需求灵活配置日志输出格式,从而获得期望格式的日志信息,用于后续分析、错误排查等。log4j2则是原有log4j的升级版本,该版本使用更加简洁方便。
log4j2使用步骤
注:小编我目前使用的 IDE 为 Intellij,自从写Android代码时使用了Android Studio后,就爱上了 Intellij 这一系列的产品了。
- 下载 log4j2 的JAR包:从http://logging.apache.org/log4j/2.x/download.html 官网下载最新版Apache Log4j 2 binary 压缩包,解压后会得到许多JAR包,只需要将其中的 log4j-core-2.7.jar 和 log4j-api-2.7.jar 添加到项目中即可;
- 在项目的 src 文件夹下添加 log4j2.xml 文件,该文件为 log4j2 的配置文件,默认文件内容为:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
- 在项目中使用 log4j2:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class LogTest {
// 获取 Logger 实例
static Logger logger = LogManager.getLogger();
public static void main(String[] args) {
logger.trace("I am a trace message");
logger.warn("I am a warn message");
logger.error("I am a error message");
logger.info("I am a info message");
logger.fatal("I am a fatal message");
}
}
效果如下图所示:
日志效果图-1咦?上面明明定义了五条日志信息,怎么控制台就显示了两条呢?细致的读者maybe会有如此困惑。小小剧透一波:此玄机存在于 log4j 的配置文件中,若想一探究竟,就继续随我一起仔细探究吧!
log4j2 日志级别
log4j2 中默认的日志级别为:trace < debug < info < warn < error < fatal
当设置了日志输出级别时,当前级别及以上级别的日志会输出,而当前级别以下级别的日志则不会输出,系统默认输出级别为 error,也就是说默认设置下,只有 error 和 fatal这两级日志会输出,这也就解释了为什么上面只输出了两条日志信息
- trace:即追踪,程序每推进一步均可输出一条trace日志,主要用于展现程序运行轨迹
- debug:调试
- info:输出你感兴趣的信息,一般用于基本的、高层次的诊断信息。在长时间运行的代码段开始运行及结束运行时应该产生消息,以便知道现在系统在干什么。但是这样的信息不宜太过频繁
- warn:提示信息
- error:错误信息,显示一个错误,或一个通用的错误情况,但还不至于会将系统挂起。这种程度的错误一般会触发邮件的发送,将消息发送到alert list中,运维人员可以在文档中记录这个bug并提交
- fatal:会导致程序崩溃的重大错误,用在极端的情形中,即必须马上获得注意的情况
log4j2 日志组件
下图是Apache官网贴出的log4j的类图,通过该类图可以清晰地看出log4j的类层次结构。
log4j2类层次结构图由类图可以看出:log4j2中主要包含如下概念/组成部分:Loggerontext、Configuration、Filter、Logger、LoggerConfig、Appender、Layout等,后文会详细解释这些概念。
log4j2 由以下三个核心组件组成:
- Loggers:Logger负责捕捉事件并将其发送给合适的Appender;
- Appenders:负责对Logger传来的日志事件进行处理,如过滤、格式化等,然后将处理后的日志事件记录到指定位置;
- Layouts:负责对日志事件中的数据进行转换和格式化;Layouts决定了数据在一条日志记录中的最终形式。
当Logger记录一个事件时,它将事件转发给适当的Appender。然后Appender使用Layout来对日志记录进行格式化,并将其发送给控制台、文件或其它目标位置。另外,Filters可以让你进一步指定一个Appender是否可以应用在一条特定的日志记录上。在日志配置中,Filters并不是必需的,但可以让你更灵活地控制日志消息的流动。
log4j2核心概念
Loggerontext
LoggerContext在Logger系统中扮演着锚点的角色,一般来说,一个应用只会存在于一个LoggerContext中,但是也可以根据使用场景的差异和项目的具体需求,为一个项目配置多个不同的LoggerContext。在同一个LoggerContext下,log系统是可以互通的。
Configuration
Configuration顾名思义,即是当前LoggerContext中的所有Logger的配置环境,每一个LoggerContext均会拥有一个动态可配置的Configuration,该Configuration 包含了当前LoggerContext环境中所有的Loggers、Appenders、Filters、Layouts等配置信息。注意:在reConfiguration期间,新旧Configuration均会同时存在,然而一旦重配过程结束,旧的Configuration就会失效,配置信息会重定向至新的Configuration中。
Logger
Logger是一个通过给LoggerManager.getLogger()方法传递特定的Logger名称而获得的一个AbstractLogger实现类对象,该对象自身并不具体进行任何操作,而是通过关联一个LoggerConfig完成具体的Logger配置,并通过实现AbstractLogger的方法进行具体的操作。注意:通过同一个名称获得的Logger对象会指向同一个Logger引用。
LoggerConfig
LoggerConfig包含了一个Logger对象的具体配置信息,如该Logger关联的Appenders、Filters、过滤级别等。
Filter
过滤器,定义LogEvent的过滤规则。log4j中定义了三种过滤状态:Accept(接受)、Deny(拒绝)和Nertual(中立)。
Accept:该LogEvent将被执行而无需调用其它过滤器;
Deny:忽略当前LogEvent,并将该LogEvent的控制权移交给过滤器调用者;
Nertual:中立,也就是说,当前LogEvent应该传递给其它过滤器,如果没有别的过滤器可以传递了,那么就由现在这个过滤器来处理。
Appender
Appender意为输出目的地,负责将日志消息转发给期望的输出。它负责接收日志事件,使用Layout格式化事件,然后将其发送给对应的目标。对于一个日志事件,我们可以使用多个Appenders来将事件发送到不同的目标位置。例如,我们可以在控制台上显示一个简单的日志事件的同时,将其通过邮件的方式发送给指定的接收者。目前,Appender可以是console、文件、远程socket服务器、Apache Flume、JMS以及远程 UNIX 系统日志守护进程等。
注意:
在某一个Logger中被启用的log请求将被转发到该Logger相关联的的所有Appenders上,并且还会被转发到LoggerConfig的父级的Appenders上。由此可知,如果设置不当的话,会产生一系列的遗传反应。如:Logger A 继承自 Logger B,并且两者均是root的子节点,若LoggerConfig B 中定义了一个文件作为Appender的话,那么使用LoggerConfig A和LoggerConfig B的logger 的log请求都会在该文件中打印,文件中便会显示很多重复的日志信息,这会给日志分析带来不必要的麻烦。还好这是可以被避免的,通过设置 Appender 的 additivity属性为false即可。(默认情况下,additivity="true")
Layout:
Layout用于定义LogEvent的输出格式。
log4j2配置方式
log4j2 有如下四种配置方式,可以根据你的意愿、喜好等自行选择:
- 通过配置文件进行配置,配置文件可以是 XML、 JSON、 YAML 和 properties这四种类型中的任意一种;
- 在代码中创建 ConfigurationFactory 和 Configuration 累的实现类;
- 在代码中调用Configuration 接口的 API 在默认配置类上添加组件;
- 在代码中调用内部类 Logger 的方法实现自定义。
通过配置文件配置log4j
目前小编还只使用了文件的配置方式,所以本文着重于通过配置文件配置log4j,以后有机会再好好研究一波代码配置的方式,然后与看官分享 _。
文件配置优先级
对应于四类配置文件:XML、 JSON、 YAML 和 properties,Log4j 分别有与之相应的 ConfigurationFactory 实现类,不同的实现类可以加载不同扩展名的配置文件中的配置信息。当 Log4j 启动时会按照一定的优先级顺序在classpath(一般是项目的src文件夹下)查找项目中的配置文件,若找到一个配置文件,就调用与当前文件扩展名对应的ConfigurationFactory实现类加载该配置文件中的配置信息,否则继续查找下一优先级的配置文件,若未找到任何配置文件,就使用默认配置DefaultConfiguration。
不同配置文件的优先级如下图所示:
配置文件详解
如下是个比较完整详细的 XML 形式的配置文件,该文件通过注释的方式提供简要解释以便于理解。
<?xml version="1.0" encoding="UTF-8"?>
<!-- configuration 有多个属性,常用的有:name(配置名)
status(是否记录log4j2本身的event信息,默认是OFF,
可选值有: "trace", "debug", "info", "warn", "error" and "fatal")
dest(错误流输出位置,标准错误流、文件或远程邮件发送)
strict(是否使用strict xml形式,configuration支持concise(简约)和strict(严格)两种模式的xml。
简约模式下,使得文件编写起来更简洁) -->
<configuration status="OFF">
<!-- 定义如下几个引用名,用于后面配置文件中使用 -->
<Properties>
<property name="log_pattern">%d{yyyy-MM-dd HH:mm:ss z} %-5level %class{36}%L%M - %msg%xEx%n
</property><!-- 日志输出格式 -->
<property name="log-path">${web:rootDir}/logs</property><!-- ${web:rootDir}是项目根目录 -->
<property name="every_file_size">5M</property><!-- 日志切割的最小单位 -->
<property name="output_log_level">debug</property><!-- 日志输出级别 -->
</Properties>
<!-- 先定义所有的appender(日志输出位置) -->
<appenders>
<!-- 输出到控制台 name: 当前appender名 target: 目的位置-->
<Console name="Console" target="SYSTEM_OUT">
<!-- ThresholdFilter: 用于定义过滤机制 -->
<!-- level: 日志过滤级别 -->
<!-- onMatch="ACCEPT":保留level 及以上级别的日志 -->
<!-- onMismatch="DENY":丢掉level 以下级别的日志-->
<!-- onMatch 和 onMissmatch 可选值为:ACCEPT DENY NEUTRAL-->
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY" />
<!--这个都知道是输出日志的格式 -->
<PatternLayout pattern="${log_pattern}" />
</Console>
<!-- debug级别日志文件 -->
<!-- RollingFile:日志滚动输出 -->
<!--每次大小超过size,则这size大小的日志会自动进行压缩,作为存档 -->
<RollingFile name="app_debug" fileName="${log-path}/debug/debug.log"
filePattern="${log-path}/debug/debug-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL" />
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="NEUTRAL" />
</Filters>
<PatternLayout pattern="${log_pattern}" />
<!-- SizeBasedTriggeringPolicy: 基于日志大小切分日志 -->
<SizeBasedTriggeringPolicy size="${every_file_size}"/>
</RollingFile>
<!-- info级别日志文件 -->
<RollingFile name="app_info" fileName="${log-path}/info/info.log"
filePattern="${log-path}/info/info-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL" />
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
<PatternLayout pattern="${log_pattern}" />
<SizeBasedTriggeringPolicy size="${every_file_size}"/>
</RollingFile>
<!-- error级别日志文件 -->
<RollingFile name="app_error" fileName="${log-path}/error/error.log"
filePattern="${log-path}/error/error-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
<PatternLayout pattern="${log_pattern}" />
<SizeBasedTriggeringPolicy size="${every_file_size}"/>
</RollingFile>
</appenders>
<!-- 定义logger,只有定义了logger并引入之前定义的appender,appender才会生效 -->
<loggers>
<!--建立一个默认的root的logger,需要在root的level中指定输出的级别, -->
<root level="${output_log_level}">
<appender-ref ref="Console" />
<appender-ref ref="app_debug"/>
<appender-ref ref="app_info"/>
<appender-ref ref="app_error"/>
</root>
</loggers>
</configuration>
log4j2 配置节点分析
configuration
configuration决定了当前上下文的loggers配置信息,是配置文件根节点,主要包含如下属性值:
- name:当前配置的名称
- dest:输出位置,标准错误流、文件或远程邮件发送
- status:是否记录 Log4j 自身的event信息,默认为 off,可选值有: "trace", "debug", "info", "warn", "error" and "fatal"
- strict:是否使用strict xml形式,configuration支持concise(简约)和strict(严格)两种模式的xml。简约模式下,使得文件编写起来更简洁
Appenders
Appenders用于定义当前LoggerContext中所需要使用到的Appender。目前log4j2提供了多种不同类型的appender可供配置。如下介绍几种常用的,如果想了解更全面的信息,可移步官网
Appender类型
ConsoleAppender:
ConsoleAppender是最常用的Appenders之一,它只是将日志消息显示到控制台上。log4j2的默认Appender即为ConsoleAppender。
ConsoleAppender参数如下:
- filter:用于决定是否需要使用该Appender来处理当前日志事件,通过使用CompositeFilter可以组合使用多个Filter;
- layout:用于决定如何对日志记录进行格式化,默认情况下使用“%m%n”,它会在每一行显示一条日志记录;
- follow:用于决定Appender是否需要了解输出(system.out或者system.err)的变化,默认情况是不需要跟踪这种变化;
- name:用于设置Appender的名字;
- ignoreExceptions:默认为true,用于决定是否需要记录在日志事件处理过程中出现的异常;
- target:用于指定输出目标位置,默认情况下使用SYSTEM_OUT,但也可以修改成SYSTEM_ERR
FileAppender:
FileAppenders将日志记录写入到文件中,它负责打开、关闭文件,向文件中追加日志记录,并对文件进行加锁,以免数据被破坏或者覆盖。
FileAppender参数如下:
- append:是否追加写入,默认为true,表示将日志事件追加在文件末尾,若设置为false,则每次都会先清空文件,然后将日志事件写入文件中;
- bufferedIO:默认为true,表示使用缓存,也就是说每次进行日志输出时会先将日志事件输出到缓存中,缓存满或者设置了immediateFlush 时,才将日志事件写入磁盘文件。注意:llocking属性不能与该属性同时使用;
- bufferSize:缓存大小,默认为8192字节;
- createOnDemand:当需要时才创建日志文件,即只有当有日志事件需要被输出到该文件中时才创建文件;
- filter:过滤器,含义与ConsoleAppender中相似;
- fileName:日志文件名;
- immediateFlush:表示每次写完日志后均进行flush,这可以保证每次日志均被写入磁盘中了;
- layout:格式化;
- locking:是否对该日志文件加锁,一般用于多主机场景中,默认为false,该属性对性能影响较大,需要谨慎使用;
- name:Appender名称;
- ignoreExceptions:用法同ConsoleAppender。
RollingFileAppender
滚动输出日志到文件中,RollingFileAppender 根据TriggeringPolicy 和RolloverPolicy滚动将日志事件输出到fileName指定的文件中。TriggeringPolicy用于决定是否触发RolloverPolicy所定义的日志滚动操作,若未设置RolloverPolicy ,则会使用默认设置 DefaultRolloverStrategy。
RollingFileAppender参数如下:
- append、bufferedIO、bufferSize、createOnDemand、filter、fileName、immediateFlush、layout、name、ignoreExceptions等属性与FileAppender类似,就不一一概述了;
- filePattern:存档日志文件的文件名模式,模式格式依赖于所采用的RolloverPolicy ;
- policy:TriggeringPolicy,触发策略;触发策略有如下几种:Composite Triggering Policy(组合策略)、CronTriggeringPolicy(定时触发)、OnStartupTriggeringPolicy(JVM虚拟机启动时触发)、SizeBasedTriggeringPolicy(基于日志文件大小触发)、TimeBasedTriggeringPolicy(基于时间触发)
- strategy:RolloverStrategy,决定存档文件名称、数量和位置的策略。
其它Appender:
SyslogAppenders将日志记录发送给本地或者远程系统的日志服务;SMTPAppender会将日志内容以邮件的形式发送出去;FailoverAppender设置在处理日志的过程中,如果一个或者多个Appender失败,自动切换到其他Appender上等,更多信息详见官网
Layout
log4j2中使用较多的Layout为PatternLayout,格式为:
<PatternLayout pattern = "%d{yyyy-MM-dd HH:mm:ss z} %-5level %class{36}%L%M - %msg%xEx%n" />
%d{HH:mm:ss:SSS}:日志输出时间,精确到毫秒
%t:当前线程名称
%-5level:日志级别,-5表示左对齐并固定输出5个字符,如果不足右边补零
%logger:logger名称
%msg:日志文本
%n:换行
%F:文件所在的类文件名
%L:行号
%M:所在方法名
%l:语句所在的行数,包括类名,方法名,文件名,行数