Java日志体系(1) —— 那些年那些事,那些日志的历史
体系概述
日志接口
- JCL:Apache基金会所属的项目,是一套Java日志接口,之前叫Jakarta Commons Logging,后更更名为Commons Logging,简称JCL
- SLF4J:Simple Logging Facade for Java,缩写Slf4j,是一套简易易Java日志门面,只提供相关接口,和其他日志工具之间需要桥接
日志实现
JUL:JDK中的日志工具,也称为jdklog、jdk-logging,自Java1.4以来sun的官⽅方提供。
Log4j:隶属于Apache基金金会的一套日志框架,现已不再维护
Log4j2:Log4j的升级版本,与Log4j变化很大,不兼容
Logback:一个具体的日志实现框架,和Slf4j是同一个作者,性能很好
发展历程
上古时代
在JDK 1.3及以前,Java打日志依赖System.out.println(), System.err.println()或者
e.printStackTrace(),Debug⽇日志被写到STDOUT流,错误日志被写到STDERR流。这样打日志有一个非常大的缺陷,非常机械,无法定制,且日志粒度不不够细分。
开创先驱
于是,Ceki Gülcü于2001年发布了Log4j,并将其捐献给Apache软件基金会,称为Apache基金会的顶级项目。后来衍生出了支持C/C++,C#,Python等语言。Log4j在设计上非常优秀,它定义的Logger、Appender、Level等概念对后续的java Log框架有深远的影响,如今的很多日志框架基本沿用了这种思想。Log4j的性能是个问题,在Logback和Log4j2出来之后,2015年9月,Apache软件基金会宣布,Log4j不再维护,建议所有相关项目升级到Log4j2.
引用log4j:
pom.xml
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>apache-log4j-extras</artifactId>
<version>1.2.17</version>
</dependency>
配置:
log4j.rootLogger=debug
#console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern= log4j:[%d{yyyy-MM-dd HH:mm:ss
a}]:%p %l%m%n
#dailyfile
log4j.appender.dailyfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyfile.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.dailyfile.File=./log4j.log
log4j.appender.dailyfile.Append=true
log4j.appender.dailyfile.Threshold=INFO
log4j.appender.dailyfile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyfile.layout.ConversionPattern=log4j:[%d{yyyy-MM-dd HH:mm:ss
a}] [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
代码:
import org.apache.log4j.Logger;
Logger logger = Logger.getLogger(Demo.class);
logger.info("xxxx");
JUL的诞生
sun公司对于log4j的出现内心隐隐表示嫉妒。于是在jdk1.4版本后,开始搞事情,增加了一个包为java.util.logging,简称为JUL,用以对抗log4j ,但是却给开发造成了了麻烦。相互引用的项目之间可能使用了了不不同的⽇日志框架,经常将代码搞得一片混乱。
代码:
import java.util.logging.Logger;
Logger loggger = Logger.getLogger(Demo.class.getName());
logger.finest("xxxx");
配置的路径:
$JAVA_HOME/jre/lib/logging.properties
JUL功能远不如log4j完善,自带的Handlers有限,性能和可用性上也一般,JUL在Java1.5以后才有所提升。
JCL应运而生
从上面可以看出,JUL的api与log4j是完全不同的(参数只接受string)。由于日志系统没有互相关联,彼此没有约定,不同人的代码使用不同日志,替换和统一也就变成了一件非常棘手的事情。加入你使用了Log4j,然后团队使用了其他团队的苦,他们使用JUL,你的应用就得使用两个日志系统了,然后其他团队又使用了simplelog……这个时候如果要调整日志的输出级别,用于追踪某个信息,简直是要命。
那我们该如何解决这个问题呢?抽象,抽象出一个接口层,对每个日志实现都适配或者转接,这样这些提供给别人的库都直接使用抽象层即可,以后需要调用的时候,就调用这些接口。(面向接口开发。)
于是,JCL(Jakarta Commons Logging)应运而生,也就是commons-logging-xx.jar组件。JCL 只提供 log 接口,具体的实现则在运行时动态寻找。这样一来组件开发者只需要针对 JCL 接口开发,而调用组件的应用程序则可以在运行时搭配自己喜好的日志实践工具。
那接口下真实的日志是谁呢?参考下图:
JCL会在ClassLoader中进行查找,如果能找到Log4j 则默认使用log4j 实现,如果没有则使用JUL(jdk自带的) 实现,再没有则使⽤用JCL内部提供的SimpleLog 实现。(代码验证)
pom:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
代码:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
Log log =LogFactory.getLog(Demo.class);
log.info('xxx');
JCL缺点也很明显,一是效率较低,二是容易易引发混乱,三是JCL的机制有很⼤大的可能会引发内存泄
露。
同时,JCL的书写存在一个不太优雅的地方,典型的场景如下:
假如要输出一条debug日志,而一般情况下,生产环境 log 级别都会设到 info 或者以上,那这条log 是不会被输出的。于是,在代码里里就出现了
logger.debug("this is a debug info , message :" + msg);
这个有什么问题呢?虽然生产不会打出日志,但是这其中都会做一个字符串串连接操作,然后生成一个新的字符串串。如果这条语句句在循环或者被调用很多次的函数中,就会多做很多无用的字符串连接,影响性能。
所以,JCL推荐的写法如下:
if (logger.isDebugEnabled()) {
logger.debug("this is a debug info , message :" + msg);
}
再起波澜
于是,针对以上情况,log4j的作者再次出手,他觉得JCL不好用,自己又写了一个新的接口api,就是slf4j,并且为了了追求更更极致的性能,新增了了一套日志的实现,就是logback.
pom:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
配置:
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<property name="logPattern" value="logback:[ %-5level] [%date{yyyy-MM-dd
HH:mm:ss.SSS}] %logger{96} [%line] [%thread]- %msg%n"></property>
<!-- 控制台的标准输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<charset>UTF-8</charset>
<pattern>${logPattern}</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
logback-core 提供基础抽象,logback-classic 提供日志实现,并且直接就是基于Slf4j API。所以slf4j配合logback来完成日志时,不需要像其他的日志框架一样提供适配器。
slf4j本身并没有实际的日志输出能力力,它底层还是需要去调用具体的日志框架API,也就是它需要跟具体的日志框架结合使用。由于具体日志框架比较多,而且互相也大都不不兼容,日志门面接口要想实现与任意日志框架结合就需要额外对应的桥接器器。
有了了新的slf4j后,上面的字符串串拼接问题,被以下代码所取代,而logback也提供了了更高级的特性,如异步 logger,Filter等。
logger.debug("this is a debug info , message : {}", msg);
再度青春
前面提到,log4j由apache宣布,2015年年后,不再维护。推荐大家升级到log4j2,虽然log4j2沿袭了log4j的思想,然而log4j2和log4j完全是两码事,并不不兼容。
log4j2以性能著称,它⽐比其前身Log4j 1.x提供了了重大改进,同时类比logback,它提供了Logback中可用的许多改进,同时修复了了Logback架构中的一些固有问题。功能上,它有着和Logback相同的基本操作,同时又有自己独特的部分,比如:插件式结构、配置文件优化、异步日志等。
pom:
<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>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.1</version>
</dependency>
代码:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Logger logger = LogManager.getLogger(Demo.class);
logger.debug("debug Msg");
配置:
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="info" monitorInterval="30">
<Properties>
<Property name="pattern">log4j2:[%-5p]:%d{YYYY-MM-dd HH:mm:ss} [%t]
%c{1}:%L - %msg%n</Property>
</Properties>
<appenders>
<!--console :控制台输出的配置-->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${pattern}"/>
</Console>
</appenders>
<loggers>
<logger name="org.springframework" level="INFO"></logger>
<root level="info">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
到log4j2,轰轰烈烈烈烈的java log战役基本就结束了。