日志系统概述:结构、级别与格式归纳总结
说起日志,很多新手觉得不就是打印点信息出来嘛。其实没那么简单。一个设计良好的日志系统,就像是飞机的黑匣子。出事之后,它是你还原现场的唯一依据。我在排查线上故障时,百分之八十的时间都在跟日志打交道。如果日志本身一团糟,那排查效率就低得吓人。
这一章,我们就把日志系统的几个核心概念掰开揉碎。搞懂结构、级别和格式,你写出来的日志才算“可用的日志”。
核心观点: 日志的价值不在于“记录了”,而在于“能看懂、能搜索、能关联”。
一、日志结构:一条完整的日志长什么样?
一条日志,说白了就是一条包含上下文信息的记录。我见过很多团队,日志就写个字符串,比如“登录失败”。这种日志等于白写。出了事你根本不知道是哪个用户、什么时候、在哪台机器上登录失败。
一条高质量的日志,至少应该包含以下几部分:
| 字段 | 说明 | 典型示例 |
|---|---|---|
| 时间戳 | 精确到毫秒,包含时区 | 2025-03-18 14:23:45.678 +0800 |
| 日志级别 | 标记严重程度 | ERROR |
| 线程/进程ID | 区分并发请求 | thread-12 |
| 模块/类名 | 标明来源 | com.payment.PaymentService |
| 请求追踪ID | 关联上下游调用链路 | traceId=abc123def456 |
| 消息体 | 具体的事件描述 | 用户 9527 支付失败,余额不足 |
小建议: 我个人习惯在每个日志里都带上 traceId。这样哪怕服务跨了十个节点,也能把一次请求的日志串起来看。
再来看一个反面例子。你写出来的代码,别出现这种日志:
// ❌ 反面教材
log.info("连接超时");
// ✅ 正确示范
log.info("[DBConnectionPool] [pool-1] [traceId=xyz789] 连接超时,超时时间=5000ms,目标地址=jdbc:mysql://10.0.0.1:3306/orders");
二、日志级别:用对级别,别让ERROR变废话
日志级别可能是被用得最乱的东西。很多团队什么异常都打ERROR,结果就是ERROR日志一天上百万条,没人看。
我记得在维护一个老系统时,发现有人用 logger.error("用户登录") 来记录正常登录。我当时就愣住了——这到底是成功还是失败?
级别定义了事件的严重程度和紧急程度。一般分以下五级:
- DEBUG — 开发调试用。上线后直接关掉,别带到生产环境。
- INFO — 系统正常运行的关键节点。比如“订单创建成功”、“支付回调已接收”。
- WARN — 出问题了但系统能扛住。比如“重试第3次”、“缓存未命中”。
- ERROR — 功能受损,但系统还活着。比如“支付接口返回500”、“数据库写入失败”。
- FATAL — 系统要挂了。比如“内存溢出”、“主从同步断开”。遇到FATAL你就该爬起来看日志了。
避坑指南: 我曾经遇到一个项目,整个代码里只有两种级别:INFO和ERROR。DEBUG和WARN压根没人用。排查问题时,INFO日志不完整,ERROR日志又全是噪音。最后重构日志系统花了整整两周时间。所以一开始就要定好级别使用规范。
关于级别的配置,生产环境常见的做法是这样:
// 生产环境一般只开 WARN 及以上
rootLogger.level = WARN
// 特定模块可以单独开级别
logger.com.payment.level = INFO
logger.com.debug.feature.level = DEBUG
// 日志输出位置也要分级别
appender.file.error = /var/log/app/error.log // 只写 ERROR + FATAL
appender.file.all = /var/log/app/app.log // 写 WARN 及以上
三、日志格式:机器能读懂的日志才是好日志
很多人写日志只考虑人能不能看懂,忽略了机器能不能解析。现在日志量早就不是人工翻文件能处理的了。你得上ELK、得上Splunk、得上日志平台。如果你的日志格式乱七八糟,这些工具根本没法用。
日志格式可以分成三类:
| 格式类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 纯文本 | 人类易读,开发简单 | 机器解析困难,搜索效率低 | 小型服务,临时调试 |
| JSON | 结构化,机器友好,扩展性强 | 占用空间稍大,早期加载较慢 | 微服务、云原生、日志平台接入 |
| 二进制 | 空间小、性能极高 | 不可读,需要专用工具 | 嵌入式系统、高性能中间件 |
我推荐的做法: 新的微服务项目,别想太多,直接用JSON格式。为什么?因为日志平台(如ELK)原生支持JSON解析。你只要把字段名定好,Kibana里直接就能搜、能聚合、能画图。
JSON格式的日志长这样:
{
"timestamp": "2025-03-18T14:23:45.678+0800",
"level": "WARN",
"thread": "http-nio-8080-exec-3",
"logger": "com.gateway.RateLimiter",
"traceId": "abc123def456",
"message": "接口 /api/v1/orders 触发限流",
"context": {
"currentQPS": 350,
"limitQPS": 300,
"clientIP": "192.168.1.100"
}
}
总结一下: 日志的级别、结构和格式是一个整体。级别帮你筛掉噪音,结构帮你找到关联,格式帮你喂给机器。三者配合好了,你才能从海量数据里快速定位问题。
顺便提一句,如果你的日志是纯文本格式,那也没关系。但建议你用统一的模式,比如 [级别] [模块] [traceId] 消息内容。这样至少正则表达式还能匹配得上。
嗯,日志系统的基础概念就先聊到这儿。这些知识点看着简单,但真要落地到项目里,每一步都有坑。后面几章我们会对每个部分展开深入讲,包括怎么设计日志策略、怎么避免性能损耗等等。