2. 日志基础
日志这东西,说白了就是系统的「黑匣子」。API 出问题的时候,你第一个翻的就是它。我见过太多团队,日志写得乱七八糟,出了事故根本没法追溯。今天咱们就把日志基础打扎实。
日志级别:从 DEBUG 到 FATAL
日志级别不是拍脑袋定的。每个级别都有明确的语义,用错了反而添乱。我个人习惯把这五个级别分成三类:调试类、告警类、灾难类。
| 级别 | 含义 | 典型场景 | 生产环境是否开启 |
|---|---|---|---|
| DEBUG | 调试信息 | 变量值、函数入口/出口、SQL 语句 | ❌ 关闭 |
| INFO | 常规信息 | 请求开始/结束、用户登录、配置加载 | ✅ 开启 |
| WARN | 警告 | 重试、降级、配置缺失但使用默认值 | ✅ 开启 |
| ERROR | 错误 | 请求失败、数据库连接超时、业务异常 | ✅ 开启 |
| FATAL | 致命错误 | OOM、磁盘写满、配置严重错误 | ✅ 开启 |
核心原则:生产环境只开 INFO 及以上级别。DEBUG 日志量太大,会拖垮磁盘 IO。我曾经接手过一个项目,生产环境开着 DEBUG 级别,一天产生 200GB 日志,结果磁盘三天就满了——嗯,那场面挺狼狈的。
这里有个容易踩的坑:WARN 和 ERROR 的边界。我见过有人把「请求超时重试」写成 ERROR,其实这应该是 WARN。ERROR 意味着当前请求已经失败,需要人工介入;WARN 只是告诉你「有点不对劲,但系统自己处理了」。你想想看,如果所有异常都打 ERROR,告警系统会把你烦死的。
日志格式标准:别让解析器崩溃
日志格式不统一,是 API 审计的噩梦。我建议所有日志都采用 结构化格式,比如 JSON。为什么?因为机器好解析,人也好读。
一个标准的 API 日志条目,至少包含以下字段:
- timestamp:ISO 8601 格式,带时区
- level:日志级别
- logger:产生日志的类或模块名
- trace_id:请求追踪 ID,用于串联上下游
- message:日志正文
- context:上下文信息(用户 ID、请求路径、耗时等)
举个例子,这是我常用的格式:
{
"timestamp": "2025-03-21T14:32:10.123+08:00",
"level": "ERROR",
"logger": "com.api.gateway.AuthFilter",
"trace_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": " 验证失败",
"context": {
"user_id": "u_10086",
"request_path": "/api/v2/orders",
"method": "POST",
"duration_ms": 45,
"error_detail": "JWT 签名过期"
}
}
我的习惯:所有日志都带上 trace_id。这样你从网关到微服务,再到数据库,一条链路全串起来。排查问题的时候,直接 grep trace_id 就能拿到完整上下文,不用东翻西找。
为什么强调 JSON?因为非结构化日志(比如纯文本)解析起来太痛苦了。我曾经遇到过一个系统,日志格式是 [时间] 级别 - 消息,结果时间格式不统一,有的带毫秒有的不带,解析脚本写了一百多行——后来我直接让开发改成了 JSON。
日志轮转策略:别让磁盘爆炸
日志不轮转,磁盘迟早会满。这不是危言耸听。我见过一个线上事故:日志文件涨到 50GB,直接把磁盘撑爆了,导致整个服务不可用。所以轮转策略必须提前规划好。
常用的轮转策略有三种:
| 策略 | 触发条件 | 适用场景 | 注意事项 |
|---|---|---|---|
| 基于大小 | 文件达到指定大小(如 100MB) | 日志量稳定的系统 | 避免设置过小,频繁轮转影响性能 |
| 基于时间 | 每天/每小时轮转一次 | 日志量波动大的系统 | 配合保留天数使用 |
| 混合策略 | 大小或时间任一条件满足 | 大多数生产系统 | 推荐使用 |
以 Java 的 Logback 为例,一个典型的配置长这样:
<appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/var/log/api-gateway.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>/var/log/api-gateway.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
</appender>
这个配置的意思是:每天一个日志文件,单个文件超过 100MB 就自动切分,保留最近 30 天,总大小不超过 10GB。你想想看,这样既不会丢日志,也不会撑爆磁盘。
避坑指南:我曾经遇到过一个问题——日志轮转后,旧日志文件被删除了,但进程还持有文件句柄,导致磁盘空间没释放。这是因为 Linux 下删除文件只是解除链接,文件句柄还在。解决方案是:轮转后通知进程重新打开日志文件,或者使用 logrotate 的 copytruncate 模式。
知识体系总览
下面这张图把日志基础的核心逻辑串起来了。从日志产生到最终归档,每一步都有讲究。
实战建议:从第一天就规范起来
日志这东西,前期偷懒,后期加倍还债。我建议你从项目第一天就定好规范:
- 统一日志框架:整个团队用同一个日志库,别各写各的
- 日志级别评审:每次代码评审都看一眼日志级别用得对不对
- 轮转策略自动化:用配置中心管理,别手动改服务器配置
- 定期检查磁盘:写个脚本每天检查日志目录大小,超过阈值就告警
一个小技巧:我习惯在日志里加一个 @version 字段。每次改日志格式就升版本号。这样下游的日志解析系统可以根据版本号自动适配,不会因为格式变更导致解析失败。
好了,日志基础就聊到这儿。记住一句话:日志是系统的第一道防线。级别、格式、轮转,这三件事做好了,后面做审计和分析才能事半功倍。