日志是我们工作中经常提及的内容,但是我们很少关心他的实现原理,基本的都是直接使用别人配置好的东西,那么这么多的日志框架,他是如何做到日志的统一打印呢,spring是如何实现的,springboot是如何实现,又有哪些日志框架呢,具体是如何实现以及选择的呢
日志框架
- Jul (Java Util Logging):JDK中的日志记录工具,也常称为JDKLog、jdk-logging,自Java1.4以来的官方日志实现。
- Log4j:Apache Log4j是一个基于Java的日志记录工具。它是由Ceki Gülcü首创的,现在则是Apache软件基金会的一个项目。Log4j是几种Java日志框架之一。
- Log4j2:一个具体的日志实现框架,是Log4j 1的下一个版本,与Log4j 1发生了很大的变化,Log4j 2不兼容Log4j 1。
- Logback:一个具体的日志实现框架,和Slf4j是同一个作者,但其性能更好(推荐使用)
门面型日志框架
- JCL:Apache基金会所属的项目,是一套Java日志接口,之前叫Jakarta Commons Logging,后更名为Commons Logging
- SLF4J:是一套简易Java日志门面,本身并无日志的实现。(Simple Logging Facade for Java,缩写Slf4j)
JCL原理
JCL依赖包
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
JCL开发代码
public void jcl(){
Log logger = LogFactory.getLog("a");
logger.info("jcl");
}
JCL核心代码
//这是JCL的最后发布版本中内置的四种日志技术
private static final String[] classesToDiscover = {
LOGGING_IMPL_LOG4J_LOGGER,
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"
};
Class c;
try {
c = Class.forName(logAdapterClassName, true, currentCL);
} catch (ClassNotFoundException originalClassNotFoundException) {
..............
}
//通过遍历那个字符串数组(classesToDiscover)调用 Class.forName //判断用户是否添加了该日志技术的核心依赖constructor = c.getConstructor(logConstructorSignature);
Object o = constructor.newInstance(params);
//然后通过反射实例化对象,那么返回给用户的就是那个数组//当中的一个日志框架的logger对象
<!-- log4j1 核心-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
可以简单的理解成,jcl模式是判断是否有log4j的依赖包,如果有就使用log4j实现,如果没有则使用jul(jdk自带日志框架),如果连jdk都没有,则使用自身实现的SimpleLog日志框架
SLF4J门面型框架
slf4j依赖包
<!--只有slf4j-api依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
slf4j开发代码
public void slf4j(){
Logger logger = LoggerFactory.getLogger("a");
logger.error("slf4j");
}
slf4j基本原理
如果我们的项目使用的是slf4j日志门面框架,那就要看我们具体的实现日志组件是什么,就使用哪个日志组件,但是有多个日志实现组件同时存在,例如同时存在Logback,slf4j-log4j12,slf4j-jdk14,slf4j-jcl四种实现,则在项目实际运行中,Slf4j的绑定选择绑定方式将有Jvm确定,并且是随机的(往往谁写到前面就使用谁),这样会和预期不符,实际使用过程中需要避免这种情况
可以简单的理解成,slf4j使用哪种日志打印,和我们加入依赖是有关系,我们把这个依赖,可以理解成一种绑定器,比如下面logback依赖,这个依赖就是把slf4j门面日志框架和logback日志框架自动的进行绑定,从而实现使用logback日志框架,其中绑定器依赖包包含了logback核心包和slf4j包以及logback的绑定器包三者
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
其他常见日志绑定器
<!-- log4j1 绑定器-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>
<!--绑定log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.13.3</version>
</dependency>
<!-- logback 绑定器-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- jul 绑定器-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.30</version>
</dependency>
<!-- jcl 绑定器-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jcl</artifactId>
<version>1.7.30</version>
</dependency>
使用Slf4j时如何桥接遗留的api
在实际环境中我们经常会遇到不同的组件使用的日志框架不同的情况,例如Spring Framework(spring4)使用的是日志组件是Commons Logging,假设其他组件依赖的是Java Util Logging.当我们在同一项目中使用不同的组件时应该如果解决不同组件依赖的日志组件不一致的情况呢?现在我们需要统一日志方案,统一使用Slf4j,把他们的日志输出重定向到Slf4j,然后Slf4j又会根据绑定器把日志交给具体的日志实现工具.Slf4j带有几个桥接模块,可以重定向Log4j,Log4j2,JCL和JUL中的Api到Slf4j。
<!--log4j2桥接器-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.9.1</version>
</dependency>
<!--log4j-over-slf4j log4j1的桥接器-->
<!--不能和log4j1的核心共存会有jar冲突问题 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.32</version>
</dependency>
<!--jul桥接器-->
<dependency>
<groupId>org.slf4j</groupId >
<artifactId>jul-to-slf4j</artifactId >
<version>1.7.6</version>
</dependency>
<!--jcl桥接器-->
<dependency>
<groupId> org.slf4j</groupId>
<artifactId> jcl-over-slf4j</artifactId>
<version> 1.6.2</version>
</dependency>
slf4j死循环问题
多个日志形成死循环 | 原因 |
log4j-over-slf4j.jar和slf4j-log4j12.jar同时存在 | 由于slf4j-log4j12.jar的存在会将所有日志调用委托给log4j.但由于同时由于log4j-over-slf4j.jar的存在,会将所有对log4j api的调用委托给相应等值的slf4j,所以log4j-over-slf4j.jar和slf4j-log4j12.jar同时存在会形成死循环 |
jul-to-slf4j.jar和slf4j-jdk14.jar同时存在 | 由于slf4j-jdk14.jar的存在会将所有日志调用委托给jdk的log.但由于同时jul-to-slf4j.jar的存在,会将所有对jul api的调用委托给相应等值的slf4j,所以jul-to-slf4j.jar和slf4j-jdk14.jar同时存在会形成死循环 |
案例说明
比如我们有一个项目,有两个组件,一个组件使用的是log4j1,另外一个使用的slf4j,具体实现的日志框架是log4j2,我们如何统一使用log4j2呢
spring日志
spring4和spring5的日志本质上是一样的,spring4日志是依赖我们的原生JCL依赖包实现日志打印,而我们的spring5专门引入了spring jcl模块实现日志打印
spring4日志依赖包,common-logging 这就是JCL使用到的包,可以看出,Spring4使用的是原生的JCL,所以在有log4j的时候使用log4j打印日志,没有的时候使用JUL打印日志
spring5日志体系,大体结构没变,只是原来common-logging,换成了spring-jcl,看名字就知道是spring自造的包,jcl,更是标注了,它使用的是JCL日志体系,如下图
springBoot日志
springboot底层使用的是slf4j+logback来进行日志记录,把其他common-logging、log4j、java.util.logging转换为slf4j
因此SpringBoot项目中,我们一般引入的其他组件,不需要特殊处理,都会按照logback日志打印,基本上常用的日志框架都会被桥接到slf4j,然后使用logback-classic把slf4j和logback进行绑定。