- 新增 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>
18 KiB
18 KiB
日志优化方案 - 生产环境快速错误定位
文档创建时间: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- 用户 IDrole- 用户角色(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 - 处理异常:数据库连接超时
步骤四:定位问题根因
根据完整链路日志,快速定位问题:
- 请求参数:查看
RequestLogAspect输出的请求参数 - 业务逻辑:查看 Service 层的业务处理日志
- SQL 执行:查看
reading-platform-sql.log中的 SQL 执行情况 - 异常信息:查看
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"
最佳实践
- 日志简洁:避免输出过长的日志,单行控制在 500 字符以内
- 结构化:使用 JSON 格式输出复杂对象,便于日志平台解析
- 敏感信息脱敏:密码、手机号、身份证等敏感信息需要脱敏
- TraceId 优先:定位问题时首先获取 TraceId,然后搜索完整链路
- 定期清理:生产环境日志保留 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
定位步骤:
- 在
error.log中发现错误,获取 TraceId[A1B2C3D4] - 使用
grep "A1B2C3D4" logs/*.log搜索完整链路 - 在
sql.log中查看 SQL 错误原因:表不存在 - 定位问题:代码中表名写错(
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
定位步骤:
- 在
error.log中发现错误,获取 TraceId[E5F6G7H8] - 搜索完整链路,发现 SQL 执行耗时 30 秒
- 在
sql.log中查看慢 SQL 语句 - 定位问题:数据库连接池耗尽或 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'
定位步骤:
- 在
error.log中发现错误,获取 TraceId[I9J0K1L2] - 在
request.log中查看请求参数:username=admin - 在
sql.log中查看错误:唯一约束违规 - 定位问题:用户名
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 |
查看指定接口的请求 |