Java日志体系

🤖🤖-摘要:
本文介绍了Java的日志体系,包括日志的含义、用途和重要性。涵盖了几种常见的日志框架如Log4j、JUL、JCL、SLF4J、Logback和Log4j2的详细介绍,包括它们的优势、劣势和工作机制。推荐使用SLF4J做日志门面,配合Logback或Log4j2做日志实现。
日志记录了系统行为的时间, 地点, 状态等相关信息, 能够帮助我们了解并监控系统状态,
在发生错误或者接近某种危险状态时能及时提醒我们处理, 同时在系统产生问题, 能够帮助我们快速定位, 诊断问题.
在生产环境中日志可能是我们了解系统运行状况的唯一方式, 其重要性无需赘述
总结如下优点:
- 记录系统行为
- 监控系统状态
- 提示系统风险
- 定位系统问题
System.out
同样可以打印信息, 为什么不用System.out.print("")?
- 难定位
System.out.print输出的日志只打印在控制台, 没有存储到一个日志文件中, 且格式不统一, 在生产环境这样打印出来很难定位信息, 意义不大. - 性能弱
public void print(String s) {
write(String.valueOf(s));
}由上面的源码可以看出private void write(String s) {
try {
synchronized (this) {
ensureOpen();
textOut.write(s);
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush && (s.indexOf('\n') >= 0))
out.flush();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}print的方法实现是由synchronized包裹的同步代码块
不能异步打印日志, 在高并发的情况下, 会严重影响性能
阿里巴巴Java开发手册:
【强制】 应用中不可直接使用日志系统( Log4j、 Logback) 中的 API,而应依赖使用日志框架( SLF4J、 JCL–Jakarta Commons Logging) 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一
【强制】 生产环境禁止直接使用 System.out 或 System.err 输出日志或使用e.printStackTrace()打印异常堆栈
【强制】 在日志输出时,字符串变量之间的拼接使用占位符的方式
【强制】 对于 trace/debug/info 级别的日志输出,必须进行日志级别的开关判断
Log4j
Log for Java
Ceki Gülcü于2001年发布了Log4j, 并将其捐献给Apache软件基金会, 成为Apache基金会的顶级项目.
Apache基金会最早实现的一套日志框架, 通过使用Log4j
- 可以控制日志信息输送的目的地是控制台, 文件等
- 可以控制每一条日志的输出格式
- 通过定义每一条日志信息的级别, 我们能够更加细致地控制日志的生成过程
- 可以通过一个配置文件来灵活地进行配置, 而不需要修改应用的代码 ☆☆
Log4j主要由Loggers(日志记录器), Appenders(输出端), Layout(格式化).
Loggers控制日志输出级别与日志是否输出Appenders指定日志的输出方式(输出到控制台或文件等)Layout控制日志的输出格式
2015年9月, Apache软件基金业宣布, Log4j不再维护, 建议所有相关项目升级到Log4j2.
JUL
Java Util Logging
SUN公司在JDK1.4发布的Java原生的日志框架, 使用时不需要另外引用第三方的类库, 相对其他的框架使用方便
- 初始化
LogManagerLogManager加载logging.properties配置- 添加
Logger到LogManager
- 从
LogManager中获取Logger - 设置级别
Level, 并指定日志记录LogRecord Filter提供日志级别之外更细粒度的控制Handler是用来处理日志输出位置Formatter用来格式化LogRecord

JCL
Jakarta Commons Logging
JUL的api与log4j是完全不同的(参数只接受string).由于日志系统没有互相关联,彼此没有约定,不同人的代码使用不同日志,替换和统一也就变成了一件非常棘手的事情.
那我们该如何解决这个问题呢?
抽象出一个接口层,对每个日志实现都适配或者转接,这样这些提供给别人的库都直接使用抽象层即可,以后需要调用的时候,就调用这些接口.
Apache基金下Jakarta小组开发的通用日志API
主要功能是给所有的日志实现提供一个统一接口, 本身也提供日志实现(SimpleLog),但是很弱一般不用.
常见的日志实现如:最早的Log4j以及JDK自带的JUL等

日志门面的好处:
- 面向接口编程 解耦合
- 可灵活切换日志框架
- 统一日志API方便使用
- 统一日志配置和管理
由于设计缺陷,只支持当时主流的几个日志实现,不利于其他日志的使用(需修改源码),现已淘汰!!!
SLF4J
Log4j的作者觉得JCL不好用,自己又写了一个新的接口api,就是SLF4J,
并且为了追求更极致的性能,新增了一套日志的实现,就是logback
Simple Logging Facade For Java
SLF4J主要给Java日志访问提供一套标准,规范的API框架.
目前市面最流行的日志门面,主要功能:
- 日志框架的绑定
- 日志框架的桥接
SLF4J的各种组合
需对应的适配器SLF4J unbound:没有绑定实现,则只有接口,没有日志功能
SLF4J bound to logback:logback是后出现的日志实现,实现了SLF4J接口,可直接搭配使用
SLF4J + logback 组合结果(黑色字体):
20:24:32.167 [main] ERROR com.fredo.slf4j.SLF4JTest -- error
20:24:32.172 [main] WARN com.fredo.slf4j.SLF4JTest -- warn
20:24:32.172 [main] INFO com.fredo.slf4j.SLF4JTest -- info
20:24:32.172 [main] ERROR com.fredo.slf4j.SLF4JTest -- 出现异常:java.lang.ArithmeticException: / by zero
注意:注释slf4j-simple, 并且不要排除SpringBoot内置的日志实现:logbackSLF4J bound to reload4j:reload4j是先出现的日志实现,没有实现了SLF4J接口,需配合适配器使用
SLF4J + log4j 组合结果:
2023-06-11 20:38:19.738 error
2023-06-11 20:38:19.739 warn
2023-06-11 20:38:19.739 info
2023-06-11 20:38:19.739 debug
2023-06-11 20:38:19.740 trace
2023-06-11 20:38:19.741 出现异常:java.lang.ArithmeticException: / by zero
注意:需要适配器:slf4j-log4j12以及log4j对应的配置文件SLF4J bound to JUL:JUL是先出现的日志实现,没有实现了SLF4J接口,需适配器使用
SLF4J + JUL 组合结果(红色字体):
6月 11, 2023 20:42:46 上午 com.fredo.slf4j.SLF4JTest test01
SEVERE: error
6月 11, 2023 20:42:46 上午 com.fredo.slf4j.SLF4JTest test01
WARNING: warn
6月 11, 2023 20:42:46 上午 com.fredo.slf4j.SLF4JTest test01
INFO: info
6月 11, 2023 20:42:46 上午 com.fredo.slf4j.SLF4JTest test01
SEVERE: 出现异常:java.lang.ArithmeticException: / by zero
注意:JDK默认自带无需依赖,但需要对应的适配器:slf4j-jdk14SLF4J bound to simple:simple是后出现的日志实现,实现了SLF4J接口,可直接搭配使用
SLF4J + SLF4J-Simple 组合结果(红色字体):
[main] ERROR com.fredo.slf4j.SLF4JTest - error
[main] WARN com.fredo.slf4j.SLF4JTest - warn
[main] INFO com.fredo.slf4j.SLF4JTest - info
[main] ERROR com.fredo.slf4j.SLF4JTest - 出现异常:java.lang.ArithmeticException: / by zero
注意:需要提前排除:logback.SpringBoot默认的日志实现
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
</dependency>SLF4J bound to nop:nop是日志开关,关闭日志实现,不打印日志
SLF4J + slf4j-nop 组合结果:
无任何打印(slf4j-nop关闭了日志打印)
注意:提前排除SpringBoot默认自带的logback日志实现.
小结一下:
- 添加依赖:
slf4j-api,使用sl4j的API在项目中进行统一日志记录 - 绑定具体的日志实现
- 对于已经实现了
sl4jAPI的日志实现,直接添加对应的依赖即可 - 对于没有实现
sl4jAPI的日志实现,需要先添加对应的日志适配器,再添加对应的依赖
- 对于已经实现了
sl4j有且仅有一个日志实现的绑定,出现多个时默认使用第一个
SLF4J桥接器
以上都是SLF4J和不同的日志组合实现,可以发现不同的日志框架打印的日志格式不一!!!
这是因为SLF4J为了适配不同的日志框架提供了不同的适配器,具体的日志记录还是交给对应的日志框架实现的
SLF4J还提供了桥接器!!!
桥接器则是由SLF4J实现一套对应的日志框架的API,日志的记录打印完全由这套API实现,可以做到格式统一,且旧代码无需任何更改.
桥接器解决的是项目中日志的遗留问题,当系统中存在之前的日志API可以通过桥接转换到SLF4J的实现
- 移除旧日志框架的依赖–此时旧代码会报错(没有依赖了)
- 添加SLF4J提供的对应的桥接组件–SLF4J自己提供一套API,旧代码不再报错
- 为项目添加SLF4J的具体实现–就是新的日志实现(SLF4J作为日志门面,还需要具体的日志实现)

代码演示
pom.xml部分内容:
<!-- |
测试代码:
/** |
注意:**适配器和桥接器不要同时出现!!!**否则可能出现StackOverflowError异常(我没演示出来~~~)
Logback
是Log4j的创始人设计的另一个开源日志组件,性能比Log4j好.
logback分为三个模块:
- logback-core:其他两个模块的核心
- logback-classic:兼容
log4j 1.x和JUL,并进行了改进 - logback-access:与Servlet容器(如Tomcat和Jetty)集成,以提供
HTTP访问日志功能
Log4j2
Apache Log4j2是对Log4j的重大升级,并参考了logback的一些优秀设计,主要特点:
- 异常处理机制: 在
logback中,Appender中的异常不会被感知到,但在log4j2中提供了异常处理机制 - 性能提升: 相较
log4j和logback都有明显性能提升 - 自动重载配置: 参考
logback的设计,自动刷新参数配置,生产环境可动态修改日志配置 - 无垃圾机制: 避免频繁的日志收集导致JVM的GC.
注意:Log4j2既是日志门面又是日志实现,但SLF4J日志门面更早出现更流行,所以市面上主流的搭配还是SLF4J+Logback/Log4J2
- log4j-api: 是日志门面
- log4j-core: 是日志实现
总结
市面上的日志框架可分成两类:日志门面和日志实现

项目中会选一个日志门面和一个日志实现组合
- 日志门面一般选
SLF4J,相比JCL更简单好用 - 日志实现一般选
Logback.SLF4J和Logback/Log4j是同一个作者更易搭配,且Logback是Log4j的升级版
市面上常见搭配是:SLF4J+Logback(也是SpringBoot的默认)
而Apache基金新推出的Log4j2比Logback性能更好,SLF4J+Log4j2也是大势所趋.