沉梦手记 沉梦手记
首页
  • 基础篇
  • 集合篇
  • 并发篇
  • JVM
  • 新特性
  • 计算机网络
  • 操作系统
  • 数据结构与算法
  • 基础篇
  • MySql
  • Redis
  • 达梦数据库
  • Spring
  • SpringBoot
  • Mybatis
  • Shiro
  • 设计须知
  • UML画图
  • 权限校验
  • 设计模式
  • API网关
  • 网络通信
  • 消息队列
  • SpringCloud
  • 分布式事务
  • 云存储
  • 搜索引擎
  • 音视频处理
  • Linux 与容器化运维
  • 开发工具篇
  • 工具库篇
  • 开发技巧篇
  • 工具类系列
  • 随笔
  • 前端环境搭建
  • HTML与CSS
  • JS学习
  • Axios入门
  • Vue Router入门
  • Pinia入门
  • Vue3入门
  • Vue3进阶
  • 黑马Vue3
  • 脚手架搭建
  • 瑞吉外卖
  • 黑马点评
  • vue-blog
  • 沉梦接口开放平台
  • 用户中心
  • 聚合搜索平台
  • 仿12306项目
  • 壁纸小程序项目
  • RuoYi-Vue
  • 博客搭建
  • 网站收藏箱
  • 断墨寻径摘录
  • 费曼学习法
Github (opens new window)

沉梦听雨

时间是最好的浸渍剂,而沉淀是最好的提纯器🚀
首页
  • 基础篇
  • 集合篇
  • 并发篇
  • JVM
  • 新特性
  • 计算机网络
  • 操作系统
  • 数据结构与算法
  • 基础篇
  • MySql
  • Redis
  • 达梦数据库
  • Spring
  • SpringBoot
  • Mybatis
  • Shiro
  • 设计须知
  • UML画图
  • 权限校验
  • 设计模式
  • API网关
  • 网络通信
  • 消息队列
  • SpringCloud
  • 分布式事务
  • 云存储
  • 搜索引擎
  • 音视频处理
  • Linux 与容器化运维
  • 开发工具篇
  • 工具库篇
  • 开发技巧篇
  • 工具类系列
  • 随笔
  • 前端环境搭建
  • HTML与CSS
  • JS学习
  • Axios入门
  • Vue Router入门
  • Pinia入门
  • Vue3入门
  • Vue3进阶
  • 黑马Vue3
  • 脚手架搭建
  • 瑞吉外卖
  • 黑马点评
  • vue-blog
  • 沉梦接口开放平台
  • 用户中心
  • 聚合搜索平台
  • 仿12306项目
  • 壁纸小程序项目
  • RuoYi-Vue
  • 博客搭建
  • 网站收藏箱
  • 断墨寻径摘录
  • 费曼学习法
Github (opens new window)
  • 设计须知

    • 命名规范
    • 聊聊什么是耦合度
    • 幂等性问题分析
    • LocalDateTime和DateTime
    • 聊聊软件架构设计
    • 说说war和jar的区别
    • 聊聊多租户是什么
    • 加密算法分析
    • 那些关于管理系统的知识
    • 唯一索引和逻辑删除冲突解决方法
    • 日志记录相关
      • 日志级别
      • 常见日志框架对比
        • 为什么推荐 SLF4J + Logback?
      • SLF4J 的使用
        • 基本用法
        • 使用 Lombok 简化
      • Logback 配置详解
        • 配置文件结构
        • 完整配置示例
        • Pattern 格式说明
      • Spring Boot 中的日志配置
        • application.yml 配置方式
        • 按环境区分日志级别
      • 日志最佳实践
        • 1. 使用占位符,避免字符串拼接
        • 2. 异常日志要打印完整堆栈
        • 3. 日志内容要有上下文信息
        • 4. 敏感信息不要记录日志
        • 5. 避免在循环中打印大量日志
        • 6. 使用 MDC 添加链路追踪信息
        • 7. 条件性日志
      • 日志的常见问题与排查
        • 1. 日志不输出
        • 2. 日志框架冲突
        • 3. 日志文件过大
        • 4. 异步日志提升性能
      • 日志在排查问题中的应用
        • 常见排查场景
      • 总结
    • 过滤器、拦截器、AOP切面与其他拦截机制
    • 缓存设计分析
  • UML画图

  • 权限校验

  • 设计模式

  • 系统设计
  • 设计须知
沉梦听雨
2024-08-31
目录

日志记录相关

# 日志记录相关

# 日志级别

日志级别用于确定记录在日志中的消息的重要性和严重性。不同的日志级别对应不同的日志消息类型,通常按照以下常见的级别进行分类(从高到低):

  1. ERROR(错误): 用于记录严重错误,表示应用程序可能无法继续正常运行。这通常是致命错误,需要立即处理。
  2. WARN(警告): 用于记录一些不太严重的问题或潜在的问题,这些问题不会导致应用程序停止运行,但可能需要关注和处理。
  3. INFO(信息): 用于记录一般性的信息性消息,例如应用程序启动、配置信息、重要事件等。这些消息有助于了解应用程序的状态。
  4. DEBUG(调试): 用于记录详细的调试信息,通常用于开发和调试过程中,以便排查问题。在生产环境中通常会关闭 DEBUG 级别的日志。
  5. TRACE(跟踪): 最详细的日志级别,通常记录了每个方法的调用和变量值。TRACE 级别通常用于深度调试,对于大多数应用程序来说是不需要的,因为它会生成大量的日志数据。

还有一个更高级别 FATAL(致命),部分框架(如 Log4j)支持,表示极其严重的错误,通常会导致应用程序直接退出。Logback 中没有单独的 FATAL 级别,而是将其归入 ERROR。

在实际应用程序中,可以根据需要使用这些日志级别来记录不同类型的消息。通常,可以配置日志系统,以便在不同环境(例如开发、测试、生产)中使用不同的日志级别,以便更好地管理日志信息。例如,在生产环境中通常会将日志级别设置为 ERROR 或 WARN,以减少日志数据量,而在开发环境中可以使用 DEBUG 或 TRACE 级别以便更好地进行调试。

不同的日志框架(例如 Log4j、Logback、Java 自带的 java.util.logging 等)具有不同的配置方式,用于设置和管理日志级别。

# 常见日志框架对比

Java 生态中日志框架众多,容易混淆。下表对常见的日志框架进行对比:

框架 类型 简介 推荐度
SLF4J 日志门面 简单日志门面(Simple Logging Facade for Java),提供统一 API ⭐⭐⭐⭐⭐
Logback 日志实现 Log4j 作者编写的下一代框架,性能优秀,Spring Boot 默认集成 ⭐⭐⭐⭐⭐
Log4j 2 日志实现 Apache 基金会出品,支持异步日志,性能极佳 ⭐⭐⭐⭐
Log4j 日志实现 老牌日志框架,功能完善但性能一般,已被 Logback / Log4j 2 取代 ⭐⭐
JUL 日志实现 Java 自带的 java.util.logging,功能简单,性能一般 ⭐
JCL(Commons Logging) 日志门面 Apache Commons Logging,Spring 早期使用,运行时动态绑定实现 ⭐⭐

核心概念:日志框架分为「门面(Facade)」和「实现」两层。

  • 门面(如 SLF4J、JCL):提供统一的日志 API,代码中只面向门面编程,不直接依赖具体实现。
  • 实现(如 Logback、Log4j 2):真正负责日志输出的底层框架。

这种设计类似于 JDBC——代码面向 java.sql.Connection 接口编程,运行时由具体的数据库驱动提供实现。

# 为什么推荐 SLF4J + Logback?

  1. 解耦:代码只依赖 SLF4J 接口,切换日志实现无需修改业务代码。
  2. 性能:Logback 相比 Log4j 性能提升约 10 倍,且初始化阶段更快。
  3. 功能丰富:支持条件化配置、自动滚动归档、过滤器、JMX 管理等。
  4. Spring Boot 默认集成:开箱即用,无需额外引入依赖。

⚠️ Log4j 2 的安全提醒:Log4j 2 在 2021 年底曾爆出 CVE-2021-44228(Log4Shell) (opens new window) 严重漏洞,使用时务必升级到 2.17.1 及以上版本。

# SLF4J 的使用

# 基本用法

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class UserService {

    // 推荐使用当前类的 Class 对象作为参数
    private static final Logger log = LoggerFactory.getLogger(UserService.class);

    public void getUserById(Long id) {
        log.info("查询用户信息, userId={}", id);

        try {
            // 业务逻辑...
        } catch (Exception e) {
            log.error("查询用户失败, userId={}", id, e);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 使用 Lombok 简化

如果项目中引入了 Lombok,可以使用 @Slf4j 注解自动生成 log 字段:

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class UserService {

    public void getUserById(Long id) {
        log.info("查询用户信息, userId={}", id);
    }
}
1
2
3
4
5
6
7
8
9

Maven 依赖:

<!-- SLF4J + Logback(Spring Boot 已默认包含,通常无需手动引入) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-logging</artifactId>
</dependency>

<!-- Lombok(可选) -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>provided</scope>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12

# Logback 配置详解

Logback 的配置文件通常命名为 logback-spring.xml(Spring Boot 项目推荐)或 logback.xml,放在 src/main/resources/ 目录下。

# 配置文件结构

Logback 配置文件包含三个核心组件:

组件 作用
Logger 日志记录器,绑定日志级别和输出目标
Appender 日志输出目的地(控制台、文件等)
Encoder/Layout 日志格式化(Pattern、JSON 等)

# 完整配置示例

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">

    <!-- 引入 Spring Boot 默认配置 -->
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>

    <!-- 定义变量 -->
    <property name="LOG_PATH" value="logs"/>
    <property name="APP_NAME" value="my-app"/>
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>

    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 滚动文件输出 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/${APP_NAME}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 按天滚动,同时限制单文件大小 -->
            <fileNamePattern>${LOG_PATH}/${APP_NAME}-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>10GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 错误日志单独输出 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/${APP_NAME}-error.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/${APP_NAME}-error-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>90</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 根据环境配置不同级别 -->
    <!-- 开发环境 -->
    <springProfile name="dev">
        <root level="DEBUG">
            <appender-ref ref="CONSOLE"/>
        </root>
    </springProfile>

    <!-- 生产环境 -->
    <springProfile name="prod">
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="FILE"/>
            <appender-ref ref="ERROR_FILE"/>
        </root>
    </springProfile>

</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

# Pattern 格式说明

占位符 含义 示例
%d 日志时间 2024-08-31 11:24:37.123
%thread 线程名称 http-nio-8080-exec-1
%-5level 日志级别(左对齐,5 字符宽) ERROR
%logger{36} 日志记录器名称(最长 36 字符) c.e.s.UserService
%msg 日志消息 查询用户成功
%n 换行符
%class 输出日志的类名 com.example.UserService
%method 输出日志的方法名 getUserById
%line 输出日志的行号 42
%M 方法名(同 %method) getUserById
%X{key} MDC(Mapped Diagnostic Context)中的值 userId=12345

💡 性能提示:%class、%method、%line 需要通过堆栈信息获取,性能开销较大,生产环境不建议使用。

# Spring Boot 中的日志配置

# application.yml 配置方式

如果不需要复杂的 Logback XML 配置,可以直接在 application.yml 中进行简单配置:

logging:
  # 日志级别
  level:
    root: info
    com.example: debug
    org.springframework.web: debug
    org.hibernate.SQL: debug

  # 日志文件配置
  file:
    name: logs/my-app.log
    # 或指定路径(文件名由 spring.app.name 决定)
    # path: /var/log

  # 日志文件滚动策略
  logback:
    rollingpolicy:
      max-file-size: 100MB
      max-history: 30
      total-size-cap: 10GB
      file-name-pattern: logs/my-app-%d{yyyy-MM-dd}.%i.log.gz

  # 控制台日志格式
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 按环境区分日志级别

# application-dev.yml(开发环境)
logging:
  level:
    root: debug
    com.example: trace

# application-prod.yml(生产环境)
logging:
  level:
    root: warn
    com.example: info
1
2
3
4
5
6
7
8
9
10
11

关于 Spring Boot 多环境配置的详细说明,可参考 配置文件详解。

# 日志最佳实践

# 1. 使用占位符,避免字符串拼接

// ❌ 错误:即使日志级别不满足,也会执行字符串拼接
log.debug("用户信息: " + user.toString());

// ✅ 正确:使用占位符,日志级别不满足时不会执行拼接
log.debug("用户信息: {}", user);
1
2
3
4
5

# 2. 异常日志要打印完整堆栈

// ❌ 错误:丢失堆栈信息
try {
    // ...
} catch (Exception e) {
    log.error("处理失败: " + e.getMessage());
}

// ✅ 正确:将异常对象作为最后一个参数
try {
    // ...
} catch (Exception e) {
    log.error("处理失败, userId={}", userId, e);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

⚠️ 注意:异常对象必须是最后一个参数,且不要再用 {} 占位符引用它。

# 3. 日志内容要有上下文信息

// ❌ 错误:日志缺乏上下文,难以定位问题
log.error("查询失败");

// ✅ 正确:包含关键业务参数
log.error("查询用户订单失败, userId={}, orderId={}", userId, orderId);
1
2
3
4
5

# 4. 敏感信息不要记录日志

// ❌ 错误:密码、身份证号等敏感信息泄露
log.info("用户登录, username={}, password={}", username, password);

// ✅ 正确:脱敏处理
log.info("用户登录, username={}", username);
1
2
3
4
5

# 5. 避免在循环中打印大量日志

// ❌ 错误:循环中打印日志,可能产生海量日志
for (User user : users) {
    log.info("处理用户: {}", user.getName());
}

// ✅ 正确:使用批量处理 + 汇总日志
log.info("开始批量处理用户, 总数={}", users.size());
// ... 批量处理逻辑
log.info("批量处理完成, 成功={}, 失败={}", successCount, failCount);
1
2
3
4
5
6
7
8
9

# 6. 使用 MDC 添加链路追踪信息

MDC(Mapped Diagnostic Context)可以为每条日志自动添加上下文信息,常用于链路追踪:

import org.slf4j.MDC;

// 在请求入口设置
MDC.put("traceId", UUID.randomUUID().toString());
MDC.put("userId", String.valueOf(userId));

try {
    // 业务逻辑,所有日志都会自动带上 traceId 和 userId
    log.info("处理请求");
} finally {
    // 请求结束后清理
    MDC.clear();
}
1
2
3
4
5
6
7
8
9
10
11
12
13

配合 Pattern 中的 %X{traceId} 即可在日志中输出链路 ID。

# 7. 条件性日志

// 在高频场景下,先判断日志级别再执行耗时操作
if (log.isDebugEnabled()) {
    log.debug("复杂对象详情: {}", expensiveToString(obj));
}
1
2
3
4

# 日志的常见问题与排查

# 1. 日志不输出

可能原因:

  • 日志级别配置过高,导致低级别日志被过滤
  • Logback 配置文件命名或位置错误(Spring Boot 项目应使用 logback-spring.xml)
  • 存在多个日志实现冲突

排查方法:

// 打印当前 Logger 的级别
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
lc.getLoggerList().forEach(l -> System.out.println(l.getName() + " => " + l.getLevel()));
1
2
3

# 2. 日志框架冲突

当项目中同时引入多个日志框架时,可能出现 Class path contains multiple SLF4J bindings 警告。

解决方法:使用 Maven 排除冲突依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <!-- 排除 commons-logging -->
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
1
2
3
4
5
6
7
8
9
10
11

# 3. 日志文件过大

解决方法:

  • 配置 RollingFileAppender 实现日志滚动
  • 设置 maxFileSize(单文件最大大小)和 maxHistory(保留天数)
  • 设置 totalSizeCap(总大小上限),避免磁盘被占满

三个滚动参数的关系:

参数 限制对象 触发动作
maxFileSize 单个归档文件大小 超过则滚动生成新文件
maxHistory 归档文件保留数量/天数 超过则删除最旧的文件
totalSizeCap 所有归档文件总大小 超过则删除最旧的文件

maxHistory 和 totalSizeCap 是双重限制,哪个先触发就先清理。

totalSizeCap 超过后的行为:

  • 会自动删除最旧的归档日志文件(按文件名排序,即日期最早或序号最小的 .gz 文件)
  • 逐个删除,直到总大小降到 totalSizeCap 以下
  • 清理发生在每次日志滚动时(即新建归档文件时检查),不是实时监控

⚠️ 注意:totalSizeCap 只管归档文件,当前正在写入的活跃日志文件(如 my-app.log)不计入此限制,所以实际磁盘占用可能略大于 totalSizeCap。

# 4. 异步日志提升性能

在高并发场景下,同步写日志可能成为性能瓶颈。可以使用异步 Appender:

<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
    <!-- 不丢弃 WARN/ERROR 级别日志 -->
    <discardingThreshold>0</discardingThreshold>
    <!-- 队列大小 -->
    <queueSize>1024</queueSize>
    <!-- 引用实际的 Appender -->
    <appender-ref ref="FILE"/>
</appender>
1
2
3
4
5
6
7
8

💡 如果使用 Log4j 2,其原生支持异步日志,性能更优,配置更简单。

# 日志在排查问题中的应用

日志是线上问题排查的核心手段。关于 Linux 环境下查看日志的常用命令(grep、tail、less、zcat 等),可参考 高效查看日志的常用命令详解。

# 常见排查场景

场景 命令示例
实时查看错误日志 tail -F app.log \| grep --line-buffered -i "error"
查看某时间段日志 sed -n '/2024-08-31 10:00/,/2024-08-31 11:00/p' app.log
统计 ERROR 数量 grep -c "ERROR" app.log
查看压缩历史日志 zcat app.log.2024-08-30.gz \| grep "ERROR"
查看某异常堆栈 grep -A 20 "NullPointerException" app.log

# 总结

要点 说明
日志级别 ERROR > WARN > INFO > DEBUG > TRACE,按环境配置不同级别
框架选择 推荐 SLF4J(门面)+ Logback(实现),Spring Boot 默认集成
配置方式 简单场景用 application.yml,复杂场景用 logback-spring.xml
占位符 使用 {} 占位符,避免字符串拼接
异常处理 异常对象作为最后一个参数,保留完整堆栈
上下文信息 日志中包含关键业务参数,便于定位问题
敏感信息 密码、身份证号等敏感信息禁止记录日志
性能优化 高频场景使用 isDebugEnabled() 判断;高并发使用异步日志
链路追踪 使用 MDC 添加 traceId,便于分布式系统问题追踪
日志归档 配置滚动策略,控制日志文件大小和保留时间
上次更新: 2026/8/14 18:06:06
唯一索引和逻辑删除冲突解决方法
过滤器、拦截器、AOP切面与其他拦截机制

← 唯一索引和逻辑删除冲突解决方法 过滤器、拦截器、AOP切面与其他拦截机制→

Theme by Vdoing | Copyright © 2023-2026 沉梦听雨 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式