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 日志知识体系总览
下面这张图,把本章的核心知识点串起来了。你可以把它当作一个「日志基础速查表」:
1.6 本章小结
日志这东西,说简单也简单,说复杂也复杂。但只要你把这三件事做好,就已经超过了 80% 的团队:
- 明确日志的作用:不只是记录,更是为了定位、审计、分析和监控。
- 选对日志级别:别乱用,别滥用。DEBUG 给开发看,ERROR 给运维看,FATAL 给值班电话看。
- 统一日志格式:时间戳、级别、线程、类名、Trace ID、消息内容,一个都不能少。