Skip to content

日志最佳实践:记录什么、怎么记录

日志写得好,排查问题时事半功倍;写得烂,日志再多也是废纸。

日志框架选择

Java 日志框架经过多年混战,目前主流组合是 SLF4J + Logback

框架说明现状
Log4j早期霸主已停止维护,不推荐
Log4j2Apache 重写版,性能好仍在维护
SLF4J日志门面(API 层)事实标准
LogbackLog4j 作者重写,Spring Boot 默认主流选择

推荐组合:SLF4J(API)+ Logback(实现)

使用占位符

java
// ❌ 字符串拼接:即使 INFO 级别不输出,拼接也已执行
log.info("用户: " + userName + " 订单: " + orderId);

// ✅ 占位符:INFO 级别不输出时,拼接不会执行
log.info("用户: {} 订单: {}", userName, orderId);

SLF4J 的 {} 占位符是延迟求值的,只有日志级别匹配时才会拼接字符串。

不要记录敏感信息

java
// ❌ 禁止:敏感信息日志
log.info("用户登录成功,用户名: {} 密码: {}", username, password);
log.info("Token: {}", token);

// ✅ 脱敏处理
log.info("用户登录成功,用户名: {}", username);
log.info("Token: {}", maskToken(token));

// 脱敏方法
private String maskToken(String token) {
    if (token == null || token.length() < 8) return "***";
    return token.substring(0, 4) + "****" + token.substring(token.length() - 4);
}

业务日志规范

结构化日志

java
// ❌ 非结构化日志:grep 难以精确查找
log.info("用户注册成功: userId=10001, phone=138****1234");

// ✅ 结构化日志:Key-Value 格式
log.info("用户注册成功 | userId={} | phone={}", 10001, "138****1234");

// ✅ 或者用 MDC 传递上下文
MDC.put("userId", "10001");
MDC.put("traceId", "abc123");
log.info("用户注册成功");
// 输出自动带上 traceId 和 userId

统一日志格式

xml
<encoder>
    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n</pattern>
</encoder>

%X{traceId} 会输出 MDC 中存储的 traceId,方便在 ELK 中按请求追踪。

日志级别选择

级别含义生产环境
ERROR错误,已影响功能记录
WARN警告,可能有问题记录
INFO正常运行流程记录
DEBUG调试信息通常关闭
TRACE最详细追踪通常关闭

总结

  1. 用 SLF4J + Logback:标准组合,配置简洁
  2. 占位符 {}:避免不必要的字符串拼接
  3. 不记录敏感信息:密码、token 都要脱敏
  4. 结构化日志:用 Key-Value 格式,方便检索
  5. 带上上下文:traceId、userId 等字段对排查问题至关重要

日志的第一读者不是人,是 ELK。写日志时要想着怎么让搜索更方便。

基于 VitePress 构建