28

Java各类日志组件分析汇总

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzAxNDEwNjk5OQ%3D%3D&%3Bmid=2650408994&%3Bidx=1&%3Bsn=5eff3507a83861c77a3f0a5b9dfa69aa
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

MVb2ai6.gif

作为一名开发人员,相信大家对日志工具不会陌生 Java 也 拥有功能和性能都非常强大的日志库; 不过这么多日志工具&第三方的包,怎样保证每个 组件里都能使用约定好的日志工具?

本文将和大家介绍一下  Java 主流的日志工具,以及相对应的使用场景。

基本介绍

在java的世界里有许多实现日志功能的工具,最早得到广泛使用的是 log4j,现在比较流行的是slf4j+logback。作为开发人员,我们有时候需要封装一些组件(二方包)提供给其他人员使用,但是那么多的日志工具,根本没法保证每个组件里都能使用约定好的日志工具,况且还有很多第三方的包,鬼知道他会用什么日志工具。假如一个应用程序用到了两个组件,恰好两个组件使用不同的日志工具,那么应用程序就会有两份日志输出了,蛋疼吧。。

下面简单介绍下常见的日志工具:

  JUL

JUL 全称 java.util.logging.Logger,JDK 自带的日志系统,从 JDK1.4 就有了。因为 log4j 的存在,这个 logger 一直沉默着,其实在一些测试性的代码中,jdk自带的 logger 比 log4j 更方便。JUL是自带具体实现的,与 log4j、logback 等类似,而不是像 JCL、slf4j 那样的日志接口封装。

import java.util.logging.Level;
import java.util.logging.Logger;
 
private static final Logger LOGGER = Logger.getLogger(MyClass.class.getName());
  • 相同名字的Logger对象全局只有一个;

  • 一般使用圆点分隔的层次命名空间来命名 Logger Logger 名称可以是任意的字 符串,但是它们一般应该基于被记录组件的包名或类名,如 java.net javax.swing

  • 配置文件默认使用 jre/lib/logging.properties ,日志级别默认为 INFO

  • 可以通过系统属性 java.util.logging.config.file 指定路径覆盖系统默认文件;

  • 日志级别由高到低 依次为: SEVERE (严重)、 WARNING (警告)、 INFO (信息)、 CONFIG (配置)、 FINE (详细)、 FINER (较详细)、 FINEST (非常详细)。 另外还有两个全局开关: OFF 「关闭日志记录」和 ALL 「启用所有消息日志记录」。

  • logging.properties 》文件中,默认日志级别可以通过 .level= ALL 来控制,也可以基于层次命名空间来控制,按照 Logger 名字进行前缀匹配,匹配度最高的优先采用; 日志级别只认大写;

  • JUL 通过 handler 来完成实际的日志输出,可以通过配置文件指定一个或者多个 hanlder ,多个 handler 之间使用逗号分隔; handler 上也有一个日志级别,作为该 handler 可以接收的日志最低级别,低于该级别的日志,将不进行实际的输出; handler 上可以绑定日志格式化器,比如 java.util.logging.ConsoleHandler 就是使 用的 String.form at 来支持的;

配置文件示例:

handlers= java.util.logging.ConsoleHandler
 
.level= ALL
com.suian.logger.jul.xxx.level = CONFIG
com.suian.logger.jul.xxx.demo2.level = FINE
com.suian.logger.jul.xxx.demo3.level = FINER
 
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format=%1$tF %1$tT [%4$s] %3$s -  %5$s %n

  Apache Commons Logging

之前叫Jakarta Commons Logging,简称JCL,是Apache提供的一个通用日志API,可以让应用程序不再依赖于具体的日志实现工具。

commons-logging包中对其它一些日志工具,包括Log4J、Avalon LogKit、JUL等,进行了简单的包装,可以让应用程序在运行时,直接将JCL API打点的日志适配到对应的日志实现工具中。

common-logging通过动态查找的机制,在程序运行时自动找出真正使用的日志库。这一点与slf4j不同,slf4j是在编译时静态绑定真正的Log实现库。

commons-logging包里的包装类和简单实现列举如下:

  • org.apache.commons.logging.impl.Jdk14Logger,适配JDK1.4里的JUL;

  • org.apache.commons.logging.impl.Log4JLogger,适配Log4J;

  • org.apache.commons.logging.impl.LogKitLogger,适配avalon-Logkit;

  • org.apache.commons.logging.impl.SimpleLog,common-logging自带日志实现类,它实现了Log接口,把日志消息都输出到系统错误流System.err中;

  • org.apache.commons.logging.impl.NoOpLog,common-logging自带日志实现类,它实现了Log接口,其输出日志的方法中不进行任何操作;

如果只引入Apache Commons Logging,也没有通过配置文件《commons-logging.properties》进行适配器绑定,也没有通过系统属性或者SPI重新定义LogFactory实现,默认使用的就是jdk自带的java.util.logging.Logger来进行日志输出。

JCL使用配置文件commons-logging.properties,可以在该文件中指定具体使用哪个日志工具。不配置的话,默认会使用JUL来输出日志。配置示例:

  Avalon LogKit

Avalon LogKit是一个高速日志记录工具集,Avalon里的各个组件Framework、Excalibur、Cornerstone和Phoenix都用到它。它的模型与JDK 1.4 Logging package采用相同的原理,但与JDK 1.2+兼容。使用LogKit的原因是:Context和LogTargets。

使用Log4j的时候,日志的内容只能是一句话,而使用LogKit,你可以记录很多项内容,甚至可以各项内容记录到对应的数据库字段中。如果使用Log4j存储日志到不同的存储介质,如数据库,需要使用Appender,而LogKit已经可以支持多种存储目标。

   log4j

Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、数据库等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。

Log4j有7种不同的log级别,按照等级从低到高依次为:TRACE、DEBUG、INFO、WARN、ERROR、FATAL、OFF。如果配置为OFF级别,表示关闭log。Log4j支持两种格式的配置文件:properties和xml。包含三个主要的组件:Logger、appender、Layout。

  SLF4J

SLF4J全称The Simple Logging Facade for Java,简单日志门面,这个不是具体的日志解决方案,而是通过门面模式提供一些Java Logging API,类似于JCL。题外话,作者当时创建SLF4J的目的就是为了替代Jakarta Commons Logging(JCL)。

SLF4J提供的核心API是一些接口以及一个LoggerFactory的工厂类。在使用SLF4J的时候,不需要在代码中或配置文件中指定你打算使用哪个具体的日志系统,可以在部署的时候不修改任何配置即可接入一种日志实现方案,在编译时静态绑定真正的Log库。

使用SLF4J时,如果你需要使用某一种日志实现,那么你必须选择正确的SLF4J的jar包的集合(各种桥接包)。SLF4J提供了统一的记录日志的接口,只要按照其提供的方法记录即可,最终日志的格式、记录级别、输出方式等通过具体日志系统的配置来实现,因此可以在应用中灵活切换日志系统。

logback是slf4j-api的天然实现,不需要桥接包就可以使用。另外slf4j还封装了很多其他的桥接包,可以使用到其他的日志实现中,比如slf4j-log4j12,就可以使用log4j进行底层日志输出,再比如slf4j-jdk14,可以使用JUL进行日志输出。

   Logback

Logback,一个“可靠、通用、快速而又灵活的Java日志框架”。Logback当前分成三个模块:logback-core,logback- classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是log4j的一个改良版本,完整实现了SLF4J API。

logback-access模块与Servlet容器集成提供通过Http来访问日志的功能。Logback依赖配置文件logback.xml,当然也支持groovy方式。Logback相比log4j,有很多很多的优点,网上一搜一大片,此处就不再赘述了。

   Log4j2

Log4j 2是log4j 1.x和logback的改进版,据说采用了一些新技术(无锁异步等等),使得日志的吞吐量、性能比log4j 1.x提高10倍,并解决了一些死锁的bug,而且配置更加简单灵活。

Log4j2支持插件式结构,可以根据需要自行扩展Log4j2,实现自己的appender、logger、filter等。在配置文件中可以引用属性,还可以直接替代或传递到组件,而且支持json格式的配置文件。不像其他的日志框架,它在重新配置的时候不会丢失之前的日志文件。

Log4j2利用Java5中的并发特性支持,尽可能地执行最低层次的加锁。解决了在log4j 1.x中存留的死锁的问题。Log4j 2是基于LMAX Disruptor库的。在多线程的场景下,和已有的日志框架相比,异步logger拥有10倍左右的效率提升。

Log4j2体系结构:

eA36re2.png!web

使用场景

   只使用java.util.logging.Logger

最简单的场景,正式系统一般不会这么用,自己写点小demo、测试用例啥的是可以这么用。不要任何第三方依赖,jdk原生支持。

   只使用Apache Commons Logging

需要引入commons-logging包,示例如下:

      <dependency>
          <groupId>commons-logging</groupId>
          <artifactId>commons-logging</artifactId>
          <version>1.2</version>
      </dependency>

   Apache Commons Logging和log4j结合使用

需要引入commons-logging包和log4j包,示例如下:

      <dependency>
          <groupId>commons-logging</groupId>
          <artifactId>commons-logging</artifactId>
          <version>1.2</version>
      </dependency>
      <dependency>
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
          <version>1.2.17</version>
      </dependency>

该模式下可以使用的打点api:

  • org.apache.commons.logging.Log,commons-logging里的api;

  • org.apache.log4j.Logger,log4j里的api;

无论使用哪种api打点,最终日志都会通过log4j进行实际的日志记录。推荐用commons-logging里的api,如果直接用log4j里的api,就跟单用log4j没区别,就没有再引入commons-logging包的必要了。

既然最终是通过log4j实现日志记录,那么日志输出的level、target等也就是通过log4j的配置文件进行控制了。下面是一个log4j配置文件《log4j.properties》的简单示例:

#log4j.rootLogger = error,console
log4j.logger.com.suian.logtest = trace,console
 
#输出源console输出到控制台
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5p %c - [log4j]%m%n

既然是推荐使用commons-logging里的api打点,为了能找到log4j的日志实现,必须通过《commons-logging.properties》配置文件显式的确定关联,示例如下:

<span>org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger</span>

代码中使用JCL api进行日志打点,底层使用log4j进行日志输出。日志输出控制依托于log4j的配置文件,另外需要在commons-logging.properties配置文件中显式指定与log4j的绑定关系。

   单独使用log4j

这个是早几年最最流行的用法了,现在因为log4j本身的问题以及新的日志框架的涌现,已经逐步退出历史舞台了。具体怎么用自己去百度吧。

   SLF4J结合Logback

当下最流行的用法,SLF4J为使用场景最广泛的日志门面,加上Logback的天然实现,简单、统一、快速。

需要引入第三方依赖:

      <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
          <version>${slf4j.version}</version>
      </dependency>
      <dependency>
          <groupId>ch.qos.logback</groupId>
          <artifactId>logback-core</artifactId>
          <version>${logback.version}</version>
      </dependency>
      <dependency>
          <groupId>ch.qos.logback</groupId>
          <artifactId>logback-classic</artifactId>
          <version>${logback.version}</version>
      </dependency>

   单独使用Log4j2

Log4j2感觉就是SLF4J+Logback。log4j-api等价于SLF4J,log4j-core等价于Logback。

需要引入第三方依赖:

      <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-api</artifactId>
          <version>2.6.2</version>
      </dependency>
      <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-core</artifactId>
          <version>2.6.2</version>
      </dependency>

冲突处理

理论上各种日志输出方式是可以共存的,比如log4j和log4j2以及logback等,但是麻烦的是我们得维护多个配置文件,必须充分了解每个组件使用的是那种日志组件,然后进行对应的配置文件配置。

如何解决呢?每一个想做通用日志解决方案的,都对兼容性问题进行了特殊处理。目前只有slf4j和log4j2提供了这样的整合机制,其他的基本都很弱。

代码中可能使用的日志打点Api列举:

  • java.util.logging.Logger,jdk自带的;

  • org.apache.commons.logging.Log,commons-logging包里的api;

  • org.apache.log4j.Logger,log4j包里的api;

  • org.apache.logging.log4j.Logger,log4j2提供的api,在log4j-api包里;

  • org.slf4j.Logger,slf4j提供的api,在slf4j-api包里;

上述打点方式,在一个应用中是有可能共存的,即使自己写的代码可以确保都使用同一类api,但是引入的第三方依赖里就可能各式各样了。该怎么处理呢?

前面已经提过了,现在能够对各类冲突支持比较到位的就是slf4j和log4j2,他们都提供了很多的绑定器和桥接器。

  • 所谓的绑定器,也可以称之为适配器或者包装类,就是将特定api打点的日志绑定到具体日志实现组件来输出。比如JCL可以绑定到log4j输出,也可以绑定到JUL输出;再比如slf4j,可以通过logback输出,也可以绑定到log4j、log4j2、JUL等;

  • 所谓的桥接器就是一个假的日志实现工具,比如当你把 jcl-over-slf4j.jar 放到 CLASS_PATH 时,即使某个组件原本是通过 JCL 输出日志的,现在却会被 jcl-over-slf4j “骗到”SLF4J 里,然后 SLF4J 又会根据绑定器把日志交给具体的日志实现工具。

slf4j整合日志输出

   java.util.logging.Logger

将JUL日志整合到slf4j统一输出,需要引入slf4j提供的依赖包:

      <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>jul-to-slf4j</artifactId>
          <version>1.7.22</version>
      </dependency>

只引入依赖并不能整合JUL日志,该包里只是提供了一个JUL的handler,仍旧需要通过JUL的配置文件进行配置,slf4j绑定器(如logback)上设置的日志级别等价于JUL handler上的日志级别,因此控制JUL的日志输出,日志级别仍旧分两个地方控制:JUL配置文件《logging.properties》和slf4j绑定器的配置文件,比如《logback.xml》、《log4j2.xml》等。

  • 建立jdk14-logger的配置文件《logger.properties》,加入handler配置以及日志级别配置;

<span>handlers= org.slf4j.bridge.SLF4JBridgeHandler</span>

<span>.level= ALL</span>

  • 在启动程序或容器的时候加入JVM参数配置-Djava.util.logging.config.file = /path/logger.properties;当然也可以使用编程方式进行处理,可以在main方法或者扩展容器的listener来作为系统初始化完成;此种方式有些场景下不如配置JVM参数来的彻底,比如想代理tomcat的系统输出日志,编程方式就搞不定了。

   org.apache.commons.logging.Log

将JCL日志整合到slf4j统一输出,需要引入slf4j提供的依赖包:

      <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>jcl-over-slf4j</artifactId>
          <version>1.7.22</version>
      </dependency>

j cl-over-slf4j包里所有类的根路径为org.apache.commons.logging,也有Log和LogFactory类,相当于以重写commons-logging包的代价来实现对JCL的桥接。Log与commons-logging包里的一模一样,LogFactory的实现,代码写死使用的是org.apache.commons.logging.impl.SLF4JLogFactory。

commons-logging包里默认使用的是org.apache.commons.logging.impl.LogFactoryImpl。以这样的代价来实现桥接,可以实现无缝对接,不像JUL那样还得添加额外配置,但是有一个坏处就是需要处理类库冲突了。commons-logging包和jcl-over-slf4j包肯定是不能共存的,需要将commons-logging包在classpath里排掉。

题外话,因为JCL本身就支持通过配置文件《commons-logging.properties》绑定适配器,所以个人感觉更倾向于封装一个适配器的方式来支持,就像commons-logging包里的org.apache.commons.logging.impl.Log4JLogger,这样更符合程序员的思维,明明白白。

桥接包的命名也是很讲究的,覆写的这种,命名为xxx-over-slf4j,如本例的jcl-over-slf4j;纯桥接的,命名为xxx-to-slf4j,如文章前面提到的jul-to-slf4j。

▐  org.apache.log4j.Logger

将log4j日志整合到slf4j统一输出,需要引入slf4j提供的依赖包:

      <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>log4j-over-slf4j</artifactId>
          <version>1.7.22</version>
      </dependency>

看桥接包的名字就知道了,log4j-over-slf4j肯定是覆写了log4j:log4j包,因此使用起来只需要引入依赖即可,不需要其他额外的配置。但是仍旧是要处理冲突的,log4j包和log4j-over-slf4j是不能共存的哦。

▐   org.apache.logging.log4j.Logger

将log4j2日志整合到slf4j统一输出,slf4j没有提供桥接包,但是log4j2提供了,原理是一样的,首先引入log4j2的桥接包:

      <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-to-slf4j</artifactId>
          <version>2.6.2</version>
      </dependency>

log4j2提供的依赖包有org.apache.logging.log4j:log4j-api和org.apache.logging.log4j:log4j-core,其作用看包名就清楚了。log4j-core是log4j-api的标准实现,同样log4j-to-slf4j也是log4j-api的一个实现。

log4j-to-slf4j用于将log4j2输出的日志桥接到slf4j进行实际的输出,作用上来讲,log4j-core和log4j-to-slf4j是不能共存的,因为会存在两个log4j2的实现。

经测试,就测试结果分析,共存也是木有问题的,何解?log4j2加载provider的时候采用了优先级策略,即使找到多个也能决策出一个可用的provider来。在所有提供log4j2实现的依赖包中,都有一个META-INF/log4j-provider.properties配置文件,里面的FactoryPriority属性就是用来配置provider优先级的,幸运的是log4j-to-slf4j(15)的优先级是高于log4j-core(10)的,因此测试结果符合预期,log4j2的日志桥接到了slf4j中进行输出。

同样,为确保系统的确定性,不会因为log4j2的provider决策策略变更导致问题,建议还是要在classpath里排掉log4j-core,log4j2也是推荐这么做的。

log4j2整合日志输出

▐    java.util.logging.Logger

将JUL日志整合到log4j2统一输出,需要引入log4j2提供的依赖包:

      <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-jul</artifactId>
          <version>2.6.2</version>
      </dependency>

log4j2整合JUL日志的方式与slf4j不同,slf4j只是定义了一个handler,仍旧依赖JUL的配置文件;log4j2则直接继承重写了java.util.logging.LogManager。

使用时,只需要通过系统属性java.util.logging.manager绑定重写后的LogManager(org.apache.logging.log4j.jul.LogManager)即可,感觉比slf4j的方式要简单不少。

▐   org.apache.commons.logging.Log

将JCL日志整合到log4j2统一输出,需要引入log4j2提供的依赖包:

      <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-jcl</artifactId>
          <version>2.6.2</version>
      </dependency>

基于log4j-jcl包整合JCL比较简单,只要把log4j-jcl包扔到classpath就可以了。看起来slf4j的整合方式优雅多了,底层原理是这样的:JCL的LogFactory在初始化的时候,查找LogFactory的具体实现,是分了几种场景的,简单描述如下:

  1. 首先根据系统属性 org.apache.commons.logging.LogFactory 查找 LogFactory 实现类;

  2. 如果找不到,则以 SPI 方式查找实现类, META-INF/services/org.apache.commons.logging.LogFactory log4j-jcl 就是以这种方式支撑的;此种方式必须确保整个应用中,包括应用依赖的第三方 jar 包中, org.apache.commons.logging.LogFactory 文件只有一个,如果存在多个的话,哪个先被加载则以哪个为准。万一存在冲突的话,排查起来也挺麻烦的。

  3. 还找不到,则读取《 commons-logging.properties 》配置文件,使用 org.apache.commons.logging.LogFactory 属性指定的 LogFactory 实现类;

  4. 最后再找不到,就使用默认的实现 org.apache.commons.logging.impl.LogFactoryImpl

▐    org.apache.log4j.Logger

将log4j 1.x日志整合到log4j2统一输出,需要引入log4j2提供的依赖包:

      <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-1.2-api</artifactId>
          <version>2.6.2</version>
      </dependency>

log4j2里整合log4j 1.x日志,也是通过覆写log4j 1.x api的方式来实现的,跟slf4j的实现原理一致。因此也就存在类库冲突的问题,使用log4j-1.2-api的话,必须把classpath下所有log4j 1.x的包清理掉。

  org.slf4j.Logger

将slf4j日志整合到log4j2统一输出,需要引入log4j2提供的依赖包:

      <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-slf4j-impl</artifactId>
          <version>2.6.2</version>
      </dependency>

log4j-slf4j-impl基于log4j2实现了slf4j的接口,其就是slf4j-api和log4j2-core之间的一个桥梁。这里有个问题需要注意下,务必确保classpath下log4j-slf4j-impl和log4j-to-slf4j不要共存,否则会导致事件无止尽地在SLF4J和Log4j2之间路由。

日志打点API绑定实现

slf4j-api和log4j-api都是接口,不提供具体实现,理论上基于这两种api输出的日志可以绑定到很多的日志实现上。slf4j和log4j2也确实提供了很多的绑定器。简单列举几种可能的绑定链:

  • slf4j → logback

  • slf4j → slf4j-log4j12 → log4j

  • slf4j → log4j-slf4j-impl → log4j2

  • slf4j → slf4j-jdk14 → jul

  • slf4j → slf4j-jcl → jcl

  • jcl → jul

  • jcl → log4j

  • log4j2-api → log4j2-cor

  • log4j2-api → log4j-to-slf4j → slf4j

来个环图:

2uIRzyU.png!web

手淘行业与智能运营团队

在阿里,如果不经历电商,那么你可能就失去了一半的工作乐趣; 做电商,如果不搞商业智能,那么你可能就失去了链接人、货、场、商,给几亿用户创造更美好生活的机会! 但是现在,一个充满乐趣和机会的岗位就摆在你的面前 —— 它就是,行业与智能运营团队,电商中最智能的技术团队! 大家走过路过,不要错过! 我们要从上到下打造一支幸福感极强的团队 —— 如果你在追求幸福感,找我们,没毛病! 未来已来,淘系技术部行业与智能运营团队,这支即将成为阿里最具幸福感的技术团队,期待具有好奇心和思考力的你的加入!

就是现在, 阿里巴巴淘系技术部行业与智能运营团队,Java工程师、技术专家、架构师

面向社会+校园招聘,base杭州阿里巴巴西溪园区!

投喂简历给我们:

[email protected]

✿  拓展阅读

uM3qY3B.png!web

EFbQniY.png!web

J7fyyiq.png!web

作者| 王明凯(苍唐)

编辑| 橙子君

出品| 阿里巴巴新零售淘系技术

veE3IrM.jpg!web

uyQNNnZ.png!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK