文章目录
- 一、SLF4J和Logback分别是什么?
-
- [1. SLF4J 是什么?](#1. SLF4J 是什么?)
- [2. Logback 是什么?](#2. Logback 是什么?)
- [3. 两者到底是什么关系?](#3. 两者到底是什么关系?)
- [4. 为什么不直接使用 Logback?](#4. 为什么不直接使用 Logback?)
- [5. 一句话区分](#5. 一句话区分)
- 二、怎么在项目中使用SLF4J和Logback?
- 三、拓展:使用注解
-
- [1. `@Slf4j` 是谁提供的?](#1.
@Slf4j是谁提供的?) - [2. 它到底生成了什么?](#2. 它到底生成了什么?)
- [3. 这个过程发生在哪里?](#3. 这个过程发生在哪里?)
- [4. Lombok 到底生成了什么?](#4. Lombok 到底生成了什么?)
- [5. 那 `@Slf4j` 和 Logback 有关系吗?](#5. 那
@Slf4j和 Logback 有关系吗?) - [6. 为什么写了 `@Slf4j` 就可以直接使用 `log`?](#6. 为什么写了
@Slf4j就可以直接使用log?) - [7. 为什么有时候 `@Slf4j` 不生效?](#7. 为什么有时候
@Slf4j不生效?) -
- 第一种:Lombok没配置
- [第二种:IDE没有正确启用 Lombok](#第二种:IDE没有正确启用 Lombok)
- [第三种:没有 SLF4J](#第三种:没有 SLF4J)
- 第四种:没有日志实现
- [8. 你可以用一个非常简单的方式验证](#8. 你可以用一个非常简单的方式验证)
- [9. 最后把整个链条串起来](#9. 最后把整个链条串起来)
- [1. `@Slf4j` 是谁提供的?](#1.
一、SLF4J和Logback分别是什么?
可以把它们先粗暴地理解成:
SLF4J = 规定"怎么记日志"的接口/门面
Logback = 真正负责"把日志记下来"的实现
人类又一次把"打印一句话"拆成两套东西,但这个拆分其实非常有价值。
1. SLF4J 是什么?
SLF4J 全称是 Simple Logging Facade for Java。
它本身不是具体的日志实现,而是一层抽象接口。
比如你写:
java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Logger log = LoggerFactory.getLogger(UserService.class);
log.info("用户登录");
这里的:
java
Logger log
就是 SLF4J 提供的 Logger 接口。
你调用:
java
log.info("用户登录");
实际上是在说:
"我想记录一条 INFO 级别的日志。"
但 SLF4J 不负责决定这句话最终写到哪里。
它把这个问题交给下面的具体日志实现。
2. Logback 是什么?
Logback 是一个真正的日志框架实现。
它负责具体执行:
这条日志怎么处理、输出到哪里、输出什么格式、是否保存文件、文件多大时切换等。
例如:
text
log.info("用户登录");
最终可能被 Logback 输出成:
text
2026-08-19 15:20:31 INFO UserService - 用户登录
并且可以输出到:
text
控制台
↓
ConsoleAppender
或者:
text
日志文件
↓
FileAppender
甚至按日期生成:
text
logs/
├── app.2026-08-18.log
├── app.2026-08-19.log
└── app.log
这些才是 Logback 干的活。
3. 两者到底是什么关系?
可以画成:
text
你的业务代码
│
│ log.info("用户登录")
↓
SLF4J
(Logger 接口)
│
│ 把日志请求交给具体实现
↓
Logback
(具体实现)
│
├──→ 控制台
│
├──→ 日志文件
│
└──→ 其他输出
所以你之前理解的那句话其实已经抓到核心了:
代码对外使用的是 SLF4J 的 Logger 接口,真正干活的是 Logback。
4. 为什么不直接使用 Logback?
假设你的业务代码直接写:
java
ch.qos.logback.classic.Logger log;
那么你的业务代码就和 Logback 强绑定了。
以后想换成 Log4j2,你可能需要改大量代码。
而使用:
java
org.slf4j.Logger
你的业务代码只依赖 SLF4J 的抽象:
text
业务代码
↓
SLF4J Logger
↓
Logback
以后换实现:
text
业务代码
↓
SLF4J Logger
↓
Log4j2
业务代码基本不用动。
这就是它非常重要的价值:
面向接口,而不是面向具体实现。
5. 一句话区分
| SLF4J | Logback | |
|---|---|---|
| 是什么 | 日志门面/抽象 | 日志实现 |
| 主要作用 | 定义日志 API | 真正处理日志 |
提供 Logger |
✅ | 实现它 |
| 决定日志输出到哪里 | ❌ | ✅ |
| 控制日志格式 | ❌ | ✅ |
| 控制文件滚动 | ❌ | ✅ |
| 可以替换 | 本身就是抽象层 | 可以换成 Log4j2 等 |
你可以记成一句非常形象的话:
SLF4J 负责"说我要记日志",Logback 负责"行,我来把它记下来"。
再往下理解,就是你前面一直在追的那条线:
Logger 接口 → LoggerFactory → SLF4J 与 Logback 的绑定 → Logback 创建具体 Logger 对象 → Appender 输出日志。
这条链搞明白,Java 日志这块基本就从"会配 XML"进入"知道它为什么能跑"的阶段了。
二、怎么在项目中使用SLF4J和Logback?
1、引入依赖
-
maven项目,如果使用了springboot,那么springboot集成的日志框架就是SLF4J+Logback,不需要额外引入依赖。
-
若不是springboot项目,需引入依赖如下:
xml
<!-- SLF4J 门面 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.13</version>
</dependency>
<!-- Logback 实现,自动绑定 SLF4J -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.6</version>
</dependency>
2、加入配置
在src->main->resources路径下,增加logback.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>
3、使用日志
新建Logger对象:
java
Logger log = LoggerFactory.getLogger(ClientApplication.class);
打印不同级别的日志:
java
log.info("===info:开始组装machine注册信息===");
log.debug("===debug:开始组装machine注册信息===");
log.warn("===warn:开始组装machine注册信息===");
log.error("===error:开始组装machine注册信息===");
例子如下:
java
public class ClientApplication extends Application {
private static final Logger log = LoggerFactory.getLogger(ClientApplication.class);
public static void main(String[] args){
launch(args);
}
@Override
public void start(Stage stage){
log.info("===info:开始组装machine注册信息===");
log.debug("===debug:开始组装machine注册信息===");
log.warn("===warn:开始组装machine注册信息===");
log.error("===error:开始组装machine注册信息===");
MachineRegisterDTO dto = new MachineRegisterDTO();
dto.setMachineName(MachineInfoUtil.getMachineName());
dto.setIp(MachineInfoUtil.getIp());
dto.setMac(MachineInfoUtil.getMac());
dto.setStatus("ONLINE");
register(dto);
stage.show();
}
}
日志打印效果:

对比两种日志打印方式效果:


三、拓展:使用注解
@Slf4j 能生效,核心其实就一句话:
它不是 SLF4J 自己提供的运行时魔法,而是 Lombok 在编译阶段帮你"生成了 Logger 代码"。
你之前理解的:
java
private static final Logger log =
LoggerFactory.getLogger(ClientApplication.class);
和:
java
@Slf4j
public class ClientApplication {
}
本质上是同一件事。只是前者你手写,后者 Lombok 替你写。人类终于把"写一行固定代码"也自动化了。
1. @Slf4j 是谁提供的?
是 Lombok。
java
import lombok.extern.slf4j.Slf4j;
注意这里:
text
@Slf4j
↓
Lombok
↓
编译阶段生成代码
↓
SLF4J Logger
↓
Logback
所以不要把 @Slf4j 误认为是 SLF4J 的注解。
2. 它到底生成了什么?
你写:
java
@Slf4j
public class UserService {
public void login() {
log.info("用户登录");
}
}
Lombok 在编译的时候,等价于帮你加入:
java
public class UserService {
private static final Logger log =
LoggerFactory.getLogger(UserService.class);
public void login() {
log.info("用户登录");
}
}
所以:
java
@Slf4j
最重要的作用其实就是:
帮你生成
log这个 Logger 对象。
3. 这个过程发生在哪里?
这里是理解 @Slf4j 的关键。
不是:
text
程序启动
↓
发现 @Slf4j
↓
动态生成 Logger
而主要是:
text
你写 Java 源代码
↓
↓
@Slf4j
↓
Lombok 编译器插件
↓
修改/增强 AST
↓
生成 Logger 字段
↓
Java 编译器编译
↓
.class 文件
↓
程序运行
也就是说:
@Slf4j主要发生在编译阶段,而不是运行阶段。
4. Lombok 到底生成了什么?
例如:
java
@Slf4j
public class UserService {
public void login() {
log.info("用户登录");
}
}
可以把 Lombok 理解成在编译的时候偷偷给你塞进去:
java
private static final org.slf4j.Logger log =
org.slf4j.LoggerFactory.getLogger(UserService.class);
然后你的:
java
log.info("用户登录");
就有东西可以调用了。
所以整个关系变成:
text
Lombok
│
│ 编译阶段生成
↓
Logger log = ...
│
↓
SLF4J Logger
│
│ 找具体实现
↓
Logback
│
↓
控制台 / 文件 / ...
5. 那 @Slf4j 和 Logback 有关系吗?
间接有关系,但 @Slf4j 本身并不依赖 Logback。
这是非常重要的一点。
例如:
java
@Slf4j
public class UserService {
public void login() {
log.info("用户登录");
}
}
Lombok生成的大概是:
java
private static final Logger log =
LoggerFactory.getLogger(UserService.class);
这里的:
java
Logger
LoggerFactory
来自:
text
SLF4J
而不是 Logback。
然后运行的时候:
text
log.info(...)
↓
SLF4J Logger
↓
SLF4J 找到具体日志实现
↓
Logback
↓
真正输出日志
因此:
Lombok
负责:
帮你生成 Logger 代码
SLF4J
负责:
定义 Logger 接口和统一调用方式
Logback
负责:
真正实现日志输出
6. 为什么写了 @Slf4j 就可以直接使用 log?
因为 Lombok帮你生成了这个字段:
java
private static final Logger log =
LoggerFactory.getLogger(UserService.class);
所以:
java
@Slf4j
public class UserService {
public void login() {
log.info("登录");
}
}
编译器最终看到的效果可以近似理解成:
java
public class UserService {
private static final Logger log =
LoggerFactory.getLogger(UserService.class);
public void login() {
log.info("登录");
}
}
所以 log 并不是凭空出现的。
7. 为什么有时候 @Slf4j 不生效?
因为这里实际上有几个环节。
你需要:
text
Lombok
↓
编译器识别 @Slf4j
↓
生成 Logger
↓
SLF4J
↓
找到日志实现
↓
Logback
任何地方出问题,都可能导致问题。
比如:
第一种:Lombok没配置
java
import lombok.extern.slf4j.Slf4j;
都找不到。
第二种:IDE没有正确启用 Lombok
可能出现:
text
Cannot resolve symbol 'log'
虽然 Maven 编译可能又能正常运行。
第三种:没有 SLF4J
Lombok生成的是类似:
java
LoggerFactory.getLogger(...)
它还是需要 SLF4J。
第四种:没有日志实现
例如 SLF4J 存在:
text
你的代码
↓
SLF4J
但没有:
text
Logback
之类的实现。
这时候你可能会看到类似:
text
No SLF4J providers were found
日志框架这套东西就像接力赛,接口、实现、绑定,一个掉棒,最后都是程序在抱怨。
8. 你可以用一个非常简单的方式验证
在 IDEA 里找到:
java
@Slf4j
public class UserService {
public void test() {
log.info("hello");
}
}
然后使用 IDEA 的:
View → Show Bytecode
或者把编译后的 .class 反编译。
你最终会看到类似:
java
private static final Logger log =
LoggerFactory.getLogger(UserService.class);
这就是 @Slf4j 的核心秘密。
9. 最后把整个链条串起来
你现在可以把它记成这一条:
text
编译阶段
│
▼
@Slf4j
│
▼
Lombok
│
│ 自动生成
▼
private static final Logger log =
LoggerFactory.getLogger(UserService.class);
│
▼
SLF4J
(日志门面/接口)
│
│ 运行时找到实现
▼
Logback
(具体日志实现)
│
┌────────┴────────┐
▼ ▼
控制台 文件
所以你之前的理解可以进一步升级成:
@Slf4j是 Lombok 提供的编译期注解,它自动生成一个 SLF4J 的Logger字段;程序运行时,这个 Logger 再通过 SLF4J 的机制交给 Logback 等具体实现处理。
最关键的是区分三个层次:
@Slf4j ≠ SLF4J ≠ Logback
而是:
Lombok负责"帮你写代码" → SLF4J负责"统一接口" → Logback负责"真正干活"。