日志的艺术:日志分级、格式规范与关键埋点策略
日志这东西,说简单也简单,说复杂也复杂。我见过太多团队,日志写得跟流水账似的,出了问题两眼一抹黑。也有团队,日志打得比业务代码还多,结果磁盘撑爆了,排查问题反而更慢。
说白了,日志是你系统在黑暗中的一盏灯。灯怎么点、点多亮、照哪里,都是有讲究的。今天我们就聊聊这盏灯该怎么用。
一、日志分级:别把信号和噪音混在一起
日志分级,核心就四个级别:DEBUG、INFO、WARN、ERROR。但很多人用错了。我见过最离谱的,是把所有日志都打成 ERROR,理由是「这样不会被过滤掉」。嗯,那你还不如直接写个 print 呢。
| 级别 | 含义 | 典型场景 | 生产环境建议 |
|---|---|---|---|
| DEBUG | 开发调试用 | 变量值、函数入参、中间计算结果 | 关闭 |
| INFO | 关键业务节点 | 请求开始/结束、状态变更、定时任务执行 | 开启 |
| WARN | 异常但可恢复 | 重试、降级、限流触发、配置缺失 | 开启 |
| ERROR | 功能受损或中断 | 数据库连接失败、第三方接口超时、业务校验不通过 | 开启 |
核心原则:生产环境只开 INFO 及以上。DEBUG 日志只在本地或测试环境用。别问我为什么,我曾经见过一个团队在生产环境开了 DEBUG,三天后磁盘满了,整个服务挂了。
我个人习惯,在代码里会这样区分:
// DEBUG:开发时看细节
logger.debug("用户查询参数:userId={}, page={}", userId, page);
// INFO:记录业务流转
logger.info("订单创建成功,orderId={}, amount={}", orderId, amount);
// WARN:出了问题但还能扛
logger.warn("库存不足,触发降级策略,skuId={}", skuId);
// ERROR:真的出事了
logger.error("支付回调处理失败,orderId={}, error={}", orderId, e.getMessage(), e);
小技巧:ERROR 日志一定要带上异常堆栈。很多人只打了个错误消息,结果排查时根本不知道哪行代码出的问题。你想想看,这跟没打有什么区别?
二、日志格式规范:让机器和人都能看懂
日志格式,我建议统一成 JSON。为什么?因为 JSON 结构清晰,日志分析工具(比如 ELK、Splunk)可以直接解析。你写个自由文本,到时候 grep 都费劲。
一个标准的日志格式,至少包含这些字段:
- 时间戳:精确到毫秒,带时区
- 日志级别:DEBUG/INFO/WARN/ERROR
- 线程名:方便追踪并发问题
- 类名/方法名:快速定位代码位置
- 消息体:业务相关的关键信息
- traceId:链路追踪的唯一标识
举个例子,我常用的格式是这样的:
{
"timestamp": "2025-03-21T14:32:17.123+08:00",
"level": "ERROR",
"thread": "http-nio-8080-exec-3",
"logger": "com.example.order.OrderService",
"message": "订单支付回调处理失败",
"traceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"context": {
"orderId": "20250321143217001",
"userId": "u_10086",
"amount": 299.00
}
}
注意:千万别在日志里打印敏感信息,比如密码、身份证号、银行卡号。我曾经接手过一个项目,日志里明文打印了用户的支付密码……嗯,那个项目后来重构了。
三、关键日志埋点策略:该打的地方一个不能少
日志埋点,说白了就是「在关键路径上放哨」。放多了浪费资源,放少了查问题找不到线索。我总结了一套「四必打」原则:
- 入口必打:每个外部请求进来,打一条 INFO 日志,记录请求参数和 traceId
- 出口必打:调用外部服务(数据库、Redis、第三方API)前后,各打一条日志
- 状态变更必打:订单状态、用户状态、任务状态发生变化时,记录变更前后值
- 异常必打:catch 到异常时,打 ERROR 日志,带上上下文
我画了一张图,帮你理解日志埋点的核心逻辑:
你看这张图,四个日志点刚好覆盖了请求的完整生命周期。入口记录「谁来了」,业务处理记录「发生了什么」,外部调用记录「去了哪里」,异常记录「哪里出了问题」。
实战建议:每个日志点都带上 traceId。这样你就能把一次请求的所有日志串起来。我习惯在网关层生成 traceId,然后通过 ThreadLocal 传递到各个服务。排查问题时,拿着 traceId 一搜,整个调用链就出来了。
四、避坑指南:我踩过的那些坑
做日志埋点这么多年,我踩过的坑不少。分享几个典型的:
- 日志打太多:我曾经在一个循环里打了 DEBUG 日志,结果循环了 10 万次,日志文件直接炸了。现在我的原则是:循环里不打日志,真要打,用计数器控制频率。
- 日志打太少:有一次线上出了个 bug,我翻遍日志只看到「处理失败」四个字。没有上下文,没有参数,没有堆栈。那感觉,就像在黑夜里找一只黑猫。
- 日志格式不统一:不同团队用不同格式,有的用 JSON,有的用自由文本,有的用 XML。排查问题时,grep 出来的结果乱七八糟。后来我强制统一了 JSON 格式,世界清净了。
最后提醒一句:日志是给你自己看的,也是给运维同事看的。写日志的时候,想想「如果三个月后的我来排查这个问题,我能看懂吗?」。如果答案是否定的,那就重写。
好了,日志的艺术就聊到这里。记住:好的日志,是系统最好的文档;坏的日志,是系统最大的噪音。