查看原文
其他

从原理到实践彻底搞懂 Java 日志系统,再也不迷茫了!

刘赫 Java精选 2022-08-09
>>号外:关注“Java精选”公众号,回复“2021面试题”,领取免费资料!Java精选面试题”小程序,3000+ 道面试题在线刷,最新、最全 Java 面试题!
引言
你是否还在用system.out.print(“”)来追踪程序的重要运行信息?
你是否因无法区分commons-logging.jar、log4j.jar、slf4j-api.jar等日志框架而烦恼?
你是否因为日志框架不统一而纠结是否改代码而惆怅?
没关系,本文带你走进Java日志体系,从原理到实践解决你的困惑。
一、日志框架分类
1、门面型日志框架:不实现日志功能,仅整合日志

1)JCL:一套Apache基金所述的java日志接口,由Jakarta Commons Logging,更名为Commons Logging;
2)SIF4J:一套简易的Java日志门面,全称为Simple Logging Facade for Java。

2、记录性日志框架:实现日志的功能

1)JUL:JDK中的日志记录工具,自Java1.4来由官方日志实现;
2)Log4j:具体的日志实现框架;
3)Log4j2:具体日志实现框架;
4)Logback:一个具体的日志实现框架。

二、日志框架的发展演变

1、Log4j
在JDK1.3版本及以前,Java日志的实现依赖于System.out.print()、System.err.println()或者e.printStackTrace()Debug日志被写到STDOUT流,错误日志被写到STDERR流。这样的日志系统无法定制且粒度太粗,无法精确定位错误。
Gülcü于2001年发布了Log4j框架,也就是后来Apache基金会的顶级项目。Log4j定义的Logger、Appender、Level等概念如今已经被广泛使用。Log4j 的短板在于性能,在Logback和 Log4j2出来之后Log4j的使用也减少了,目前已停止更新。
2、JUL
受Logj启发,Sun在Java1.4版本中引入了java.util.logging,但是jull功能远不如log4j完善,开发者需要自己编写Appenders(Sun称之为Handlers),且只有两个Handlers可用(Console和File),jul在Java1.5以后性能和可用性才有所提升。
3、JCL
JCL(commons-logging)是一个门面框架,它由于项目的日志打印必然选择两个框架中至少一个,利用JCL只提供 Log API,不提供实现,实现采用Log4j或者 JUL 。
4、SLF4j
SLF4J(Simple Logging Facade for Java)和 Logback 也是Gülcü创立的项目,目的是为了提供更高性能的实现。
从设计模式的角度说,SLF4J是用来在log和代码层之间起到门面作用,类似于 JCL的Log Facade。对于用户来说只要使用SLF4J提供的接口,即可隐藏日志的具体实现,SLF4J提供的核心API是一些接口和一个LoggerFactory的工厂类,用户只需按照它提供的统一纪录日志接口,最终日志的格式、纪录级别、输出方式等可通过具体日志系统的配置来实现,因此可以灵活的切换日志系统。
三、日志实现框架实践
1、采用Log4j日志框架实现
1)Log4j为开源组件,使用前需要添加依赖:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
2)同时需要添加log4j.properties配置文件;
配置文件内容:
log4j.rootLogger=trace, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
3)测试代码:
import org.apache.log4j.Logger;
public class Log4jTest {
public static void main(String[] args) {
Logger logger = Logger.getLogger(Log4jTest.class);
logger.info(logger.getClass().getName());
logger.info("apache日志框架log4j");
}
}
4)运行结果:
2、使用JUL日志框架实现
1)JUL日志系统在JDK中已引入,故无需导包,代码如下:
import java.util.logging.Logger;
public class JulTest {
public static void main(String[] args) {
Logger logger = Logger.getLogger(JulTest.class.getName());
//判断日志使用类型
logger.info(logger.getClass().getName());
logger.info("官方JDK日志框架Jul");
}
}
2)运行结果:

四、日志门面框架整合日志实现框架
使用日志门面框架缘由:在阿里开发手册上有关于日志门面使用系统的强制规约:
应用中不可直接使用日志系统(log4j、logback)中的 API ,而应依赖使用日志框架中的 API 。使用门面模式的日志框架,有利于维护和各个类的日志处理方式的统一。 
1、Sif4j门面框架+Log4j实现
1)添加slf4j的核心依赖:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
2)Sif4j门面框架+Log4j实现使用的桥接器:
桥接器:日志门面接口本身通常并没有实际的日志输出能力,它底层还是需要去调用具体的日志框架API的,也就是实际上它需要跟具体的日志框架结合使用。
由于具体日志框架比较多,而且互相也大都不兼容,日志门面接口要想实现与任意日志框架结合可能需要对应的桥接器,说白了,所谓“桥接器”,不过就是对某套API的伪实现。
这种实现并不是直接去完成API所声明的功能,而是去调用有类似功能的别的API。这样就完成了从“某套API”到“别的API”的转调。
添加桥接依赖:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
3)测试代码:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Log4jSif4jTest {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(Log4jSif4jTest.class);
logger.info(logger.getClass().getName());
logger.info("门面框架Sif4j整合Log4j输出");
}
}
4)结果输出:

2、JCL门面框架+JUL实现
1)引入桥接依赖:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</dependency>
2)测试代码:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class JCLJULTest {
public static void main(String[] args) {
Log log = LogFactory.getLog(JCLJULTest.class.getName());
log.info(log.getClass());
log.info("门面框架JCL整合JUL输出");
}
}
3)输出结果:
此时,不是红色字体的JCL依赖。
原因是:JCL动态查找机制进行日志实例化,执行顺序为

commons-logging.properties>系统环境变量>log4j>jul>simplelog>nooplog

故添加commons-logging.properties。
org.apache.commons.logging.Log = org.apache.commons.logging.impl.Jdk14Logger
4)重新运行,输出结果:

五、合整合日志门面为slf4j
1、需求场景:
若项目在开发过程中,不同开发小组使用了不同的日志门面和日志实现系统,如第四节中JCL+JUL和Slf4j+Log4j的实现模式,先在为了统一用Slf4j这个门面系统,又不想对原有JCL+JUL模式的代码进行修改,该如何操作?
2、采用适配器
官方流图如下:
对于JCL门面来说,要想转换成Slf4j,只需要引入JCL的适配器,引入jcl-over-slf4j 的jar包:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
3、依旧使用第四章节的代码
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class JCLJULTest {
public static void main(String[] args) {
Log log = LogFactory.getLog(JCLJULTest.class.getName());
log.info(log.getClass());
log.info("门面框架JCL整合JUL输出");
}
}
4、结果输出
此时,可见日志类已经变成了Slf4j。
六、总结
1、对于spring框架,默认使用JCL门面以JUL作为日志框架输出,演示代码见第四章第二小节,结构图如下:
2、若项目采用Slf4j门面以Log4j作为日志框架输出,演示代码见第四章第一小节,结构图如下:

3、为了整合,使spring以log4j2的日志框架进行输出,需要使用jcl-over-slf4j适配器,演示代码见第五章,结构图如下:
往期精选  点击标题可跳转

【源码解读】JDK1.8 中 ConcurrentHashMap 不支持空键值对源码剖析

为什么要代码重构?如何重构?常见重构技巧,值得收藏!

面试官问:为什么 Java 线程没有 Running 状态?一下被问懵!

SpringBoot + Mybatis + Druid + PageHelper 实现多数据源并分页(附源码)

Intellij IDEA 中的各种调试代码技巧,轻松定位 Bug 问题(涵盖超全面)

MyBatis 真坑!Integer 类型赋值 0 ,当 != '' 时无法通过判断执行 SQL 语句

面试官问:Spring Boot 中实现通用 Auth 认证,有哪几种方式?

Spring 中 IService 有多个实现类,它是如何知道该注入哪个 ServiceImpl 类?

突然慌了!面试官问:线程池中多余的线程是如何回收的?

MySQL 数据库中百万级数据量,大神是如何分页查询?

数据库中 SQL 语句使用索引,还是很慢?可能是这几点原因

点个赞,就知道你“在看”!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存