日志在排查线上问题、跟踪线上系统运行情况中发挥着重要作用。在Java应用的开发中,常见的日志框架有 JCL (commons-logging), slf4j , JUL (java.util.logging), log4j , log4j2 , logback 等。这些日志框架大致可以分为两类,一类是日志门面(JCL、slf4j),定义日志的抽象接口;另一类是日志实现(JUL,log4j,log4j2,logback),负责真正地处理日志。为什么会有这么多的日志框架呢?从Java日志框架的发展史里大概可以一探究竟。
Java日志框架的发展历史
那么面对这些日志框架,该如何选择呢?如果你是在开发一个新的项目(类库)而不是维护一个上古的遗留代码,那么在打印日志时推荐使用日志门面,秉承面向接口编程的思想,与具体的日志实现框架解耦,这样日后可以很容易地切换到其他的日志实现框架。
特别是当你的代码以SDK的方式提供给别人使用时,使用日志门面能避免使用方可能出现的日志框架冲突问题。如果你的SDK里使用了log4j,而使用方的应用里使用的logback,这时使用方就不得不分别针对log4j和logback维护两套日志配置文件,来确保所有日志正常的输出(slf4j提供了冲突解决方案,稍后在下文介绍)。
在目前已有的两个日志门面框架中,slf4j规避了JCL在部分场景下因为ClassLoader导致绑定日志实现框架失败的问题;能支持以上提到的所有日志实现框架;且slf4j支持占位符功能,在需要拼接日志的情况在接口层面就比JCL有更好的性能,所以推荐使用slf4j,下面简单多介绍下slf4j。
// slf4j的占位符功能 LOGGER.info("hello {}", name);
logback因为本身就实现了slf4j-api,所以天然就能很好地支持slf4j,但是log4j和JCL不同,早在slf4j之前就已经存在,他们可不是为了实现slf4j而设计的,那么如何实现slf4j和他们的绑定呢?答案就是适配器模式。如下图,slf4j分别为log4j和JCL实现了适配层 slf4-log4j12.jar
和 slf4j-jdk14.jar
,通过适配层把日志的处理转发给底层日志实现框架。
下面是使用log4j 1.x做为日志实现框架的maven依赖配置:
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.26</version> </dependency> <!-- slf4j-log4j12 依赖了log4j,不需要再显示地依赖log4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.26</version> </dependency>
由于历史原因,总会遇到依赖的多个类库使用不同日志实现框架的情况,之前也提到了,为了确保日志正常输出,需要针对多个的日志实现框架维护多个配置文件。为了解决这个问题,slf4j再次基于适配器模式提供了解决方案,针对不同的日志实现框架实现了xxx-over-slf4j适配层,把对日志实现框架的调用转发到slf4j-api,再由slf4j把日志处理转发给日志实现框架。
上图分别展示了把log4j,logback,JUL,JCL的调用分别转换成其中一种日志实现框架的示意图。假设项目依赖的SDK分别使用了log4j、JUL和JCL,打算把日志实现框架统一成log4j,maven依赖配置如下:
<dependency> <groupId>me.tunzao</groupId> <artifactId>classloader-common</artifactId> <version>1.0-SNAPSHOT</version> <exclusions> <!-- 排除对jcl的依赖 --> <exclusion> <artifactId>commons-logging</artifactId> <groupId>commons-logging</groupId> </exclusion> </exclusions> </dependency> <!-- 把对jcl的请求转发给slf4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.26</version> </dependency> <!-- 把对jul的请求转发给slf4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jul-to-slf4j</artifactId> <version>1.7.26</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.26</version> </dependency> <!-- slf4j-log4j12 依赖了log4j,不需要再显示地依赖log4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.26</version> </dependency>
需要注意的是log4j-over-slf4j.jar 和 slf4j-log4j12.jar 不能同时出现在classpath下,否则就会因为循环调用而堆栈溢出,同理jul-to-slf4j.jar和slf4j-jdk14.jar、jcl-over-slf4j.jar和slf4j-jcl.jar亦不能同时出现。
原文