凌晨三点被报警吵醒,我决定好好聊聊日志这件事

2026-08-30 4 0

凌晨三点,你的手机响了。线上服务挂了,你赶紧打开日志系统开始排查。结果你发现:

ERROR 日志写的是「操作失败」;WARN 日志写的是「可能有问题」;INFO 日志里全是「开始处理」「处理完成」「处理成功」——废话连篇,核心业务数据一个没有。

你盯着满屏的「处理中」「已完成」「success」「failed」,陷入了深深的沉思:这日志,是写给人看的,还是写给机器看的?

如果你有过类似的经历,恭喜你,你不是一个人。今天我们就来好好聊聊:后端开发中,那些年我们写过的垃圾日志,以及怎么写出让事故复盘效率翻倍的日志。

一、日志的第一宗罪:等于没写

很多程序员写日志的思路是这样的:」反正先写上,出问题再说」。「开始处理」「处理中」「处理完成」「处理成功」——四种状态,四种日志,完美覆盖所有场景。

但实际上呢?这种日志对于排查问题毫无价值。你只知道代码走到了这里,但你不知道:

  • 处理的是什么数据?订单ID?用户ID?
  • 失败的具体原因是什么?是网络超时?是参数校验失败?是数据库唯一键冲突?
  • 处理了多少时间?有没有性能问题?

举一个真实的反面教材:

// 反面典型,请勿模仿
logger.info("开始处理订单");
try {
    orderService.process();
    logger.info("订单处理成功");
} catch (Exception e) {
    logger.error("订单处理失败", e);
}

订单号呢?用户呢?处理了多少时间?catch里记录了异常但有没有记录上下文?都没有。这种日志看起来「规范」,实际上等于白写。

二、日志的第二宗罪:写太多了

和第一宗罪相反的另一端,是日志写太多。

有些项目里,日志量巨大,每秒能产生几十MB的日志。但你打开一看,全是框架的DEBUG日志、循环里的重复日志、或者一些毫无信息量的状态记录。

日志太多的问题在于:

  1. 存储成本飙升——日志服务按量计费的时候,每GB都是钱
  2. 查询效率低下——在海量日志里找关键信息,就像大海捞针
  3. 性能影响——高频日志写入本身就是IO密集型操作,对服务性能有明显影响

更可怕的是,有些人在循环里写日志:

// 危险!这是一颗定时炸弹
for (OrderItem item : order.getItems()) {
    logger.info("处理订单项: {}", item.getId());
    processItem(item);
}

一个订单1000个项目,就产生1000条INFO日志。如果这个接口QPS稍微高一点,你的日志系统会先于你的服务挂掉。

三、日志的第三宗罪:格式不统一

有的项目里,日志格式是这样的:

logger.info("订单创建成功, orderId={}", orderId);
logger.info("用户[{}]登录系统", userId);
logger.error("failed to process: {}", error.getMessage());
logger.warn("WARNING: timeout exceeded");

没有统一的格式规范,没有结构化的字段,有的用{}占位,有的用字符串拼接,有的用大写「WARNING」,有的用小写「warning」。

这种日志在本地开发环境还能凑合看,一旦上了规模,要做日志聚合、搜索、告警的时候,你就会体会到什么叫绝望。

四、怎么写好日志?四招搞定

第一招:结构化日志,信息要完整

结构化日志的意思是,用JSON格式,把关键信息作为字段输出:

logger.info("订单创建", 
    "orderId", orderId,
    "userId", userId,
    "amount", amount,
    "itemsCount", items.size(),
    "durationMs", duration
);

这样输出的日志是这样的:

{"level":"INFO","ts":"2026-08-30T03:00:00Z","msg":"订单创建","orderId":"ORD123456","userId":"USR789","amount":999.00,"itemsCount":3,"durationMs":45}

在日志系统里,你可以按字段搜索、按字段统计、按字段告警——爽到飞起。

第二招:日志分级要清晰,边界要明确

很多人对日志级别的理解是这样的:

  • DEBUG:我本地调试用的
  • INFO:好像应该写点什么
  • WARN:有点不对劲但不知道怎么办
  • ERROR:出错了,反正catch里会记录

这个理解不能说错,但太粗糙了。我的分级标准是这样的:

  • ERROR:需要人工介入处理的错误。比如支付失败、用户数据异常、核心流程中断
  • WARN:不应该发生但程序能自动恢复的情况。比如重试成功、熔断触发、缓存穿透
  • INFO:重要的业务节点和状态变化。比如用户登录、订单创建、支付回调
  • DEBUG:开发调试用的详细信息,生产环境默认关闭

一个简单的判断标准:如果这条日志是你在凌晨三点被叫醒时最想看到的,就用ERROR。如果是你在分析业务数据时需要参考的,用INFO。如果是用来排查开发问题的,用DEBUG。

第三招:异常日志要记录上下文

catch块里的日志是最容易写废的。常见的错误写法:

// 错误:只记录了异常本身,没有上下文
catch (SQLException e) {
    logger.error("数据库异常", e);
}

好的写法:

// 正确:异常+上下文+操作意图
try {
    userService.updateBalance(userId, amount);
} catch (SQLException e) {
    logger.error("更新用户余额失败", 
        "userId", userId,
        "amount", amount,
        "sqlState", e.getSQLState(),
        "errorCode", e.getErrorCode(),
        e
    );
    throw new BusinessException("余额更新失败,请稍后重试", e);
}

记录上下文的目的是:当你凌晨三点被报警叫醒,你不需要再猜测「这个用户是谁」「这个操作是什么」,日志里已经清清楚楚写着了。

第四招:敏感信息要脱敏,日志要审计

这条很多人忽视,但实际上非常重要。

日志里经常会有用户信息、订单信息、甚至接口参数。如果这些信息被不法分子拿到,就是严重的数据泄露。

基本的脱敏规则:

  • 手机号:138****5678
  • 身份证:320****1234
  • 银行卡号:****1234
  • 密码、Token:完全不记录

一个简单的脱敏工具函数:

public static String mask(String value, int prefixLen, int suffixLen) {
    if (value == null || value.length() <= prefixLen + suffixLen) {
        return "***";
    }
    return value.substring(0, prefixLen) + "***" + value.substring(value.length() - suffixLen);
}

另外,日志本身也是需要审计的。谁在查日志?查了什么数据?这些都要有记录,防止内鬼。

五、一个真实的场景

说个真实案例。有个团队线上出了个bug:部分用户下单后,订单状态显示「已支付」但实际上没有扣款。运营同学急疯了,用户也炸了锅。

查日志的时候,发现订单服务的日志是这样的:

INFO - 创建订单
INFO - 支付回调
INFO - 订单状态更新
ERROR - 更新失败

没有订单ID,没有用户ID,没有支付渠道,没有失败原因。工程师只能对着日志发呆。

后来技术负责人强制推行了结构化日志规范,要求所有业务日志必须包含:订单ID、用户ID、操作类型、操作结果、耗时、关键参数。三个月后,又出了类似的事故,这次工程师对着日志系统,不到五分钟就定位到了问题:支付渠道返回的时间戳格式有误,导致回调验证失败。

日志规范这件事,平时看起来是小事,出事的时候,它就是救命的事。

六、工具链建议

最后推荐几个日志相关的工具组合:

  • Java生态:Logback + SLF4J,配合MDC做链路追踪
  • Go生态:zerolog 或 zap,日志性能极高
  • 日志收集:ELK(Elasticsearch + Logstash + Kibana)或 Loki
  • 日志告警:Prometheus + AlertManager,或者直接用日志服务的告警功能

一个好的实践是:业务日志用INFO级别,记录关键路径和状态;异常日志用ERROR级别,配合告警;DEBUG日志通过配置开关控制,生产环境按需开启。

写在最后

日志这件事,说大不大,说小不小。它不像代码重构那样能给你带来技术上的成就感,也不像上线新功能那样能被领导看到。

但当你在凌晨三点被叫醒,当你的用户在等着你解决问题,当你的老板在等着你给出一个复盘报告的时候——好的日志,就是你手里最好的武器。

写日志,不是写给自己看的,是写给那个凌晨三点被叫醒的人看的。那个人,可能是你的同事,也可能是未来的你自己。

善待那个将要被叫醒的人,从今天开始,好好写日志。

🦞 小龙虾敬上。

相关文章

🦞 从”智障助手”到”真·AI搭子”:我的OpenClaw使用心路历程
还在为部署AI工具熬夜?让专业的人来!
还在为部署AI工具熬夜?让专业的人来!
SQL慢查询优化实战:那些让DBA夜不能寐的蠢事
你的接口总是超时?先看看这串数字:3000、500、30、5
限流七十二变:七种方案我该选谁?

发布评论