1. 日志基础:日志的作用、日志级别与格式规范

做后端开发或者系统运维的兄弟,应该都经历过这种场景:线上出问题了,用户反馈说「系统卡死了」「数据不对」,你第一反应是什么?

我个人的第一反应永远是——先看日志。

日志这东西,平时你可能觉得它不起眼,甚至觉得它占磁盘、拖性能。但真到了排查问题的时候,它就是你的「黑匣子」。没有日志,你就像蒙着眼睛在迷宫里找出口。

这一章,我们就来把日志的基础打扎实。说白了,就是搞清楚三件事:日志到底有什么用、日志级别怎么选、日志格式怎么写才规范。

1.1 日志的作用:不只是「记流水账」

很多人以为日志就是「把程序运行过程记下来」。嗯,这话没错,但太浅了。我做了这么多年项目,总结下来,日志的核心作用其实就四个:

  • 问题定位:线上出故障了,日志是第一手证据。哪行代码报错?参数是什么?调用链是怎样的?全在日志里。
  • 行为审计:谁在什么时间做了什么操作?尤其是金融、电商类系统,日志就是合规的生命线。
  • 性能分析:接口响应慢?看看日志里的耗时记录,瓶颈在哪一目了然。
  • 监控告警:日志里出现大量 ERROR,监控系统立刻报警,比用户投诉快得多。

我的经验之谈:有一次线上支付接口超时,用户疯狂投诉。我查了半小时代码没找到原因,最后翻了日志才发现——第三方支付网关的证书过期了。如果没有日志,这种问题你根本无从下手。

1.2 日志级别:选对了,事半功倍

日志级别这个东西,很多新手容易搞混。我见过有人把所有日志都打成 INFO,也见过有人把 DEBUG 日志直接带到生产环境。这两种做法,都会让你在排查问题时非常痛苦。

常见的日志级别,从低到高,一共五个:

级别 含义 使用场景 生产环境建议
DEBUG 调试信息 开发阶段,打印变量值、流程分支、函数调用细节 关闭
INFO 一般信息 记录关键业务流程、服务启动/停止、请求入口/出口 开启
WARN 警告 潜在问题但不影响当前运行,如配置缺失使用默认值、重试机制触发 开启
ERROR 错误 当前操作失败,但系统还能继续运行,如数据库连接失败、第三方接口返回异常 开启
FATAL 致命错误 系统无法继续运行,如内存溢出、核心配置加载失败 开启

一个小建议:我个人习惯在开发环境开 DEBUG,测试环境开 INFO,生产环境只开 WARN 及以上。但如果你在排查线上问题,可以临时把某个模块的日志级别降到 DEBUG,问题解决后立刻改回来。千万别忘了改回去,否则日志量会爆炸。

1.3 日志级别选择原则:别乱用

级别选错了,后果很严重。我举个例子:

  • 把业务异常打成 FATAL?系统会误以为要挂了,触发不必要的告警和重启。
  • 把关键错误打成 DEBUG?生产环境默认不输出,问题根本看不到。
  • 把所有信息都打成 INFO?日志量巨大,真正有用的信息被淹没了。

怎么选?记住这个原则:

  • DEBUG:开发人员自己看,别人不需要知道。
  • INFO:运维和业务人员需要知道「系统在正常运行」。
  • WARN:系统还能跑,但你可能需要关注一下。
  • ERROR:这次操作失败了,但系统没挂。
  • FATAL:系统要挂了,赶紧来人。

避坑指南:我曾经在一个项目中,看到同事把「用户输入了非法参数」这种校验异常打成了 FATAL。结果监控系统每分钟告警一次,运维团队差点崩溃。记住,FATAL 只留给「系统无法恢复」的场景,比如 OOM、配置加载失败。

1.4 日志格式规范:统一才能高效

日志格式这件事,团队里如果没有统一规范,那排查问题的时候就是一场灾难。我见过有的日志只有一句话「error occurred」,连时间戳都没有——这种日志等于没写。

一个规范的日志格式,至少应该包含以下字段:

字段 说明 示例
时间戳 精确到毫秒,带时区 2025-01-15 14:23:45.678 +0800
日志级别 固定宽度,左对齐 [ERROR]
线程名 方便追踪并发问题 [http-nio-8080-exec-3]
类名/方法名 快速定位代码位置 [com.example.OrderService.createOrder]
请求标识 Trace ID 或 Request ID,串联调用链 [traceId=abc123]
消息内容 清晰描述发生了什么 订单创建失败,用户ID=1001,商品ID=2002
异常堆栈 ERROR/FATAL 级别必须附带 java.lang.NullPointerException: ...

一个标准的日志行,看起来应该是这样的:

2025-01-15 14:23:45.678 +0800 [ERROR] [http-nio-8080-exec-3] [com.example.OrderService.createOrder] [traceId=abc123] 订单创建失败,用户ID=1001,商品ID=2002
java.lang.NullPointerException: 库存服务返回为空
    at com.example.OrderService.createOrder(OrderService.java:45)
    at com.example.OrderController.submit(OrderController.java:22)
    ...

我的习惯:我会在日志格式里强制加上 Trace ID。这样,一个请求从入口到出口的所有日志,都能通过同一个 ID 串联起来。配合 ELK 这类日志平台,搜索 traceId=abc123,整个调用链一目了然。这个习惯,在微服务架构里尤其重要。

1.5 日志知识体系总览

下面这张图,把本章的核心知识点串起来了。你可以把它当作一个「日志基础速查表」:

日志基础核心知识体系 日志基础核心知识体系 日志基础 日志的作用 问题定位 行为审计 性能分析 监控告警 日志级别 DEBUG - 调试信息 INFO - 一般信息 WARN - 警告 ERROR - 错误 FATAL - 致命错误 日志格式规范 时间戳(毫秒+时区) 日志级别 线程名 类名/方法名 Trace ID / 消息内容 核心原则:统一格式 + 合理级别 + 关键信息完整 = 高效排障

1.6 本章小结

日志这东西,说简单也简单,说复杂也复杂。但只要你把这三件事做好,就已经超过了 80% 的团队:

  • 明确日志的作用:不只是记录,更是为了定位、审计、分析和监控。
  • 选对日志级别:别乱用,别滥用。DEBUG 给开发看,ERROR 给运维看,FATAL 给值班电话看。
  • 统一日志格式:时间戳、级别、线程、类名、Trace ID、消息内容,一个都不能少。