日志框架总结(一)
一、日志概述
一个在生产环境里运行的程序如果没有日志是很让维护者提心吊胆的,有太多杂乱又无意义的日志也是令人伤神。程序出现问题时候,从日志里如果发现不了问题可能的原因是很令人受挫的。本文想讨论的是如何在Java程序里写好日志。
一般来说日志分为两种:业务日志和异常日志,使用日志我们希望能达到以下目标:
1、对程序运行情况的记录和监控;
2、在必要时可详细了解程序内部的运行状态;
3、对系统性能的影响尽量小;
二、Java日志框架
java里常见的日志库有java.util.logging(JDKlog)、Apache log4j、log4j2、logback、slf4j等等,这么多的日志框架里如何选择。
2.1、日志迭代过程
首先需要梳理的是日志接口,以及日志接口对应的实现。然后再考虑如何选择使用哪个日志框架。
下图说明了几个日志框架的关系:
上述关系图,大致就是两个 API 接口 + 4个实现框架。我们应用服务调用日志框架,只需遵循 API 接口规范,配置调用即可。
现在了解下上面接口及框架迭代历史进程,如下图:
-
Log4j
Log4j : 是apache下一个功能非常丰富的java日志库实现,Log4j应该是出现比较早而且最受欢迎的java日志组件,它是基于java的开源的日志组件。Log4j的功能非常强大,通过Log4j可以把日志输出到控制台、文件、用户界面。也可以输出到操作系统的事件记录器和一些系统常驻进程。值得一提的是:Log4j可
以允许你非常便捷地自定义日志格式和日志等级,可以帮助开发人员全方位的掌控自己的日志信息是一个日志开源框架。
这个是最先出现流行的日志框架。 -
Java.util.logging
Log4j 流行之时,java自身也不忘在这方面发力,于是java 1.4 之后,java原生库也写了一套日志框架 即是 Java.util.logging。
此时,也有很多厂家的日志框架在共同竞争,各自为营,相互兼容性很差。导致应用软件API 在相互调用受阻。于是,apache 就站出来,设计了一套公共接口 – commons-logging ,来兼容各方面的日志框架,便于实际开发进程; -
Commons-logging(jcl)
1、 在log4j , java.util.logging 等问世之后,相对于日志输出就简化很多,但是由于没有统一规范,开发者调用这些框架也是很麻烦,相互兼容性也很差(不同厂家的方法各不一样),同时对日志框架迭代更新维护带来不确定性,于是 apache 为规范日志输出,定义一套标准日志API接口 ---- commons-logging API 接口。开发者,只需关注接口,而不需看重实现细节,极大提供效率。
2、尽管 commons-logging(jcl) 也为众多日志实现库提供了统一的接口,作用和slf4j类似。它允许运行时绑定任意的日志库。但commons-loggins对log4j和java.util.logging的配置问题兼容性不太好,还会遇到类加载问题。所以当时log4j的作者CEKI又创作了 SLF4j API 接口。 -
SLF4j:
1、它是基于API的java日志框架,slf4j提供了简单统一的日志记录的接口,开发者在配置部署时只需要是吸纳这个接口就能是实现日志功能。它自身并没有提供具体的日志解决方案,它是负责服务于各种各样的日志系统,允许用户在部署应用上使用自己常用的日志框架。也就是说,SLF4j是一个抽象层,它提供了众多的适配器能是配合其他所有开源日志框架。
2、为了考虑其他项目会使用大量的第三方库,而第三方库使用的日志框架又各不相同,不同的日志框架又需要不同的配置,不同配置就会导致日志输出到不同的位置。所以我们就需要一个可以将日志level、日志输出等统一管理,而slf4j的适配器又对各种日志都实现了接管,接管后就可以统一配置这些第三方库中使用的日志。 -
logback
对比 Log4j的优势:
1、更快的实现:Logback的内核重写了,在一些关键执行路径上性能提升10倍以上。而且logback不仅性能提升了,初始化内存加载也更小了。
2、非常充分的测试:Logback经过了几年,数不清小时的测试。Logback的测试完全不同级别的。
3、Logback-classic非常自然实现了SLF4j:Logback-classic实现了SLF4j。在使用SLF4j中,你都感觉不到logback-classic。而且因为logback-classic非常自然地实现了slf4j , 所 以切换到log4j或者其他,非常容易,只需要提供成另一个jar包就OK,根本不需要去动那些通过SLF4JAPI实现的代码。
4、非常充分的文档 官方网站有两百多页的文档。
5、自动重新加载配置文件,当配置文件修改了,Logback-classic能自动重新加载配置文件。扫描过程快且安全,它并不需要另外创建一个扫描线程。这个技术充分保证了应用程序能跑得很欢在JEE环境里面。
6、Lilith是log事件的观察者,和log4j的chainsaw类似。而Lilith还能处理大数量的log数据 。
7、谨慎的模式和非常友好的恢复,在谨慎模式下,多个FileAppender实例跑在多个JVM下,能够安全地写道同一个日志文件。RollingFileAppender会有些限制。Logback的FileAppender和它的子类包括 RollingFileAppender能够非常友好地从I/O异常中恢复。
8、配置文件可以处理不同的情况,开发人员经常需要判断不同的Logback配置文件在不同的环境下(开发,测试,生产)。而这些配置文件仅仅只有一些很小的不同,可以通过,和来实现,这样一个配置文件就可以适应多个环境。
9、Filters(过滤器)有些时候,需要诊断一个问题,需要打出日志。在log4j,只有降低日志级别,不过这样会打出大量的日志,会影响应用性能。在Logback,你可以继续 保持那个日志级别而除掉某种特殊情况,如alice这个用户登录,她的日志将打在DEBUG级别而其他用户可以继续打在WARN级别。要实现这个功能只需加4行XML配置。可以参考MDCFIlter 。
10、SiftingAppender(一个非常多功能的Appender):它可以用来分割日志文件根据任何一个给定的运行参数。如,SiftingAppender能够区别日志事件跟进用户的Session,然后每个用户会有一个日志文件。
11、自动压缩已经打出来的log:RollingFileAppender在产生新文件的时候,会自动压缩已经打出来的日志文件。压缩是个异步过程,所以甚至对于大的日志文件,在压缩过程中应用不会受任何影响。
12、堆栈树带有包版本:Logback在打出堆栈树日志时,会带上包的数据。
13、自动去除旧的日志文件:通过设置TimeBasedRollingPolicy或者SizeAndTimeBasedFNATP的maxHistory属性,你可以控制已经产生日志文件的最大数量。如果设置maxHistory 12,那那些log文件超过12个月的都会被自动移除。 -
Log4j2
Log4j2 的特性:
1、API分离:Log4j的API与实现是分开的,从而使应用程序开发人员可以清楚地了解他们可以使用哪些类和方法,同时确保向前的兼容性。这允许Log4j团队以兼容的方式安全地改进实施。
2、性能提升:Log4j 2包含基于LMAX Disruptor库的下一代异步记录器。在多线程方案中,与Log4j 1.x和Logback相比,异步Logger的吞吐量高18倍,延迟降低了几个数量级。
3、支持多种API:Log4j 2 API将提供最佳性能,而Log4j 2提供对Log4j 1.2,SLF4J,Commons Logging和java.util.logging(JUL)API的支持。
4、避免锁定:编码为Log4j 2 API的应用程序始终可以选择使用任何SLF4J兼容库作为其Log4j-to-slf4j适配器的记录器实现。利用jdk1.5并发的特性,减少了死锁的发生;
5、自动重载配置:与Logback一样的是,Log4j 2可以在修改后自动重新加载其配置。与Logback不同的是,它在进行重新配置时不会丢失日志事件。丢数据这种情况少,可以用来做审计功能。而且自身内部报的exception会被发现,但是logback和log4j不会。
6、进阶筛选:与Logback一样,Log4j 2支持基于上下文数据,标记,正则表达式和Log事件中的其他组件进行过滤。可以指定过滤以将所有事件应用到所有事件,然后再传递给Logger或事件通过Appender。此外,过滤器还可以与Loggers关联。与Logback不同,您可以在任何这些情况下使用通用的Filter类。
7、插件架构:Log4j使用插件模式来配置组件。这样,您无需编写代码即可创建和配置Appender,Layout,Pattern Converter等。Log4j自动识别插件,并在配置引用它们时使用它们。
8、物业支持:您可以在配置中引用属性,Log4j将直接替换它们,或者Log4j将它们传递给将动态解析它们的基础组件。属性来自配置文件中定义的值,系统属性,环境变量,ThreadContext映射以及事件中存在的数据。用户可以通过添加自己的查找插件来进一步自定义属性提供程序。
9、Java 8 Lambda支持:以前,如果构建日志消息的成本很高,则通常会在构建消息之前显式检查是否启用了请求的日志级别。在Java 8上运行的客户端代码可以受益于Log4j的lambda支持。如果未启用请求的日志级别,由于Log4j不会评估lambda表达式,因此可以用更少的代码获得相同的效果。
10、自定义日志级别:在Log4j 2中,可以通过代码或配置轻松定义自定义日志级别。不需要子类。
11、无垃圾:在稳态日志记录期间,Log4j 2 在独立应用程序中是无垃圾的,而在Web应用程序中是低垃圾的。这样可以减少垃圾收集器上的压力,并可以提供更好的响应时间性能。【拥有号称能够减少 JVM 垃圾回收停顿时间的 Garbage-free(无垃圾模式)】
12、与应用服务器集成:版本2.10.0引入了模块log4j-appserver,以改善与Apache Tomcat和Eclipse Jetty的集成。
13、启用云:2.12.0版引入了对通过Lookup访问Docker容器信息以及通过Spring Cloud Configuration访问和更新Log4j配置的支持。
2.2、Logback vs Log4j2
日志框架大战随着 SLF4j 的一统天下而落下帷幕,但 SLF4j 仅仅是接口,实现方面, logback 与 log4j2 仍然难分高下,接下来聊聊,日志框架实现到底是该选择 Log4j2 还是 Logback。这篇文章我们将从功能、API 设计、可扩展性、性能四个方面展开讨论。
- 生态
- 老牌的 Log4j2 凭借着入场早、背靠 Apache 两大优势有着不错的用户支持,官网文档也很完善。
- 新生的 Logback 凭借着 SLF4j 的原生实现以及被 Spring Boot 钦点的日志框架(Spring 也提供了Log4j2 的 starter,切换依赖即可完成更换日志框架,前文讲过,此处不再赘述),同样也拥有着很好的前景。
- 社区支持方面,Log4j2 作为 Apache 顶级项目,支持度应该是很不错的,Logback 作为Ceki创业后的产物,也能有很好的保证,二者生态可谓不相上下;
- 功能:我们从使用者的角度可以分为:配置、使用、以及独有特性。
-
配置文件方面,Log4j 提供了更多的配置文件配置方式,Log4j2 支持 properties、YAML、JSON、XML四种,Logback 则支持 XML 与 groovy 两种方式;
-
Appender 方面,两者均支持自定义扩展 Appender ,Log4j2 并且提供了几乎全部场景的 Appender,文件、控制台、JDBC、NoSql、MQ、Syslog、Socket、SMTP等,Logback提供 Appender 略少于 Log4j2,提供了文件、控制台、数据库、Syslog、Socket、SMTP等,动态化输出方面,Log4j2 提供了ScriptAppenderSelector,Logback 则提供了 Evaluator 与 SiftingAppender(两者均可以用于判断并选择对应的 Appender);
-
独有特性方面,Logback 支持 Receivers, 可以接收其他 Logback 的 Socket Appender 输出,Logbak 还拥有 logback-access 模块,可以用来与 Servlet容器(如 Tomcat 和 Jetty)集成,提供 http 访问日志功能;Log4j2 则拥有号称能够减少 JVM 垃圾回收停顿时间的 Garbage-free(无垃圾模式),Log4j2 API 支持使用 Java 8 lambda,SLF4j 则在 2.0 版本提供流式(Fluent)API 同时支持 lambda;
-
API 设计及可扩展性
如前文所说,SLF4j 则在 2.0 版本提供流式(Fluent)API ,届时Logback将会原生实现(理论上会比动态转译过去要好),而 Log4j2 并没有提供支持。扩展方面,Logback 采用配置文件中直接写对应实现(class=“ch.qos.logback.core.rolling.RollingFileAppender”)来自定义实现扩展,Log4j2 采用插件机制,无需配置,但比较复杂,个人认为 Logback 反而清晰一些。 -
性能
对于高并发,大量日志输出,Log4j2 确实有一定优势。因为其内部的队列使用的是disruptor。异步输出性能提升不少,是实话!
但是,对于日志框架应用,我们要求可能并不需要那么高,即对于一般的日志输出,Logback 与 Log4j2 性能方面差不多;
具体参考: https://www.jianshu.com/p/359b14067b9e,http://www.cainiaoxueyuan.com/bc/17731.html -
总结
Logback 使用更简单、Log4j2 功能更强大,如果不是深度使用,两者并不会有太大差别,并且在使用SLF4j的时候可以无缝切换。建议,不必纠结选型,按照偏好选择即可。
三、实际应用及区分
3.1、 Logback 实践:
3.1.1、快速入门
第1步: 对于一般 Maven 项目,引入依赖
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
第2步:导入 logback.xml 即是logback日志输出配置文件,如下
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- <property name="LOG_HOME" value="/home/core/project"/> -->
<property name="LOG_HOME" value="D:/log/" />
<property name="APP_NAME" value="lolo"/>
<!-- 控制台输出-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{64}[%line] - %msg%n</Pattern>
</layout>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="DATELOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${LOG_HOME}${APP_NAME}.log</File>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<FileNamePattern>${LOG_HOME}/${APP_NAME}.%i.bak</FileNamePattern>
<MinIndex>1</MinIndex>
<MaxIndex>30</MaxIndex>
</rollingPolicy>
<triggeringPolicy
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>100MB</MaxFileSize>
</triggeringPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{64}[%line] - %msg%n</Pattern>
</layout>
</appender>
<!-- 按照每天生成日志文件 错误信息 -->
<appender name="DATELOG_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${LOG_HOME}${APP_NAME}.error.log</File>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"><FileNamePattern>${LOG_HOME}/${APP_NAME}.error.%i.bak</FileNamePattern>
<MinIndex>1</MinIndex>
<MaxIndex>30</MaxIndex>
</rollingPolicy>
<triggeringPolicy
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>100MB</MaxFileSize>
</triggeringPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{64}[%line] - %msg%n</Pattern>
</layout>
</appender>
<logger name="com.jieshun.jscp.p" level