日志是后端系统的 “黑匣子”,但很多系统的日志还停留在 “无序打印” 阶段 —— 关键操作没记录、错误信息不完整、排查问题时翻遍日志文件却一无所获。一套完善的日志体系,能将零散的日志转化为 “可观测性中枢”,实现问题快速定位、系统状态监控、业务行为分析,是系统稳定性的 “晴雨表”。
日志体系的三层架构
1. 基础日志:规范记录 “谁做了什么”
基础日志需包含 “5W1H” 要素:Who(用户)、When(时间)、Where(接口 / 模块)、What(操作)、Why(原因)、How(结果),通过统一格式输出:
@Slf4j
@RestController
@RequestMapping("/orders")
public class OrderController {
@PostMapping
public Result createOrder(@RequestBody OrderDTO dto, @RequestHeader("X-User-Id") String userId) {
// 1. 记录请求日志(包含关键参数)
log.info("OrderCreateRequest: userId={}, productId={}, quantity={}",
userId, dto.getProductId(), dto.getQuantity());
try {
Order order = orderService.create(dto, userId);
// 2. 记录成功日志(包含结果ID)
log.info("OrderCreateSuccess: orderId={}, userId={}", order.getId(), userId);
return Result.success(order.getId());
} catch (Exception e) {
// 3. 记录错误日志(包含异常堆栈)
log.error("OrderCreateFailed: userId={}, error={}", userId, e.getMessage(), e);
return Result.fail(e.getMessage());
}
}
}
日志级别规范:
info:正常业务流程(如 “订单创建成功”)warn:需要关注但不影响流程的异常(如 “库存不足,使用备用仓库”)error:导致流程失败的异常(如 “支付接口调用失败”)debug:开发调试信息(仅在测试环境开启)
2. 链路日志:追踪请求的 “完整旅程”
在分布式系统中,一个请求可能经过多个服务(如订单服务→库存服务→支付服务),链路日志通过唯一traceId串联全流程:
// 1. 生成并传递traceId(基于Spring Cloud Sleuth)
@Configuration
public class TraceConfig {
@Bean
public Filter traceFilter() {
return (request, response, chain) -> {
String traceId = request.getHeader("X-Trace-Id");
if (traceId == null) {
traceId = UUID.randomUUID().toString();
}
// 将traceId存入MDC(日志上下文)
MDC.put("traceId", traceId);
try {
chain.doFilter(request, response);
} finally {
MDC.remove("traceId");
}
};
}
}
// 2. 日志格式中包含traceId(logback.xml配置)
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} traceId=%X{traceId} - %msg%n</pattern>
</encoder>
</appender>
链路日志价值:
- 快速定位跨服务问题(如 “订单创建失败是因为库存服务超时”)
- 分析请求耗时分布(如 “支付服务占总耗时的 60%”)
- 统计各服务调用次数(如 “订单服务日均调用库存服务 10 万次”)
3. 业务日志:分析 “用户行为与系统状态”
业务日志记录关键业务事件(如 “用户首次下单”“商品库存预警”),用于业务分析和监控:
@Service
public class BusinessLogService {
@Autowired
private BusinessLogMapper logMapper;
// 记录用户行为日志(入库用于分析)
public void recordUserAction(String userId, String action, String details) {
BusinessLog log = new BusinessLog();
log.setUserId(userId);
log.setAction(action); // 如"CREATE_ORDER"、"VIEW_PRODUCT"
log.setDetails(details); // 结构化数据,如JSON
log.setCreateTime(new Date());
logMapper.insert(log);
// 同时输出到日志文件(用于实时监控)
log.info("UserAction: userId={}, action={}, details={}", userId, action, details);
}
// 记录系统状态日志(如库存预警)
public void recordSystemStatus(String module, String status, String message) {
log.warn("SystemStatus: module={}, status={}, message={}", module, status, message);
// 库存低于阈值时触发告警
if ("STOCK".equals(module) && "LOW".equals(status)) {
alarmService.send("库存预警:" + message);
}
}
}
日志体系的进阶实践
1. 日志收集与分析平台
通过 ELK(Elasticsearch+Logstash+Kibana)或 Loki 构建日志平台:
-
收集:Logstash/Fluentd 收集分散在各服务器的日志
-
存储:Elasticsearch 存储日志(支持全文检索)
-
分析:Kibana 可视化日志(如按 traceId 查询全链路、按错误类型统计频次)
查询示例:在 Kibana 中通过traceId:xxx快速定位某请求的全链路日志,或通过level:ERROR AND module:ORDER统计订单模块的错误数。
2. 日志安全与合规
- 敏感信息脱敏:日志中禁止出现明文密码、身份证号(参考 “数据脱敏” 实践)
- 日志留存策略:按法规要求留存(如金融系统需留存 6 个月以上)
- 访问控制:日志平台需权限管理,避免敏感日志泄露
3. 日志性能优化
- 异步输出:避免日志 IO 阻塞业务线程(如使用
AsyncAppender) - 分级滚动:按大小 / 时间滚动日志(如每天生成一个日志文件,超过 100MB 拆分)
- 按需输出:生产环境关闭 debug 日志,非核心模块降低日志级别
避坑指南
-
避免日志泛滥:不要打印重复或无意义的日志(如循环中打印 “处理第 x 条数据”)
-
关键操作必须记日志:如支付、订单状态变更等核心流程,确保可追溯
-
日志格式要统一:不同服务使用相同的日志格式,便于收集和分析
-
异常日志要完整:
log.error必须传入异常对象(如log.error("msg", e)),否则丢失堆栈信息
日志体系的完善程度,直接反映了系统的可维护性。它不是 “越多越好”,而是 “在需要时能找到关键信息”—— 通过规范、分层、可视化的日志设计,让日志从 “事后救火工具” 变为 “事前预警、事中监控、事后分析” 的全链路可观测平台,这是后端系统成熟度的重要标志。