Java日志体系
日志在系统中举足轻重,特别是对于已经上线的系统,是定位问题的关键。日常开发中可以使用System.out打印日志快速查看运行时信息,可以对于线上系统来说,需要更多的信息如:时间、打印日志所在的类名和方法名和统一控制日志打印开关,System.out显然无法满足要求。于是发展出了以下的日志框架,为了方便日志体系的迁移,Java的日志框架模仿JDBC提出了Java facade,但是并没有达成统一,每个框架都有自己的facade,一般日志框架都是由API+Imp两个部分组成。
日志发展
1. Apache Log4j
Apache Log4j 2 is an upgrade to Log4j that provides significant improvements over its predecessor, Log4j 1.x, and provides many of the improvements available in Logback while fixing some inherent problems in Logback’s architecture.
Log4j是Apache公司产品,主流版本是 Log4j 2,有自己的logging facade:log4j-api,因此使用需要如下依赖
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.13.0</version>
</dependency>
</dependencies>
详细配置可从官网查找,使用如下:
import org.apache.log4j.Logger;
...
Logger logger = Logger.getLogger(Test.class);
logger.trace("trace");
2. Commons-Logging
Sun为了与Apache竞争,也开发了日志工具,内置于JDK中的java.util.logging包下,一般简称为JCL。这样市面上就出现了不同的日志框架,为了统一,Jakarta 向上抽取了一个统一的接口java commons logging。日志框架开发者面向此Facade编程实现,日志框架使用者代码中调用此Facade(类似JDBC),在Runtime时,根据配置的不同执行不同日志实现。
3. Logback+slf4j
逐渐地,Log4j满足不了需求,原作者又开发了新的日志实现LogBack和Log Facade slf4j。
1.依赖
<!-- LogBack -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
2.配置logback.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
</layout>
</appender>
<logger name="com.chanshuyi" level="TRACE"/>
<root level="warn">
<appender-ref ref="STDOUT" />
</root>
</configuration>
3.调用
import org.slf4j.Logger; // 注意导入的包是slf4j
import org.slf4j.LoggerFactory;
...
Logger logger = LoggerFactory.getLogger(Slf4jJDKLog.class);
logger.trace("Trace Level.");
logger.info("Info Level.");
logger.warn("Warn Level.");
logger.error("Error Level.");
至此市面上主流的Java日志框架是Log4j+Log4j-api和logback+slf4j
最佳实践
对于新项目来说,一般会使用logback+slf4j日志框架。那对于依赖的第三方使用了log4j或者jul的怎么办,如何统一输出?答案是桥接器。
-
本项目使用的日志框架依赖
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.7</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.1.7</version> </dependency>
-
桥接器依赖,将jul和log4j重定向到slf4j,这样真实使用的日志就是本项目中的slf4j+logback日志框架
<!-- https://mvnrepository.com/artifact/org.slf4j/jcl-over-slf4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.25</version> </dependency> <!-- https://mvnrepository.com/artifact/org.slf4j/log4j-over-slf4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> <version>1.7.25</version> </dependency>
以上是项目技术选型是logback+slf4j,如果是其他呢?
只需要
- 确定日志Facade,一般就是slf4j
- 确定日志实现框架,不同的日志框架针对slf4j都提供绑定器,也就是说slf4j可以配合大部分日志实现使用
- slf4j-jdk14:slf4j到jdk-logging的桥梁
- slf4j-log4j12:slf4j到log4j1的桥梁
- log4j-slf4j-impl:slf4j到log4j2的桥梁这是apache实现,依赖的log4j版本较新。slf4j-log4j12这是slf4j中实现,依赖的log4j版本较低。实验可以互相替代,但是注意配置文件的不同。
- logback-classic:slf4j到logback的桥梁
- slf4j-jcl:slf4j到commons-logging的桥梁
- 管理依赖项目中的日志框架,拦截且重定向到slf4j中
- jul-to-slf4j:jdk-logging到slf4j的桥梁
- log4j-over-slf4j:log4j1到slf4j的桥梁
- jcl-over-slf4j:commons-logging到slf4j的桥梁
案例二:如何让Spring以log4j2形式输出
spring默认使用的是jcl输出日志
而你的应用中,采用了slf4j+log4j-core,即log4j2进行日志记录
方案一:jcl-over-slf4j适配器
方案二:JUL to SLF4J bridge
但是注意需要在调用日志之前执行以下代码,才能正常工作。
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
循环引用问题
想象这样一种情况,通过桥接器将log4j的日志全部重定向到slf4j,然后slf4j又再次绑定到了log4j实现。死循环了。
参考:
1 Java日志框架那些事儿
2 带你弄清混乱的JAVA日志体系
3 apache log4j官网
4 slf4j