目录
[3.1 Log4j](#3.1 Log4j)
[3.1.1 日志级别](#3.1.1 日志级别)
[3.1.2 使用方式](#3.1.2 使用方式)
3) 在代码中使用 在代码中使用)
[3.1.3 定位](#3.1.3 定位)
[3.2 JUL(简单了解)](#3.2 JUL(简单了解))
[3.2.1 日志级别](#3.2.1 日志级别)
[3.2.2 使用方式](#3.2.2 使用方式)
[3.2.3 定位](#3.2.3 定位)
[3.3 JCL(简单了解)](#3.3 JCL(简单了解))
[3.3.1 日志级别](#3.3.1 日志级别)
[3.3.2 使用方式](#3.3.2 使用方式)
[3.4 SLF4J(⭐)](#3.4 SLF4J(⭐))
[3.3.1 日志级别](#3.3.1 日志级别)
[3.3.2 使用方式](#3.3.2 使用方式)
[3.5 Logback-slf4j(⭐)](#3.5 Logback-slf4j(⭐))
[3.6 Log4j2-slf4j(⭐)](#3.6 Log4j2-slf4j(⭐))
[3.6.1 日志级别](#3.6.1 日志级别)
[3.6.2 使用方式](#3.6.2 使用方式)
本文主要向大家介绍下Java开发中一个很熟悉的伙伴--日志。
日志究竟是怎么延申发展起来的呢,它又为我们的日常开发究竟带来了多少便利?
下面我将通过日志发展历程顺序来逐一讲解这些日志的使用方式 ദ്ദി˶ー̀֊ー́ )✧
一、引入
💡假设你开发了一个简单的用户注册功能,并且在代码中通过硬编码打印了一些基础的语句信息。
这时用户反馈 "注册失败",你去看控制台,只显示 "保存失败",但不知道是数据库连接超时?还是手机号重复?完全无法定位问题。
于是聪明的你就想到了用 日志框架替代硬编码打印,并在注册代码里按级别加了日志,以后排查问题就能看到具体原因,甚至生产环境没有控制台,也能通过日志文件追溯到问题。
二、核心概念:日志门面与日志实现
在讨论具体技术之前,必须要理解现代Java日志体系的核心思想:" 门面模式"。它将日志API(接口)和日志(具体)实现分离。
- 日志门面 :提供一套统一的API,你的应用程序代码只依赖这些接口。这样做的好处就是:即使将来需要更换底层的日志实现,你的业务代码也完全不需要修改。
- 日志实现 :具体的日志库,负责实现前面提到的门面接口,完成实际的日志输出工作。
可以看到日志框架这么设计,本质还是为了灵活和可维护。
三、日志框架的发展历程

3.1 Log4j
Log4j作为日志框架的第一阶段,这也是Java社区第一个广泛使用、功能完善的日志框架,不过这个1.x已经在2015年停止维护了。
3.1.1 日志级别
Log4j定义了以下级别,级别从低到高依此为:
all→trace→debug→INFO→WARN→ERROR→fatal→off
规则:Logger只输出 高于或等于其配置级别的日志。
比如,如果配置为 INFO,则会输出 INFO, WARN, ERROR, FATAL 级别的日志,而不会输出 DEBUG, TRACE
3.1.2 使用方式
log4j我就简单演示一下使用,因为这个目前已经不推荐使用了。
1)添加依赖
XML
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2)配置文件
log4j的核心就是配置,在配置中定义了日志输出的目的地和格式。
bash
# 根日志器:级别DEBUG,输出到控制台和全局文件(所有未单独配置的日志都会走这里)
log4j.rootLogger = DEBUG, CONSOLE, GLOBAL_FILE
# ========================= 公共输出源配置 =========================
# 1. 控制台输出(所有日志共享)
log4j.appender.CONSOLE = org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Target = System.out
log4j.appender.CONSOLE.layout = org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
# 2. 全局文件输出(记录所有日志,含未单独配置的模块)
log4j.appender.GLOBAL_FILE = org.apache.log4j.RollingFileAppender
log4j.appender.GLOBAL_FILE.File = ./logs/global.log # 全局日志文件
log4j.appender.GLOBAL_FILE.MaxFileSize = 10MB
log4j.appender.GLOBAL_FILE.MaxBackupIndex = 10
log4j.appender.GLOBAL_FILE.layout = org.apache.log4j.PatternLayout
log4j.appender.GLOBAL_FILE.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
# ========================= 模块专属日志配置 =========================
# 1. service模块:单独输出到service.log,不继承根日志的文件输出(避免重复)
# 定义service模块的日志器
log4j.logger.com.example.service = DEBUG, SERVICE_FILE
log4j.additivity.com.example.service = false # 禁止继承根日志的输出源(只走自己的配置)
# 配置service模块的文件输出源
log4j.appender.SERVICE_FILE = org.apache.log4j.RollingFileAppender
log4j.appender.SERVICE_FILE.File = ./logs/service.log # service模块专属日志
log4j.appender.SERVICE_FILE.MaxFileSize = 10MB
log4j.appender.SERVICE_FILE.MaxBackupIndex = 10
log4j.appender.SERVICE_FILE.layout = org.apache.log4j.PatternLayout
log4j.appender.SERVICE_FILE.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
# 2. dao模块:单独输出到dao.log,同样不继承根日志的文件输出
# 定义dao模块的日志器
log4j.logger.com.example.dao = DEBUG, DAO_FILE
log4j.additivity.com.example.dao = false # 禁止继承根日志的输出源
# 配置dao模块的文件输出源
log4j.appender.DAO_FILE = org.apache.log4j.RollingFileAppender
log4j.appender.DAO_FILE.File = ./logs/dao.log # dao模块专属日志
log4j.appender.DAO_FILE.MaxFileSize = 10MB
log4j.appender.DAO_FILE.MaxBackupIndex = 10
log4j.appender.DAO_FILE.layout = org.apache.log4j.PatternLayout
log4j.appender.DAO_FILE.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
3) 在代码中使用
java
// 注意:直接导入Log4j的类(使用Log4j自带的API)
import org.apache.log4j.Logger;
public class MyClass {
// 1. 获取Logger实例
private static final Logger logger = Logger.getLogger(MyClass.class);
public void doSomething() {
// 2. 使用不同级别记录日志
logger.debug("这是一个Debug信息,用于详细调试.");
logger.info("业务逻辑正常执行的信息.");
logger.warn("这是一个警告,表示潜在问题.");
logger.error("这是一个错误,但应用还能运行.", new Exception("错误示例"));
logger.fatal("这是一个致命错误,可能导致应用退出.");
}
}
3.1.3 定位
Log4j是具体的日志实现,在这个时候还没有门面的概念,所以应用程序代码是直接依赖Log4j的API。
3.2 JUL(简单了解)
全名叫Java Util Logging,企业中应用很少,简单了解就行。
3.2.1 日志级别
和Log4j类似,名字有些不一样。
ALL→FINEST (类似TRACE)→FINER→FINE (类似DEBUG)→CONFIG→INFO→WARNING (类似WARN)→SEVERE (类似ERROR)
规则都是一样的。
3.2.2 使用方式
使用方式和log4j极为相似,也可以通过配置文件进行管理,但是配置就不如Log4j灵活了,所以应用很少,这里就不演示了。
3.2.3 定位
是JDK自带的日志实现。
3.3 JCL(简单了解)
全名叫Jakarta Commons Logging,这个也是第一个主流的 日志门面。
也就是说只依赖JCL的接口,JCL在运行时会通过类路径动态查找并绑定到具体的日志实现(就比如Log4j, JUL)
3.3.1 日志级别
日志级别基本上和log4j一样,这里就不重复罗列了。
3.3.2 使用方式
使用方式也很简单,就是导入依赖,然后直接在代码中引用。
1)导入依赖
XML
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
2)代码中使用
java
// 注意:导入的是commons-logging的类
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class MyClass {
// 获取Log实例
private static final Log log = LogFactory.getLog(MyClass.class);
public void doSomething() {
// JCL的级别方法比较简单
log.debug("Debug消息");
log.info("Info消息");
log.warn("Warn消息");
log.error("Error消息");
log.fatal("Fatal消息");
// 记录异常
log.error("业务处理出错", new RuntimeException("异常"));
}
}
3.4 SLF4J(⭐)
slf4j定位也是一个日志门面,并且是比JCL性能更好的门面。
3.3.1 日志级别
SLF4J的级别与Log4j基本一致: TRACE→DEBUG→INFO→WARN→ERROR
3.3.2 使用方式
单说slf4j的使用的话,门面的使用无非就是导入依赖,代码中引用,所以slf4j的使用演示我会放到后面和logback和log4j2这两个日志实现一块使用。
3.5 Logback-slf4j(⭐)
Logback是SLF4J的 原生实现,是SLF4J的作者亲自设计,为了替代Log4j,性能更好,功能也更强(比如如自动热加载配置、更强大的Filter等)。
这边代码不再详细罗列,我会放到代码仓中,大家可以自行查看我主页的代码仓,或是直接访问这个链接: https://gitcode.com/m0_74977981/logDemo/tree/master/logsDemo
这里只说一点,logback自定义日志输出和log4j的语法不太一样,和log4j2倒是很相似。

3.6 Log4j2-slf4j(⭐)
Log4j2是对Log4j 1.x的彻底重写,它不仅是日志实现,也提供了自己的API。
3.6.1 日志级别
Log4j2的日志级别和slj4j/Logback一致:
TRACE→DEBUG→INFO→WARN→ERROR
3.6.2 使用方式
前面已经提到了,Log4j2提供了自己的API,所以这里理应是有两套使用方式的(使用自己原生的API以及使用其他门面),但是直接使用它原生的API会造成耦合,不推荐使用。
所以下面我将直接演示Log4j2-slf4j的使用。
使用很类似logback+slf4j:

1)配置文件
XML
<?xml version="1.0" encoding="UTF-8"?>
<!--
目标:
- 使用 SLF4J API + Log4j2 实现
- com.example.logsdemo.service1 -> logs/log1.log
- com.example.logsdemo.service2 -> logs/log2.log
-->
<Configuration status="WARN">
<Properties>
<Property name="LOG_DIR">logs</Property>
<Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %c{1.}.%M:%L - %m%ex{full}%n</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_PATTERN}"/>
</Console>
<!-- service1 独立文件 -->
<RollingFile name="Log1File"
fileName="${LOG_DIR}/log1.log"
filePattern="${LOG_DIR}/log1-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
<!-- service2 独立文件 -->
<RollingFile name="Log2File"
fileName="${LOG_DIR}/log2.log"
filePattern="${LOG_DIR}/log2-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
</Appenders>
<Loggers>
<!-- 精确匹配包,关闭向上继承,避免写入到 Root 的其他 Appender -->
<Logger name="com.example.logsdemo.service1" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="Log1File"/>
</Logger>
<Logger name="com.example.logsdemo.service2" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="Log2File"/>
</Logger>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
如上配置,service1目录下的文件日志会输出到log1.log中,service2目录下的文件日志会输出到log2.log中。
2)代码中使用

可以看见:

今日份冷笑话
有一天,Java 程序员去咖啡店点单,
服务员问:"您喝什么?"
程序员答:"给我来一杯 Java。"
服务员愣了一下:"先生,我们这儿只有 爪哇 咖啡,没有 加瓦 咖啡。"
程序员叹了口气:"唉,原来你们还没升级到 JDK( 咖啡开发工具包)......"
