3. 日志级别设计:DEBUG、INFO、WARN、ERROR、FATAL 的定义与使用场景、级别动态调整
日志级别这东西,说白了就是给日志信息贴个标签。
告诉系统:这条消息有多重要?该不该记?该不该报警?
我见过不少团队,一开始图省事,所有日志全用 INFO。结果线上出问题,日志文件几分钟就撑爆磁盘。嗯,这就是典型的「级别设计没做好」。
3.1 五大日志级别的定义
咱们从最轻到最重,一个一个说。
| 级别 | 数值 | 含义 | 典型颜色 |
|---|---|---|---|
| DEBUG | 0 | 调试信息,开发阶段用 | 灰色 |
| INFO | 1 | 正常运行的状态记录 | 绿色 |
| WARN | 2 | 潜在问题,不影响运行 | 黄色 |
| ERROR | 3 | 功能异常,需要关注 | 红色 |
| FATAL | 4 | 致命错误,进程可能退出 | 深红/紫红 |
3.2 各级别的使用场景
DEBUG
我个人习惯,DEBUG 日志只出现在开发环境和测试环境。
比如:
- 函数的入参和返回值
- 循环中的中间变量
- 条件分支的走向
线上环境开 DEBUG?我劝你别这么干。我曾经在一个高并发服务上误开了 DEBUG 级别,结果日志量暴涨 100 倍,磁盘 IO 直接打满。那叫一个惨。
INFO
INFO 是「正常业务轨迹」的记录。
比如:
- 用户登录成功
- 订单创建完成
- 定时任务执行结束
你想想看,如果线上出了故障,你第一件事就是翻 INFO 日志,看请求走到哪一步了。所以 INFO 要写得「可读性强」,别整一堆机器码。
WARN
WARN 是个容易被忽略的级别。
它表示「系统还能跑,但有点不对劲」。
举个例子:
- 接口响应时间超过 1 秒(但没超时)
- 重试了 2 次才成功
- 配置项使用了默认值
我建议把 WARN 当成「预警信号」。别等到 ERROR 才慌,WARN 就是给你提前打预防针的。
ERROR
ERROR 意味着「某个功能挂了」。
比如:
- 数据库连接失败
- 第三方接口返回 500
- 参数校验不通过
这里有个坑:ERROR 日志一定要带上上下文。光写一句「查询失败」有什么用?你得告诉我:查的哪个表?参数是什么?异常堆栈呢?
FATAL
FATAL 是最严重的级别。系统已经无法继续运行了。
比如:
- 内存溢出
- 配置文件加载失败
- 核心依赖服务不可用
FATAL 日志通常伴随着进程退出。所以,不要滥用 FATAL。我见过有人把「用户输入错误」也记成 FATAL,这明显不合适。
3.3 级别动态调整
为什么需要动态调整?
你想想看:线上跑得好好的,突然某个接口报错了。你想看 DEBUG 日志,但当时没开。重启改配置?太慢了。等下次复现?不知道什么时候。
所以,运行时动态调整日志级别 是运维的必备能力。
实现方式
常见的有三种:
- API 接口:暴露一个 HTTP 端点,比如
POST /loglevel?level=DEBUG - 配置中心:通过 Apollo、Nacos 等动态推送
- 信号机制:比如给进程发 SIGUSR1 信号切换级别
我个人最推荐 API 方式。简单、直接、可控。
代码示例(Python)
import logging
from flask import Flask, request
app = Flask(__name__)
logger = logging.getLogger("my_app")
@app.route("/loglevel", methods=["POST"])
def set_log_level():
level = request.json.get("level", "").upper()
if level in ["DEBUG", "INFO", "WARN", "ERROR", "FATAL"]:
logger.setLevel(getattr(logging, level))
return {"status": "ok", "level": level}
return {"status": "error", "msg": "invalid level"}, 400
if __name__ == "__main__":
app.run(port=8080)
代码示例(Go)
package main
import (
"encoding/json"
"log"
"net/http"
"strings"
)
var logLevel = "INFO"
func setLogLevel(w http.ResponseWriter, r *http.Request) {
var req struct {
Level string `json:"level"`
}
json.NewDecoder(r.Body).Decode(&req)
level := strings.ToUpper(req.Level)
switch level {
case "DEBUG", "INFO", "WARN", "ERROR", "FATAL":
logLevel = level
w.Write([]byte(`{"status":"ok"}`))
default:
w.WriteHeader(400)
w.Write([]byte(`{"status":"error"}`))
}
}
func main() {
http.HandleFunc("/loglevel", setLogLevel)
log.Fatal(http.ListenAndServe(":8080", nil))
}
3.4 级别设计的避坑指南
- 别把敏感信息写进日志。密码、身份证号、银行卡号,这些不能出现在任何级别里。
- 别在循环里打 DEBUG。尤其是 for 循环,一次请求可能打几万条日志。
- ERROR 和 FATAL 要配报警。光记日志没人看,等于白记。
- 级别要分层。不同模块可以设置不同的级别。比如核心交易模块用 INFO,非核心模块用 WARN。
我曾经接手过一个项目,所有日志全是 ERROR。你猜怎么着?大家看多了,反而对 ERROR 麻木了。真正出问题的时候,没人当回事。
所以,级别不是越严重越好,而是越准确越好。
3.5 小结
日志级别设计,其实就三句话:
- DEBUG 给开发看,INFO 给运维看,WARN 给预警看,ERROR 给人看,FATAL 给机器看。
- 级别要能动态调,别让重启成为唯一手段。
- 别滥用,别乱用,别不分类。
下一章,咱们聊聊日志格式的设计。嗯,那也是个容易踩坑的地方。