第3章:可观测性基石:日志(Logging)规范与最佳实践、结构化日志、日志采集与存储
聊到系统稳定性,我第一个想到的,不是监控大盘,也不是告警规则,而是日志。
你想想看,系统出了故障,你第一反应是什么?大概率是「先看看日志」。日志就是我们排查问题的「黑匣子」。但很多团队的日志,说白了就是一堆「垃圾信息」——格式混乱、内容随意、关键字段缺失。这样的日志,别说排查问题了,连日常运维都费劲。
这一章,我们就来聊聊怎么把日志这个地基打牢。
3.1 日志规范:别让日志变成「噪音」
我见过最夸张的一个项目,单台机器一天产生 200GB 日志。结果呢?90% 都是无意义的 debug 信息,真正出问题的时候,根本找不到关键线索。
所以,日志规范的第一条:明确日志级别。
| 级别 | 使用场景 | 个人建议 |
|---|---|---|
| ERROR | 系统功能不可用,需要立即人工介入 | 必须包含堆栈和上下文 |
| WARN | 预期之外的异常,但系统可自动恢复 | 比如重试失败、降级触发 |
| INFO | 关键业务流程的状态变更 | 比如订单创建、支付回调 |
| DEBUG | 开发调试用,生产环境默认关闭 | 千万别在线上开 DEBUG! |
除了级别,日志内容也要有「规矩」。我个人习惯,每条日志必须包含以下字段:
- 时间戳:精确到毫秒,最好带时区
- 请求追踪 ID:TraceId,用于串联一次请求的全链路
- 类名/方法名:快速定位代码位置
- 关键参数:比如用户 ID、订单号、请求参数
- 异常堆栈:ERROR 级别必须带,别只打个「null」
3.2 结构化日志:让机器能读懂
传统的日志长什么样?
2024-01-15 10:23:45 ERROR 用户下单失败,订单号:123456
这种日志,人眼看看还行。但你要用 ELK 或者 Loki 去检索,就麻烦了。你想想看,你要统计「今天所有下单失败的订单」,难道用正则去匹配吗?
所以,我们要用结构化日志。说白了,就是把日志变成 JSON 格式。
{
"timestamp": "2024-01-15T10:23:45.123+08:00",
"level": "ERROR",
"service": "order-service",
"traceId": "abc123def456",
"userId": 10086,
"orderId": "123456",
"message": "用户下单失败",
"error": {
"type": "InsufficientBalance",
"stack": "com.example.OrderService.createOrder(OrderService.java:123)"
}
}
这样做的好处很明显:
- 检索快:直接按字段查询,比如
level:ERROR AND service:order-service - 聚合方便:统计某个接口的 P99 耗时,直接对 duration 字段做聚合
- 自动解析:日志采集工具(如 Filebeat、Fluentd)可以直接解析 JSON,无需写正则
JsonLayout,一行配置就能搞定,不用自己拼 JSON 字符串。
3.3 日志采集:别让日志「堵」在磁盘上
日志写好了,怎么收集到中央存储?这里有几个关键点。
第一,采集方式的选择。
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Agent 采集(如 Filebeat) | 轻量、稳定、资源占用低 | 需要部署 Agent | 大多数场景,推荐首选 |
| SDK 直推(如 gRPC) | 实时性高、无磁盘 I/O | 侵入性强、可能影响业务性能 | 对实时性要求极高的场景 |
| 日志轮转 + 定时上传 | 实现简单 | 延迟高、容易丢日志 | 非关键系统、临时方案 |
我个人习惯用 Filebeat。它有个好处:背压机制。当下游(比如 Kafka 或 Elasticsearch)处理不过来时,Filebeat 会自动降速,不会把业务进程拖垮。
第二,注意日志轮转。
我曾经见过一个事故:日志文件把磁盘写满了,导致业务进程直接挂掉。嗯,这其实是个低级错误。
解决方案很简单:配置日志轮转。比如 Logback 的 SizeAndTimeBasedRollingPolicy:
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>/var/log/myapp.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>7</maxHistory>
<totalSizeCap>2GB</totalSizeCap>
</rollingPolicy>
</appender>
这个配置的意思是:每天一个文件,单个文件超过 100MB 就切分,保留最近 7 天,总大小不超过 2GB。够用了吧?
3.4 日志存储:选对工具,事半功倍
日志存到哪里?这取决于你的场景。
- Elasticsearch:全文检索能力强,适合排查问题。但写入压力大时,容易丢数据。
- Loki:轻量、成本低,适合 Kubernetes 环境。但聚合分析能力弱。
- ClickHouse:写入性能极强,适合做日志分析。但查询语法复杂。
- S3 + Athena:成本最低,适合冷数据归档。但查询延迟高。
我现在的团队,用的是「热温冷」分层存储方案:
- 热数据(最近 3 天):存 Elasticsearch,用于实时排查
- 温数据(3-30 天):存 ClickHouse,用于聚合分析
- 冷数据(30 天以上):压缩后存 S3,偶尔审计用
核心观点: 日志不是「存得越多越好」,而是「查得越快越好」。你存了 100TB 日志,但查一次要 10 分钟,那跟没存有什么区别?
3.5 避坑指南:我踩过的那些坑
最后,分享几个我亲身经历过的教训:
- 别在日志里打印敏感信息:比如密码、身份证号。我曾经见过一个项目,日志里直接打印了用户的银行卡号。嗯,后来被安全团队约谈了。
- 注意日志的异步写入:同步写日志会阻塞业务线程。用 Logback 的
AsyncAppender可以解决。 - 日志采集不要用 NFS:NFS 的 I/O 性能很差,容易导致日志丢失。用本地磁盘 + Agent 采集更靠谱。
- 别忘了日志的监控:比如 ERROR 日志的告警、日志采集延迟的告警。我见过一个系统,日志采集挂了 3 天都没人发现。
好了,这一章的内容就到这里。日志这件事,看似简单,但做扎实了,能让你在故障排查时省下大量时间。下一章,我们来聊聊指标(Metrics)——另一个可观测性的核心支柱。