Java存在好几种日志框架,刚学Java以及初入职场时,可能会一头雾水,只知道调用API,而对背后的日志体系与规范完全不了解。Reference中引用的文章就对Java日志体系做了很好的总结,本文结合自身工作经历对其中的知识点做一些简要记录。
Log Facade与Log Implementation
首先明确两个概念:Log Facade与Log Implementation,它们是日志体系的两大组成部分。
Log Facade:日志的抽象层,它是一套规范、标准,与JDBC驱动类似,它只提供一系列对外的统一接口,不提供日志功能的具体实现。主要有
common-logging
、slf4j
。Log Implementation:实现了Log Facade接口的具有真正日志功能的框架,主要有
java.util.logging
、log4j
、Logback
、Log4j2
。
日志选型
一般而言,我们会选择slf4j
+ Logback
的组合,实际上logback-classic
已经包含了logback-core
与slf4j-api
,所以只引入logback-classic
即可。如果对logback-core
与slf4j-api
有特定版本需求,可以参考下面的写法。
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.11</version>
</dependency>
但实际开发中可能会遇到一些不理想的情况:
某些依赖有自己的日志选型,比如
spring
使用的组合是common-logging
+log4j
有些老代码直接调用某个具体的Log Implementation,改造代码很麻烦
针对以上两种情况,我们需要使用桥接器,桥接器的作用是将一个Log Implementation桥接到另一个Log Implementation,如果代码中直接调用了某个Log Implementation的代码,那么桥接器会把它桥接到目标Log Implementation。比如对于spring
,我们可以使用logback-ext-spring
:
<dependency>
<groupId>org.logback-extensions</groupId>
<artifactId>logback-ext-spring</artifactId>
<version>0.1.5</version>
<exclusions>
<exclusion>
<artifactId>logback-classic</artifactId>
<groupId>ch.qos.logback</groupId>
</exclusion>
</exclusions>
</dependency>
我们引入spring-core的时候,就可以排除掉它自带的commons-logging
了
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.20</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
而针对第二种情况,假设老代码直接调用了Log4j
中的类,那么可以使用log4j-over-slf4j
桥接到slf4j
:
<!-- 代码直接调用log4j会被桥接到slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.36</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
实践中的原则
实际开发中,日志选型与依赖管理或许并不需要我们来做,但我们还是有必要知道这些原则。
禁止在代码中直接使用Log Implementation中的类,而应该总是调用Log Facade的API,这样才符合面向接口编程的规范,而且一旦需要更换Log Implementation,只需替换依赖即可。
Log Implementation不要混用,一个项目应该只用一个Log Implementation。如果引入了使用其他Log Implementation的依赖或者代码,应该使用桥接器,而不是直接引入其他Log Implementation。
Log Implementation的依赖应该设置为
<scope>runtime</scope>
、<optional>true</optional>
。scope
的作用是防止开发人员在项目中直接使用Log Implementation中的类;optional
的作用是如果别的项目B依赖本项目A,那么A的Log Implementation依赖不会被传递到B。尽量排除第三方依赖中的Log Implementation。上文在讲日志选型时,代码中频繁出现了
exclusions
标签,就是意在尽可能排除掉第三方依赖中的Log Implementation,因为我们在一开始就确定了使用slf4j
+Logback
,如果意外引入了其他Log Implementation,那就违背了第二条原则。尽量使用
logger.debug("start process request, url:{}", url);
的方式,不要在debug()
中进行运算,例如:logger.debug("start process request, url: " + url);
,因为如果日志级别高于DEBUG,那么本条日志根本不会打印,却还是执行了不必要的字符串拼接。