日志基础:日志级别、格式标准与存储位置

日志这东西,说白了就是系统的「黑匣子」。升级失败时,你第一个想到的肯定是翻日志。但日志如果写得乱七八糟,翻也白翻。今天我就把日志的基础掰开揉碎了讲清楚。

日志级别:从 DEBUG 到 FATAL

日志级别定义了信息的严重程度。不同级别对应不同场景,用错了会出大问题。

级别 含义 典型场景 生产环境建议
DEBUG 调试信息 变量值、函数入口/出口、循环次数 关闭
INFO 常规信息 服务启动、请求到达、任务完成 开启
WARN 警告 磁盘使用率超 80%、重试次数过多 开启
ERROR 错误 数据库连接失败、请求超时、业务异常 开启
FATAL 致命错误 内存溢出、配置加载失败、进程崩溃 开启

核心原则:生产环境只开 INFO 及以上级别。DEBUG 日志量太大,会拖垮磁盘 IO。我见过有人把 DEBUG 开在生产环境,结果一天写了 200GB 日志,直接把磁盘撑爆了。

每个级别都有它的「脾气」。我习惯这样用:

  • DEBUG:开发时用,上线前必须关掉。我曾经排查一个内存泄漏,就是靠 DEBUG 日志定位到某个对象没释放。
  • INFO:记录系统正常运转的关键节点。比如「用户登录成功」「订单创建完成」。别什么都打 INFO,否则跟没打一样。
  • WARN:系统还能跑,但需要关注。比如「重试第 3 次成功」。WARN 是预警信号,别忽略它。
  • ERROR:功能受损,但系统没挂。比如「数据库查询超时,返回降级数据」。ERROR 必须带堆栈,否则查不了根因。
  • FATAL:系统扛不住了。比如「无法加载核心配置文件,进程退出」。FATAL 日志一出现,就该立刻拉警报。

避坑指南:我曾经接手过一个项目,所有异常都打 ERROR,连参数校验失败也打 ERROR。结果线上出了真故障,几千条 ERROR 日志里根本找不到关键信息。级别用错了,日志就是噪音。

日志格式标准:统一才能分析

日志格式不统一,分析工具就没法用。你想想看,如果每行日志的字段顺序都不一样,grep 都救不了你。

我推荐的标准格式:

时间戳 | 线程ID | 日志级别 | 类名:行号 | 请求ID | 消息内容 | 异常堆栈

举个例子:

2025-03-21 14:32:18.123 | http-nio-8080-exec-3 | ERROR | com.example.OrderService:156 | req-abc123 | 订单支付超时,订单号:ORD20250321001 | java.net.SocketTimeoutException: Read timed out
    at com.example.OrderService.pay(OrderService.java:160)
    at com.example.OrderController.submit(OrderController.java:45)

为什么这么设计?

  • 时间戳:精确到毫秒,方便定位时间点。我习惯用 yyyy-MM-dd HH:mm:ss.SSS,别用时间戳数字,人眼看不懂。
  • 线程ID:多线程环境下,知道哪个线程出的问题。特别是 Web 应用,一个请求一个线程。
  • 日志级别:一眼看出严重程度。grep ERROR 就能过滤出所有错误。
  • 类名:行号:快速定位代码位置。没有这个,你都不知道去哪修。
  • 请求ID:串联一次请求的全链路日志。这是排查分布式问题的关键。
  • 消息内容:描述发生了什么。要具体,别写「发生异常」这种废话。
  • 异常堆栈:ERROR 和 FATAL 必须带。没有堆栈,你只能猜原因。

小技巧:请求ID 可以用 UUID 或 Snowflake 算法生成。在请求入口处生成,然后通过 MDC(Mapped Diagnostic Context)传递到日志框架。这样一次请求的所有日志都带上同一个 ID,排查起来特别爽。

日志存储位置:放对地方才能找到

日志存哪?这个问题看似简单,但坑不少。我见过有人把日志和程序放在同一个磁盘,结果日志写满,程序直接挂了。

标准做法:

/var/log/<应用名>/
├── app.log          # 当前日志
├── app.log.2025-03-21.gz  # 历史日志(已压缩)
├── error.log        # 只包含 ERROR 和 FATAL
├── access.log       # 访问日志(Web 应用)
└── gc.log           # JVM GC 日志(Java 应用)

几个关键点:

  • 独立磁盘:日志目录单独挂载一块磁盘。系统盘写满了,至少应用还能跑。
  • 按天滚动:每天一个文件,保留 30 天。用 logrotate 或框架自带的滚动策略。
  • 压缩归档:历史日志用 gzip 压缩,能省 80% 空间。我习惯保留 90 天的压缩日志,方便回溯。
  • 分离错误日志:单独一个 error.log 只存 ERROR 和 FATAL。排查问题时直接看这个文件,不用在 INFO 里翻。

避坑指南:我曾经遇到一个案例,日志文件没有做滚动,结果一个文件写了 50GB。用 vim 打开直接卡死,grep 也慢得要命。从那以后,我强制所有项目必须配置日志滚动,最大文件不超过 500MB。

知识体系总览

下面这张图把日志基础的核心知识点串起来了。你可以把它当作一张「日志排查地图」。

日志基础核心知识体系 日志基础核心知识体系 日志级别 日志格式 日志存储 DEBUG INFO WARN ERROR FATAL 时间戳 线程ID 日志级别 类名:行号 请求ID 消息+堆栈 独立磁盘 按天滚动 压缩归档 错误分离 核心原则 1. 生产环境只开 INFO 及以上级别,DEBUG 仅用于开发调试 2. 日志格式必须统一,包含时间戳、线程ID、级别、类名、请求ID、消息 3. 日志存储独立磁盘,按天滚动,保留 30 天以上,错误日志单独分离

这张图把日志级别、格式、存储三个维度串在一起了。你排查升级失败时,按这个框架去检查日志配置,基本不会漏掉关键点。

我的习惯:每次接手一个新系统,我第一件事就是看日志配置。级别对不对?格式全不全?存哪了?滚动策略有没有?这三个问题问完,心里就有底了。升级失败时,日志就是你最可靠的战友。