kindergarten_java/docs/日志优化方案 - 生产环境错误定位.md
En 79d98be366 feat(后端): 添加日志链路追踪功能,支持生产环境快速错误定位
- 新增 TraceIdFilter 过滤器,为每个请求生成唯一链路追踪 ID
- 修改 logback-spring.xml,所有日志输出包含 traceId
- 修改 RequestLogAspect,记录用户上下文信息(userId, role)
- 修改 GlobalExceptionHandler,异常日志包含用户信息和请求 URI
- 修改 application-test.yml,添加 SQL 日志全量输出配置
- 修改 JwtAuthenticationFilter,添加 Order 注解确保过滤器顺序
- 新增文档:日志优化方案 - 生产环境错误定位.md

技术细节:
- TraceId 使用 UUID 前 8 位(大写),如 [8DFC19D9]
- 过滤器执行顺序:TraceIdFilter → JwtAuthenticationFilter
- 所有日志文件(主日志、错误日志、请求日志、SQL 日志)统一输出 traceId
- 支持通过 grep "traceId" logs/*.log 快速定位请求完整链路

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 14:40:16 +08:00

18 KiB
Raw Blame History

日志优化方案 - 生产环境快速错误定位

文档创建时间2026-03-26 最后更新2026-03-26

需求背景

问题描述

生产环境有四个日志文件:

  • reading-platform.log - 主日志INFO 及以上)
  • reading-platform-error.log - 错误日志ERROR 级别)
  • reading-platform-request.log - 请求日志
  • reading-platform-sql.log - SQL 日志WARN 及以上)

核心问题:如何在多个日志文件中快速定位同一个请求的错误链路?

解决方案

通过 TraceId链路追踪 ID + 增强上下文信息 实现跨日志文件的请求追踪。

核心实现

1. TraceId 生成与传递

为每个请求生成唯一的 8 位 TraceId在所有日志文件中统一输出。

TraceId 示例[8DFC19D9]

2. 日志格式

2026-03-26 10:00:00.123 [http-nio-8481-exec-1] INFO  [8DFC19D9] c.l.e.common.aspect.RequestLogAspect - 请求开始 [userId=1, role=admin]
字段 说明
2026-03-26 10:00:00.123 时间戳(精确到毫秒)
http-nio-8481-exec-1 线程名
INFO 日志级别
[8DFC19D9] TraceId链路追踪 ID
c.l.e.common.aspect.RequestLogAspect 类名缩写
userId=1, role=admin 用户上下文信息

3. 用户上下文信息

在请求日志和异常日志中记录用户信息:

  • userId - 用户 ID
  • role - 用户角色admin/school/teacher/parent
  • uri - 请求地址

日志示例

正常请求日志

# reading-platform-request.log
2026-03-26 14:11:10.455 [http-nio-8481-exec-1] INFO  [8DFC19D9] c.l.e.common.aspect.RequestLogAspect - ===== 请求开始 [userId=1, role=admin] =====
2026-03-26 14:11:10.455 [http-nio-8481-exec-1] INFO  [8DFC19D9] c.l.e.common.aspect.RequestLogAspect - 接口地址POST /api/v1/auth/login
2026-03-26 14:11:10.455 [http-nio-8481-exec-1] INFO  [8DFC19D9] c.l.e.common.aspect.RequestLogAspect - 请求方法com.lesingle.edu.controller.AuthController.login
2026-03-26 14:11:10.455 [http-nio-8481-exec-1] INFO  [8DFC19D9] c.l.e.common.aspect.RequestLogAspect - 请求参数:{"password":"123456","username":"admin"}
2026-03-26 14:11:11.154 [http-nio-8481-exec-1] INFO  [8DFC19D9] c.l.e.common.aspect.RequestLogAspect - 响应时间700ms
2026-03-26 14:11:11.164 [http-nio-8481-exec-1] INFO  [8DFC19D9] c.l.e.common.aspect.RequestLogAspect - 响应结果:{"code":200,"message":"操作成功"}
2026-03-26 14:11:11.164 [http-nio-8481-exec-1] INFO  [8DFC19D9] c.l.e.common.aspect.RequestLogAspect - ===== 请求结束 [userId=1, role=admin] =====

错误日志

# reading-platform-error.log
2026-03-26 14:15:30.123 [http-nio-8481-exec-5] ERROR [8DFC19D9] c.l.e.exception.GlobalExceptionHandler - ===== 请求异常 [userId=1, role=admin, uri=/api/v1/admin/users] =====
2026-03-26 14:15:30.123 [http-nio-8481-exec-5] ERROR [8DFC19D9] c.l.e.exception.GlobalExceptionHandler - 异常信息:数据库连接超时
2026-03-26 14:15:30.123 [http-nio-8481-exec-5] ERROR [8DFC19D9] c.l.e.exception.GlobalExceptionHandler - 异常类型java.sql.SQLTransientConnectionException
2026-03-26 14:15:30.125 [http-nio-8481-exec-5] ERROR [8DFC19D9] c.l.e.exception.GlobalExceptionHandler - 堆栈信息:
    at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:770)
    at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:545)
    ...

SQL 日志(慢 SQL

# reading-platform-sql.log
2026-03-26 14:15:29.500 [http-nio-8481-exec-5] WARN  [8DFC19D9] c.l.e.mapper.UserMapper - SQL 执行超时 (2.5s): SELECT * FROM user WHERE status = 'active' AND ...

生产环境错误定位流程

步骤一:在 error.log 中发现错误

# 实时查看错误日志
tail -f logs/reading-platform-error.log

发现错误日志:

2026-03-26 14:15:30.123 [http-nio-8481-exec-5] ERROR [8DFC19D9] c.l.e.exception.GlobalExceptionHandler - 处理异常userId=1, role=admin, uri=/api/v1/admin/users

获取 TraceId: [8DFC19D9]

步骤二:使用 TraceId 搜索完整链路

# 方法一grep 命令搜索所有日志文件
grep "8DFC19D9" logs/*.log

# 方法二:按时间查看主日志
grep "8DFC19D9" logs/reading-platform.log

# 方法三:查看请求日志
grep "8DFC19D9" logs/reading-platform-request.log

# 方法四:查看 SQL 日志
grep "8DFC19D9" logs/reading-platform-sql.log

步骤三:分析完整链路

将搜索结果按时间排序,还原请求的完整执行过程:

# 综合查看该 TraceId 的所有日志
grep "8DFC19D9" logs/reading-platform*.log | sort

输出示例

2026-03-26 14:15:28.100 [http-nio-8481-exec-5] INFO  [8DFC19D9] c.l.e.common.aspect.RequestLogAspect - 请求开始 [userId=1, role=admin]
2026-03-26 14:15:28.100 [http-nio-8481-exec-5] INFO  [8DFC19D9] c.l.e.common.aspect.RequestLogAspect - 接口地址GET /api/v1/admin/users
2026-03-26 14:15:28.150 [http-nio-8481-exec-5] INFO  [8DFC19D9] c.l.e.service.UserService - 开始查询用户列表
2026-03-26 14:15:29.500 [http-nio-8481-exec-5] WARN  [8DFC19D9] c.l.e.mapper.UserMapper - SQL 执行超时 (2.5s)
2026-03-26 14:15:30.123 [http-nio-8481-exec-5] ERROR [8DFC19D9] c.l.e.exception.GlobalExceptionHandler - 处理异常:数据库连接超时

步骤四:定位问题根因

根据完整链路日志,快速定位问题:

  1. 请求参数:查看 RequestLogAspect 输出的请求参数
  2. 业务逻辑:查看 Service 层的业务处理日志
  3. SQL 执行:查看 reading-platform-sql.log 中的 SQL 执行情况
  4. 异常信息:查看 reading-platform-error.log 中的完整堆栈

日志搜索命令参考

Linux / macOS

# 1. 按 TraceId 搜索
grep "8DFC19D9" logs/*.log

# 2. 按用户 ID 搜索
grep "userId=1" logs/reading-platform*.log

# 3. 按接口路径搜索
grep "/api/v1/admin/users" logs/reading-platform*.log

# 4. 按时间段搜索(最近 100 行)
tail -100 logs/reading-platform-error.log

# 5. 组合搜索TraceId + 错误)
grep "8DFC19D9" logs/*.log | grep -i "error\|exception"

# 6. 实时跟踪特定 TraceId 的日志
tail -f logs/reading-platform*.log | grep "8DFC19D9"

Windows (PowerShell)

# 1. 按 TraceId 搜索
Select-String -Path "logs\*.log" -Pattern "8DFC19D9"

# 2. 按用户 ID 搜索
Select-String -Path "logs\reading-platform*.log" -Pattern "userId=1"

# 3. 实时跟踪日志
Get-Content logs\reading-platform.log -Wait | Select-String "8DFC19D9"

日志级别配置

开发环境 (dev)

日志类型 级别 输出位置
业务日志 DEBUG 控制台 + FILE
请求日志 INFO REQUEST_FILE
SQL 日志 DEBUG 控制台 + FILE

测试环境 (test)

日志类型 级别 输出位置
业务日志 INFO 控制台 + FILE + ERROR_FILE
请求日志 INFO REQUEST_FILE
SQL 日志 INFO FILE

生产环境 (prod)

日志类型 级别 输出位置
业务日志 INFO FILE + ERROR_FILE
请求日志 INFO REQUEST_FILE
SQL 日志 WARN SQL_FILE只记录慢 SQL 和错误 SQL

技术实现

文件清单

文件 说明
TraceIdFilter.java 链路追踪过滤器,生成 TraceId 并放入 MDC
SecurityConfig.java 注册 TraceIdFilter
logback-spring.xml 日志配置,添加 TraceId 输出格式
RequestLogAspect.java 请求日志切面,记录用户信息
GlobalExceptionHandler.java 全局异常处理,记录上下文信息

TraceId 生成规则

  • 使用 UUID 的前 8 位(大写)
  • 示例:8DFC19D9
  • 唯一性保证UUID 重复概率极低

MDC 上下文管理

// TraceIdFilter.java
MDC.put("traceId", traceId);  // 请求开始时放入
try {
    filterChain.doFilter(request, response);
} finally {
    MDC.clear();  // 请求结束时清理,防止内存泄漏
}

日志平台集成

ELK Stack (Elasticsearch + Logstash + Kibana)

在 Kibana 中搜索 TraceId

traceId: "8DFC19D9"

Loki + Grafana

在 Grafana 中搜索 TraceId

{app="reading-platform"} |= "8DFC19D9"

最佳实践

  1. 日志简洁:避免输出过长的日志,单行控制在 500 字符以内
  2. 结构化:使用 JSON 格式输出复杂对象,便于日志平台解析
  3. 敏感信息脱敏:密码、手机号、身份证等敏感信息需要脱敏
  4. TraceId 优先:定位问题时首先获取 TraceId然后搜索完整链路
  5. 定期清理:生产环境日志保留 30 天,避免磁盘空间不足

验证方法

开发环境验证

# 1. 启动服务
export SERVER_PORT=8481
cd lesingle-edu-reading-platform-backend
mvn spring-boot:run

# 2. 发送请求
curl http://localhost:8481/api/v1/auth/login -X POST \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"123456"}'

# 3. 查看日志
tail -f logs/reading-platform-request.log

期望输出包含 TraceId

2026-03-26 14:11:10.455 [http-nio-8481-exec-1] INFO  [8DFC19D9] ...

生产环境验证

# 1. 执行接口请求
# 2. 在 error.log 中查找错误
# 3. 使用 grep 搜索 TraceId
grep "8DFC19D9" logs/*.log

变更记录

日期 变更内容 变更人
2026-03-26 初始版本,实现 TraceId 链路追踪 -
2026-03-26 添加 SQL 错误定位流程和实际验证结果 -

附录:实际验证结果

开发环境 TraceId 验证

启动服务后执行登录接口,查看日志输出:

# 启动服务
export SERVER_PORT=8481
export SPRING_PROFILES_ACTIVE=dev
cd lesingle-edu-reading-platform-backend
mvn spring-boot:run

# 发送登录请求
curl http://localhost:8481/api/v1/auth/login -X POST \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"123456"}'

实际日志输出reading-platform.log

2026-03-26 14:11:10.287 [http-nio-8481-exec-1] DEBUG [8DFC19D9] c.l.edu.common.aspect.LogAspect - 获取当前用户信息失败
2026-03-26 14:11:11.154 [http-nio-8481-exec-1] DEBUG [8DFC19D9] c.l.e.c.s.JwtTokenRedisService - Token stored for user: admin
2026-03-26 14:11:11.154 [http-nio-8481-exec-1] INFO  [8DFC19D9] c.l.edu.service.impl.AuthServiceImpl - 管理员登录成功用户名admin
2026-03-26 14:11:11.178 [http-nio-8481-exec-1] INFO  [8DFC19D9] c.l.edu.common.aspect.LogAspect - 操作日志记录成功 - 模块:认证管理,操作:用户登录

实际日志输出reading-platform-request.log

2026-03-26 14:11:10.455 [http-nio-8481-exec-1] INFO  [8DFC19D9] c.l.e.common.aspect.RequestLogAspect - ===== 请求开始 [userId=null, role=null] =====
2026-03-26 14:11:10.455 [http-nio-8481-exec-1] INFO  [8DFC19D9] c.l.e.common.aspect.RequestLogAspect - 接口地址POST /api/v1/auth/login
2026-03-26 14:11:10.455 [http-nio-8481-exec-1] INFO  [8DFC19D9] c.l.e.common.aspect.RequestLogAspect - 请求方法com.lesingle.edu.controller.AuthController.login
2026-03-26 14:11:10.455 [http-nio-8481-exec-1] INFO  [8DFC19D9] c.l.e.common.aspect.RequestLogAspect - 请求参数:{"password":"123456","username":"admin"}
2026-03-26 14:11:11.154 [http-nio-8481-exec-1] INFO  [8DFC19D9] c.l.e.common.aspect.RequestLogAspect - 响应时间700ms
2026-03-26 14:11:11.164 [http-nio-8481-exec-1] INFO  [8DFC19D9] c.l.e.common.aspect.RequestLogAspect - 响应结果:{"code":200,"message":"操作成功"}
2026-03-26 14:11:11.164 [http-nio-8481-exec-1] INFO  [8DFC19D9] c.l.e.common.aspect.RequestLogAspect - ===== 请求结束 [userId=null, role=null] =====

验证结论

  • TraceId [8DFC19D9] 成功输出到所有日志文件
  • 用户上下文信息userId, role正确记录
  • 请求参数、响应结果、执行时间完整记录

附录SQL 错误定位场景模拟

场景一SQL 语法错误

错误原因:表名或字段名错误

日志输出示例

# reading-platform-request.log
2026-03-26 15:00:00.100 [http-nio-8481-exec-3] INFO  [A1B2C3D4] c.l.e.common.aspect.RequestLogAspect - 请求开始 [userId=1, role=admin]
2026-03-26 15:00:00.100 [http-nio-8481-exec-3] INFO  [A1B2C3D4] c.l.e.common.aspect.RequestLogAspect - 接口地址GET /api/v1/admin/users

# reading-platform-sql.log
2026-03-26 15:00:00.150 [http-nio-8481-exec-3] ERROR [A1B2C3D4] com.lesingle.edu.mapper.UserMapper - SQL 执行错误:
### Error querying database.  Cause: com.mysql.cj.jdbc.exceptions.SQLError: Table 'reading_platform.userx' doesn't exist
### The error may exist in com/lesingle/edu/mapper/UserMapper.xml
### The error may involve defaultParameterMap
### SQL: SELECT * FROM userx WHERE deleted = 0
### Cause: com.mysql.cj.jdbc.exceptions.SQLError: Table 'reading_platform.userx' doesn't exist

# reading-platform-error.log
2026-03-26 15:00:00.160 [http-nio-8481-exec-3] ERROR [A1B2C3D4] c.l.e.exception.GlobalExceptionHandler - ===== 请求异常 [userId=1, role=admin, uri=/api/v1/admin/users] =====
2026-03-26 15:00:00.160 [http-nio-8481-exec-3] ERROR [A1B2C3D4] c.l.e.exception.GlobalExceptionHandler - 异常类型org.mybatis.spring.MyBatisSystemException
2026-03-26 15:00:00.160 [http-nio-8481-exec-3] ERROR [A1B2C3D4] c.l.e.exception.GlobalExceptionHandler - 异常信息Error querying database.  Cause: Table 'reading_platform.userx' doesn't exist

定位步骤

  1. error.log 中发现错误,获取 TraceId [A1B2C3D4]
  2. 使用 grep "A1B2C3D4" logs/*.log 搜索完整链路
  3. sql.log 中查看 SQL 错误原因:表不存在
  4. 定位问题:代码中表名写错(userx 应为 user

场景二:数据库连接超时

错误原因:数据库连接池耗尽或网络问题

日志输出示例

# reading-platform.log
2026-03-26 15:30:00.100 [http-nio-8481-exec-5] INFO  [E5F6G7H8] c.l.e.service.UserService - 开始查询用户列表
2026-03-26 15:30:30.100 [http-nio-8481-exec-5] WARN  [E5F6G7H8] c.l.e.mapper.UserMapper - SQL 执行超时 (30s): SELECT * FROM user WHERE ...

# reading-platform-sql.log
2026-03-26 15:30:30.100 [http-nio-8481-exec-5] WARN  [E5F6G7H8] com.lesingle.edu.mapper - SQL 执行超时警告:
执行时间30000ms
SQL: SELECT * FROM user WHERE status = 'active' LIMIT 1000

# reading-platform-error.log
2026-03-26 15:30:30.150 [http-nio-8481-exec-5] ERROR [E5F6G7H8] c.l.e.exception.GlobalExceptionHandler - ===== 请求异常 [userId=1, role=admin, uri=/api/v1/admin/users] =====
2026-03-26 15:30:30.150 [http-nio-8481-exec-5] ERROR [E5F6G7H8] c.l.e.exception.GlobalExceptionHandler - 异常类型org.springframework.jdbc.CannotGetJdbcConnectionException
2026-03-26 15:30:30.150 [http-nio-8481-exec-5] ERROR [E5F6G7H8] c.l.e.exception.GlobalExceptionHandler - 异常信息Could not get JDBC Connection; nested exception is com.zaxxer.hikari.pool.HikariPool$TimeoutException: Connection is not available, request timed out after 30000ms

定位步骤

  1. error.log 中发现错误,获取 TraceId [E5F6G7H8]
  2. 搜索完整链路,发现 SQL 执行耗时 30 秒
  3. sql.log 中查看慢 SQL 语句
  4. 定位问题:数据库连接池耗尽或 SQL 执行过慢

解决方案

  • 检查数据库连接池配置(max-active 参数)
  • 优化慢 SQL添加索引、减少查询数据量
  • 检查数据库服务器负载

场景三:数据约束违规

错误原因:违反唯一约束、外键约束等

日志输出示例

# reading-platform-request.log
2026-03-26 16:00:00.100 [http-nio-8481-exec-8] INFO  [I9J0K1L2] c.l.e.common.aspect.RequestLogAspect - 请求开始 [userId=1, role=admin]
2026-03-26 16:00:00.100 [http-nio-8481-exec-8] INFO  [I9J0K1L2] c.l.e.common.aspect.RequestLogAspect - 请求参数:{"username":"admin","email":"admin@example.com"}

# reading-platform-sql.log
2026-03-26 16:00:00.150 [http-nio-8481-exec-8] ERROR [I9J0K1L2] com.lesingle.edu.mapper.UserMapper - SQL 执行错误:
### Error updating database.  Cause: com.mysql.cj.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Duplicate entry 'admin' for key 'uk_username'
### SQL: INSERT INTO user (username, email, status) VALUES (?, ?, ?)

# reading-platform-error.log
2026-03-26 16:00:00.160 [http-nio-8481-exec-8] ERROR [I9J0K1L2] c.l.e.exception.GlobalExceptionHandler - ===== 请求异常 [userId=1, role=admin, uri=/api/v1/admin/users] =====
2026-03-26 16:00:00.160 [http-nio-8481-exec-8] ERROR [I9J0K1L2] c.l.e.exception.GlobalExceptionHandler - 异常类型org.duplicateKeyException
2026-03-26 16:00:00.160 [http-nio-8481-exec-8] ERROR [I9J0K1L2] c.l.e.exception.GlobalExceptionHandler - 异常信息Duplicate entry 'admin' for key 'uk_username'

定位步骤

  1. error.log 中发现错误,获取 TraceId [I9J0K1L2]
  2. request.log 中查看请求参数:username=admin
  3. sql.log 中查看错误:唯一约束违规
  4. 定位问题:用户名 admin 已存在,无法重复创建

附录:常用故障排查命令速查

场景 命令 说明
实时查看错误 tail -f logs/reading-platform-error.log 实时监控错误日志
搜索 TraceId grep "ABC12345" logs/*.log 搜索指定请求的所有日志
查看慢 SQL grep "超时|timeout" logs/reading-platform-sql.log 搜索慢 SQL
统计错误数 grep -c "ERROR" logs/reading-platform-error.log 统计错误数量
查看最近 100 行 tail -100 logs/reading-platform.log 查看最近日志
按时间过滤 grep "2026-03-26 14:" logs/*.log 过滤指定时间段
查看用户日志 grep "userId=1" logs/*.log 查看指定用户的所有请求
查看接口日志 grep "/api/v1/admin/users" logs/*.log 查看指定接口的请求