2. 日志级别规范:从 DEBUG 到 FATAL,到底该怎么用?

聊日志级别,很多团队吵过架。

有人把 WARN 当普通通知打,有人用 ERROR 记录临时异常,还有人干脆只用 INFO 和 ERROR 两个级别。结果呢?告警泛滥,排查问题像大海捞针。

我个人习惯是:级别标准必须在项目第一天定死,否则后面根本改不动。先看各级别的准确定义,再说怎么在实际中统一。


2.1 五个级别的核心定义

级别 含义 典型触发条件 是否触发告警
DEBUG 开发调试信息,面向开发者自己 变量值、调用链、SQL 参数 否
INFO 正常运行事件,系统状态变化 服务启动、请求开始/结束、关键流程步骤 否
WARN 潜在风险,需要关注但不影响当前功能 配置缺失使用默认值、重试首次失败、近限值警告 视阈值而定
ERROR 功能错误,当前操作失败但系统仍在运行 数据库连接失败、第三方接口返回 500、参数校验不通过 是
FATAL 致命错误,服务即将或已经不可用 OOM、核心线程池耗尽、配置加载失败 立即告警+介入
我的一点经验: 很多团队把 ERROR 和 FATAL 混着用,结果线上重启了一堆本可以兜底的服务。建议:FATAL 只用在“服务本身已死”的场景,单个请求失败永远不该打 FATAL。

2.2 每个级别怎么用?避坑指南

DEBUG:给自己看的,别出海

DEBUG 日志说白了是“开发期显微镜”。我在项目中遇到过生产环境 DEBUG 没关,结果一天打了几十 GB 数据,磁盘撑爆了。

使用场景:

  • 进入关键方法时打印入参和重要中间值
  • 循环中的计算过程
  • 条件分支的流向判断

核心原则: 默认关闭,只在开发或排查时临时打开。千万别写 logger.debug("User {} logged in with role {}", userId, role) 这种高频日志——哪怕只是字符串拼接,在高并发下也扛不住。

INFO:记录业务痕迹,供复盘和审计用

INFO 是日志界的“正常人”。它告诉运维和监控系统:系统在正常工作。我习惯把 INFO 打在每一个有明确业务含义的节点上,比如订单创建、支付回调、定时任务执行完成。

但别滥用。我曾经看到有人把每次循环的 item 号都打 INFO,后来被监控系统负责人骂了一顿。记住:INFO 的输出量在正常业务量下应该可读,每秒几千条 INFO 就不叫 INFO 了。

WARN:黄牌警告,但不罚下

WARN 是最容易被忽略的级别,也是最容易被人随意使用的。它的本意是:系统当前没问题,但未来可能出问题。

举个例子:第三方接口超时,第一次重试失败但第二次成功。这不是 ERROR,但也不该是 INFO。我会打 WARN,并带上重试次数和超时时间。这样我们就能在监控上看到“接口抖动”的趋势。

避坑: 我曾经在项目里因为“不想浪费告警额度”,把所有的异常都记录为 WARN。结果线上服务挂了三天,WARN 日志堆到几百 GB,却没有任何告警。教训:WARN 不要替代 ERROR,它只负责“提醒”,不负责“救命”。

ERROR:出错了,但别崩溃

ERROR 是大多数开发者最熟悉的级别。但要注意:一个 ERROR 应该代表一个完整的错误事件,而不是堆栈里每条信息都打。

我见过有人把 catch (Exception e) 里打印 logger.error("错误") 之后再加一行 logger.error(e.getMessage()),这完全是浪费。正确做法是:

try {
    // 业务逻辑
} catch (SQLException e) {
    logger.error("订单:{} 写入数据库失败, 事务已回滚", orderId, e);
    // 只打一次,包含上下文和异常
}

这么做的原因:ERROR 日志要能被唯一检索。带上订单 ID、接口名、关键参数,你才能在告警里直接定位问题。

FATAL:最后的遗言

FATAL 很少见,但出现时必须认真对待。它的定义是:系统已经无法提供正常服务了。比如内存溢出后 JVM 即将退出,或者核心配置加载失败。这种情况下,打 FATAL 的同时通常伴随进程自杀或节点下线。

核心要点:

  • FATAL 必须包含完整堆栈和上下文
  • FATAL 应该直接触发最高优先级告警(短信/电话)
  • 一个 FATAL 可能对应一个进程的结束,所以别在一个请求里打多个 FATAL

2.3 统一级别标准的三个关键动作

说了半天定义,实际落地才是重点。这里有三个实践建议:

  1. 制定团队级《日志级别对照卡》——列出每个模块常见场景对应的级别。比如“限流触发”是 WARN,“限流熔断”是 ERROR。贴在 Wiki 和 IDE 注释里,新人来了直接查表。
  2. 在代码规范中约束——用 Checkstyle / SpotBugs 或自定义规则拦截不合规的日志级别选择。比如禁止在 catch 块里打 logger.info,必须用 logger.error。
  3. 定期审计——我每个季度会拉一次生产日志,按级别统计数量,揪出那些“不该是 ERROR 却打了 ERROR”或者“WARN 每秒几百条”的代码段,直接提 MR 修改。
一句话总结: 统一级别标准不是写在文档里就完事了,要靠工具+Code Review+持续优化才能养成习惯。

2.4 一张图看清整个体系

下面这张图我经常在新手培训时用,它直观展示了日志级别从开发到告警的完整流向:

日志级别层次与决策路径 日志级别判别流程 DEBUG 开发/调试临时输出 INFO 业务事件/系统状态 WARN 注意/接近阈值 ERROR 功能异常,需告警 FATAL 服务不可用,立即介入 💡 关键规则: • DEBUG 和 INFO 不触发告警,仅用于记录和复盘。 • WARN 可触发告警,但阈值要远高于 ERROR。 • ERROR → FATAL 的升级策略需在监控平台配置,避免误报。

2.5 常见场景级别对照表 (速查)

场景 正确级别 常见错误 理由
请求参数校验失败 WARN ERROR 是用户行为问题,非系统故障
数据库连接池耗尽 FATAL ERROR 服务无法处理新请求
第三方 API 超时并降级 ERROR WARN 或 INFO 功能已受影响,必须记录并告警
定时任务完成一次扫描 INFO DEBUG 需要知道任务是否正常执行
循环中临时打印变量值 DEBUG INFO 仅供开发调试用
重要提醒: 级别对照表不是一成不变的。比如在金融核心系统里,一次参数校验失败可能直接要求告警(ERROR),但在普通的 Web 应用中 WARN 就够了。统一标准的前提是理解业务上下文。

好,日志级别规范这部分就这些。记住:级别用对了,告警才是有营养的,排查问题才能从“瞎猜”变成“有迹可循”。