log4j2.x架构分析与实战

来源:互联网 发布:淘宝会员号是什么 编辑:程序博客网 时间:2024/05/20 18:01

一、 日志的重要性
对于我们开发人员来说,日志记录往往不被重视。在生产环境中,日志是查找问题来源的重要依据。日志可记录程序运行时产生的错误信息、状态信息、调试信息和执行时间信息等多种多样的信息。可以在程序运行出现错误时,快速地定位潜在的问题源。目前常用的日志系统有java.util.logging、commons logging、slf4j、log4j1.x、logback、log4j2.x 等若干种。
二、 Java常用日志框架历史
 1996年早期,欧洲安全电子市场项目组决定编写它自己的程序跟踪API(Tracing API)。经过不断的完善,这个API终于成为一个十分受欢迎的Java日志软件包,即log4j。后来log4j成为Apache基金会项目中的一员。
 期间log4j近乎成了Java社区的日志标准。据说Apache基金会还曾经建议Sun引入log4j到java的标准库中,但Sun拒绝了。
 2002年Java1.4发布,Sun推出了自己的日志库jul(java util logging),其实现基本模仿了log4j的实现。在JUL出来以前,log4j就已经成为一项成熟的技术,使得log4j在选择上占据了一定的优势。
 接着,Apache推出了jakarta commons logging,jcl只是定义了一套日志接口(其内部也提供一个simple log的简单实现),支持运行时动态加载日志组件的实现,也就是说,在你的应用代码里,只需调用commons logging的接口,底层实现可以是log4j,也可以是java util logging。
 后来(2006年),Ceki Gülcü不适应Apache的工作方式,离开了Apache。然后先后创建了slf4j(日志门面接口,类似于commons logging)和logback(slf4j的实现)两个项目,并回瑞典创建了QOS公司,QOS官网上是这样描述logback的:The Generic,Reliable Fast&Flexible Logging Framework(一个通用,可靠,快速且灵活的日志框架)。
 现今,Java日志领域被划分为两大阵营:commons logging阵营和slf4j阵营。
 commons logging在Apache大树的笼罩下,有很大的用户基数。但有证据表明,形式正在发生变化。2013年底有人分析了GitHub上30000个项目,统计出了最流行的100个Libraries,可以看出slf4j的发展趋势更好。如下图1所示。
这里写图片描述
图1
 Apache眼看有被logback反超的势头,于2012年7月重写了log4j 1.x,成立了新的项目log4j2.x。log4j2在各个方面都与logback非常相似。
三、 常用日志框架介绍
这些日志系统有什么区别,开发时如何选择适合自己项目的日志系统呢?日志系统可分为两类。一类只提供接口不提供实现,如 Apache Commons Logging 和 slf4j。这类日志系统需要和具体的日志系统一起使用,优点是可以自由切换不同的日志实现系统。如果你的项目以后要打成 jar 包被别的系统使用或者你以后可能更换日志系统,通常使用这一类日志系统。另一类是具体的日志系统实现。如 log4j1.x、logback、log4j2.x、java.util.logging 等。图2清晰的展示了 slf4j 和其它日志系统的关系。
这里写图片描述
图2
slf4j(Simple Logging Facade for Java),面向 java 的简单日志门面,人如其名,slf4j 是其他日志的门面,它提供统一的接口,并不提供实现,不是具体的日志系统。
log4j1 曾经被广泛使用,2015年8月已停止更新,logback和log4j2作为它的替代者,拥有更好的性能,有很多的改进,下面重点讲一下 logback 和 log4j2 的特性。
logback相比 log4j1 的优点:
1、 性能的提升。Logback 的内核重写了,在某些特定的场景上性能提升 10 倍以上,同时所需的内存更加少。
2、 非常充分的测试。Logback经过了几年,数不清小时的测试,与log4j1的测试相比不在同一个级别,因此 logback 更稳定可靠。
3、 非常自然的实现了slf4j。而log4j1 和 slf4j 一起使用需要适配层。
4、 自动重新加载配置文件。当配置文件修改了,Logback-classic能自动重新加载配置文件,不需要重启服务器。
5、 优雅地从I/O错误中恢复。如果一个文件服务器临时宕机,你不需要重启应用,日志功能就能正常工作。
6、 配置文件可适应多环境。通常在开发、测试、生产环境,需要变换日志的配置文件。而在不同环境下,配置文件只有一些很小的不同,为了避免重复,logback支持使用,和进行条件处理,同一个配置文件就可以在不同的环境中使用了。
7、 过滤器。生产环境中,有时需要低级别的日志来查明问题,在log4j1 中,只有降低日志级别,这样的话会打出大量的日志而影响性能。而 logback 中,你可以继续保持那个日志级别而除掉某种特殊情况。
8、 自动压缩归档日志文件。压缩通常是异步执行的,所以即使是很大的日志文件,你的应用都不会因此而被阻塞。
9、 通过配置自动去除旧的日志文件。
log4j2 的优点:
log4j2在各个方面都与logback非常相似,那么为什么我们还需要log4j 2呢?
1、 可配置的审计型日志。log4j1和logback在重新配置的时候会丢失之前的日志文件,log4j2不会。log4j2自身内部报的exception会被发现,但是log4j1和logback不会。
2、 下一代异步logger。log4j 2是基于LMAX Disruptor库的(一个用于在线程间通信的高效低延迟且简单的框架),在多线程场景下,它的日志吞吐量比其他框架多出10倍以上。
3、 可运行在免垃圾收集模式。这样可以减少垃圾收集器的压力和提供更好的响应时间性能。
4、 插件式结构。可根据自己的需要扩展框架,可以实现自己的logger、appenders、filters、layouts、lookups 和pattern converters,而无需对log4j2做任何更改。
5、 Java 5的并发性。log4j2利用Java 5中的并发特性支持,尽可能地执行最低层次的加锁,log4j1中存留的死锁问题,很多已经在logback中解决,但logback的很多类仍然保持着较高层次的同步。如果你的程序仍然饱受内存泄漏的折磨,请毫不犹豫地试一下log4j2。
图3来源于 Apache Logging PMC成员Christian Grobmeier 在2013年7月发表的一篇标题为《Log4j 2:性能几近于疯狂》的文章。
这里写图片描述
图3
四、 log4j2.x 实战
log4j2.x有两个 jar: log4j-api 和它的实现 log4j-core,需要将它们放在项目的 classpath中,使用 maven 管理项目,在 pom.xml 中添加如下依赖:
这里写图片描述
下面是一个简单的例子,定义一个 Logger 对象,然后调用不同级别的方法就可以记录日志了,运行结果只输出了 error 和 fatal 级别的日志。
这里写图片描述

这里没有加配置文件,使用了默认的配置。下面这个配置文件等同于默认配置。
这里写图片描述
默认配置一般不能满足实际开发需要,后面会再讲解复杂一些的配置文件。先来了解一下log4j2中配置文件的加载顺序,下面这个加载顺序摘自官方文档。
这里写图片描述
五、 log4j2 架构分析
log4j2 主要类图如图4所示。
这里写图片描述
图4
1、 LoggerContext(日志上下文)
LoggerContext在日志系统中扮演锚点的角色。依赖于具体环境,在一个应用程序中有多个活动的loggercontext是有可能的。在同一LoggerContext下,log system是互通的。
2、 Configuration(配置)
一个LoggerContext关联一个Configuration,Configuration包含了所有的Appenders(输出器)、上下文范围内的Filter(过滤器)、LoggerConfigs以及StrSubstitutor的引用。在重配置期间,新与旧的Configuration将同时存在。当所有的Logger对象都被重定向到新的Configuration对象后,旧的Configuration对象将被停用和丢弃。
3、 Logger(日志记录器)
程序中记录日志时,首先需要获取一个日志记录器对象。如前所述,Logger 是通过调用LogManager.getLogger方法获得的。记录器对象通常有一个name并和一个LoggerConfig 相关联。当Configuration改变时,Logger将会与另外的LoggerConfig相关联,从而改变这个Logger的行为。
一般使用当前 java 类的名称或所在包的名称作为记录器对象的名称。这是一个很有用且很直接的Logger命名方式,使用这种方式命名可以很容易的定位这个log message产生的类的位置。当然,log4j也支持任意String的命名方式以满足开发者的需要。不过,使用类名来定义Logger名仍然是最为推崇的一种Logger命名方式。
获得Logger时,使用相同的名称参数调用getLogger方法将获得同一个Logger对象的引用。如:
这里写图片描述
4、 LoggerConfig(日志配置器)
LoggerConfig是Logger的配置对象,一个LoggerConfig可以绑定多个不同的Appender。只有定义了LoggerConfig并引入的appender,appender才会生效。LoggerConfig包含了一组过滤器,LogEvent在被传往Appender之前将先经过这些过滤器。
LoggerConfig有级别配置,低于配置级别的日志消息将不会记录下来。Log4j2中,级别由高到低如下所列:
FATAL:用于立马要被注意的情形。
ERROR:通用的 bug。
WARN:不一定是 bug ,但想要知道的这个情况。
INFO:一般的诊断信息。
DEBUG:低层次的调试信息。
TRACE:展现程序执行轨迹。
LoggerConfig有自己的名称,root LoggerConfig除外。名称有层次结构,与包层次结构相对应,存在着继承关系。如“com.myapp”是 “com.myapp.action”的父包。若 “com.myapp.action”没有设定级别,则按照层次关系往上查找,直到找到一个设置了级别的LoggerConfig,继承其级别。如果没有找到,则使用根LoggerConfig(root)配置的级别。root LoggerConfig 是 LoggerConfig 层次结构的最顶层。
5、 Filter(过滤器)
与防火墙过滤的规则相似,log4j2的过滤器也将返回三类状态:Accept(接受), Deny(拒绝) 或Neutral(中立)。其中,Accept意味着不用再调用其他过滤器了,这个LogEvent将被执行;Deny意味着忽略这个event,并将此event的控制权交还给过滤器的调用者;Neutral则意味着这个event应该传递给别的过滤器,如果再没有别的过滤器可以传递了,那么就由现在这个过滤器来处理。
6、 Layout(格式化器)
对记录器所记录的文本进行格式化。
7、 Appender(输出器)
日志输出目的地。如控制台、文件、远程socket server、数据库等。
六、 配置文件详解
这里写图片描述

1、 configuration标签中的常用元素说明
status:用于设置log4j2自身内部的日志记录的等级,只对Log4j本身有效。
monitorInterval:Log4j 2自动重新加载配置文件的时间间隔秒数(如果更改配置文件,不用重启系统)
2、 properties 标签说明
在复杂的项目中,可能有一些属性会在多处使用,比如配置文件路径、项目名称等。可以将这些属性配置在 properties 中,之后使用${property_name}对其进行引用。
3、 appenders标签说明
appender有非常多的节点可选,这是只简单介绍最常见的Console、File、RollingFile。
Console节点为输出控制台配置,target 将输出结果写到 System.err或System.out。ThresholdFilter是对日志级别进行过滤。PatternLayout配置输出日志的格式,配置方式类似C语言的printf。%data为日期时间格式,%thread为线程名称,%-5level 为日志级别,%logger为日志名称,%msg为日志信息,%n为换行。
File即将结果输出到一个指定文件中。Append表示是否追加,默认为ture。ture是将新日志追加到原日志文件尾部,false则是清空已有文件。
RollingFile和File一样,都是将日志写入到文件中,但File对文件的约束很简单,而RollingFile则较为灵活,比如多大文件时分拆,最多可以分拆成几个文件等。另外,RollingFile需要触发条件。

<Policies>    <OnStartupTriggeringPolicy />    <SizeBasedTriggeringPolicy size="20MB" />    <TimeBasedTriggeringPolicy /></Policies>

OnStartupTriggeringPolicy没有参数,每次JVM重新运行都会触发。
SizeBasedTriggeringPolicy则是由文件大小触发,单位可以是KB/MB/GB。
TimeBasedTriggeringPolicy则是根据日期、时间来触发,例如每隔几个小时触发。
另外还有控制保存文件的规则,比如同一天最多保存多少个文件,默认是 7 个文件。
4、 loggers标签说明
它被用来配置LoggerConfig,包含一个root logger和若干个普通logger。普通logger必须有一个name属性,root logger没有name属性。每个logger可以指定一个level(TRACE, DEBUG, INFO, WARN, ERROR, ALL or OFF),不指定时level继承其指定level的父类的level。additivity指定是否同时输出log到父类的appender,缺省为true。
一个Logger可以绑定多个不同的Appender。只有定义了logger并引入的appender,appender才会生效。
七、 怎样记日志
日志记录要有上下文,下面列出几点常见的日志记录位置。
1、 重要方法的输入和输出。
2、 对外部系统调用前后都记下日志,方便接口调试。
3、 重要状态的变化应该记录下来,出问题时方便推断程序运行过程。
4、 业务异常(catch)。
5、 日志中应包含充分的信息。比如在网上支付功能中,与支付相关的日志应该完整的包含当前用户、订单以及支付方式等全部信息。一种比较常见的做法是把相关的日志记录分散在由不同日志记录器所记录的日志中。当出现问题之后,需要手工查找并匹配相关的日志来定位问题,所花费的时间和精力会更多。因此,应该尽可能在一条日志记录中包含足够多的信息。
不好的日志写法如下代码所示:

这里写图片描述
好的日志写法如下代码所示:
这里写图片描述

八、 参与文献
[1] http://logging.apache.org/log4j/2.x/manual/architecture.html
[2] http://www.linuxidc.com/Linux/2016-05/130976.htm
[3] https://my.oschina.net/xianggao/blog/523020