第2章:日志系统基础
日志这东西,说白了就是系统的「黑匣子」。我刚开始做运维那会儿,总觉得日志就是出问题了才去看一眼。后来被线上故障狠狠教育过几次,才明白——日志系统设计得好不好,直接决定了你排查故障的速度。
这一章,咱们就聊聊日志系统的三个核心问题:日志该怎么分级、格式怎么写才规范、以及怎么把分散的日志集中起来管理。
2.1 日志级别:DEBUG / INFO / WARN / ERROR
日志级别不是随便定的。每个级别都有它明确的用途。我个人习惯把这四个级别分成两类:调试用和监控用。
| 级别 | 含义 | 生产环境建议 | 典型场景 |
|---|---|---|---|
| DEBUG | 调试信息 | 关闭 | 变量值、函数调用栈、SQL语句 |
| INFO | 正常流程信息 | 开启 | 服务启动、请求到达、任务完成 |
| WARN | 潜在问题 | 开启 | 重试、降级、配置异常 |
| ERROR | 错误/异常 | 开启 | 数据库连接失败、接口超时、业务异常 |
核心原则:生产环境只开 INFO 及以上级别。DEBUG 日志量太大,会拖垮磁盘 IO。我见过一个团队把 DEBUG 开在生产环境,结果一天写了 200GB 日志,直接把磁盘打满了。
这里有个坑,我踩过好几次——WARN 和 ERROR 的边界。很多人把「重试」也打成了 ERROR,结果告警满天飞。我的经验是:影响用户操作的,打 ERROR;不影响用户操作的,打 WARN。比如接口第一次调用超时,自动重试成功,这就是 WARN。重试三次都失败,那才是 ERROR。
小技巧:在代码里统一封装一个日志工具类,强制要求调用时传入「业务场景」参数。这样后期做日志分析时,能快速定位到某个业务模块的日志。
2.2 日志格式规范
日志格式不统一,排查故障时你会想骂人。我接手过一个老项目,有的日志用逗号分隔,有的用竖线,有的干脆是自由文本。那次排查一个支付超时问题,我花了三个小时才把日志拼起来。
所以,我强烈建议团队统一使用 JSON 格式。为什么?因为 JSON 结构清晰,解析方便,ELK 和 Loki 都能直接识别。
一个标准的日志格式,至少包含以下字段:
- timestamp:时间戳,精确到毫秒,带时区
- level:日志级别
- logger:类名或模块名
- message:日志正文
- trace_id:链路追踪 ID(这个很重要,后面会细讲)
- thread:线程名(Java 环境)
举个例子,这是我常用的日志格式:
{
"timestamp": "2025-03-21T14:30:22.123+08:00",
"level": "ERROR",
"logger": "com.example.order.OrderService",
"message": "订单支付失败,订单号: ORD20250321001",
"trace_id": "a1b2c3d4e5f6",
"thread": "http-nio-8080-exec-3",
"extra": {
"order_id": "ORD20250321001",
"amount": 299.00,
"error_code": "PAY_TIMEOUT"
}
}
注意:千万不要把敏感信息写进日志!比如用户密码、身份证号、银行卡号。我曾经见过一个同事把完整的用户信息打到了日志里,结果被安全部门通报了。建议在日志输出前做脱敏处理。
另外,日志里一定要带 trace_id。你想想看,一个请求经过网关、订单服务、支付服务、通知服务,如果没有 trace_id,你怎么把这一串日志串起来?有了 trace_id,在 ELK 里一搜,整个请求的完整链路就出来了。
2.3 集中式日志收集:ELK 与 Loki
日志分散在各台机器上,排查故障时你得一台一台登录去看。这效率,说实话太低了。集中式日志收集就是为了解决这个问题。
目前主流的方案有两个:ELK 和 Loki。我两个都用过,说说我的感受。
2.3.1 ELK 栈
ELK 是 Elasticsearch + Logstash + Kibana 的简称。它的工作流程是这样的:
- Logstash 从各台机器采集日志
- 对日志进行解析、过滤、格式化
- 写入 Elasticsearch 做索引和存储
- 通过 Kibana 做可视化和搜索
ELK 的优点很明显:功能强大,搜索速度快,支持复杂的聚合分析。但缺点也很突出——太重了。Elasticsearch 本身就很吃内存,Logstash 也消耗资源。小团队用 ELK,光维护成本就够呛。
我记得有一次帮一个创业公司搭 ELK,他们总共就 5 台服务器,结果 ELK 集群占了 3 台。老板看了直摇头。
2.3.2 Loki
Loki 是 Grafana 团队推出的日志系统。它的设计理念和 ELK 完全不同——不索引日志内容,只索引元数据。说白了,它把日志当成普通的文本文件来存,只给日志打上标签(比如服务名、主机名、级别)。
这样做的好处是:省资源。Loki 的存储成本只有 ELK 的十分之一左右。而且它和 Prometheus、Grafana 天然集成,你可以在同一个 Grafana 面板上同时看指标和日志。
但 Loki 也有短板:全文搜索能力弱。如果你想搜「订单号 ORD20250321001 在哪些日志里出现过」,Loki 的响应速度会比 ELK 慢不少。
| 对比项 | ELK | Loki |
|---|---|---|
| 资源消耗 | 高 | 低 |
| 搜索速度 | 快(全文索引) | 中等(元数据索引) |
| 存储成本 | 高 | 低 |
| 与 Prometheus 集成 | 一般 | 原生支持 |
| 适用场景 | 大型企业、复杂查询 | 中小团队、Kubernetes 环境 |
我的建议:如果你团队规模不大(10 台服务器以内),或者主要跑在 Kubernetes 上,优先选 Loki。如果你需要做复杂的日志分析、安全审计,或者团队有专职的 ES 运维,那就选 ELK。
2.4 实战:日志采集配置示例
光说不练假把式。我给大家看一个 Loki 的采集配置,用的是 Promtail(Loki 的日志采集器)。
# promtail.yaml
scrape_configs:
- job_name: 'application-logs'
static_configs:
- targets: ['localhost']
labels:
job: 'order-service'
__path__: /var/log/order-service/*.log
pipeline_stages:
- json:
expressions:
level: level
trace_id: trace_id
message: message
- labels:
level:
trace_id:
这个配置做了三件事:
- 采集
/var/log/order-service/目录下所有.log文件 - 给日志打上
job=order-service的标签 - 从 JSON 日志中提取
level和trace_id作为额外的标签
配置好之后,在 Grafana 里就能直接搜了。比如我想看 order-service 最近一小时的 ERROR 日志,直接在查询框里输入:
{job="order-service"} |= "ERROR"
嗯,就是这么简单。
避坑指南:我曾经遇到过一个问题——日志采集器把日志文件删了。原因是 Promtail 默认会清理已经采集过的日志文件。如果你需要保留原始日志,记得在配置里加上 keep_file: true。
2.5 本章小结
日志系统是故障排查的基石。这一章我们聊了三个要点:
- 日志级别:DEBUG 用于开发,INFO 用于记录流程,WARN 用于预警,ERROR 用于告警。别混用。
- 日志格式:统一用 JSON,带上 timestamp、level、trace_id 等关键字段。敏感信息要脱敏。
- 集中式收集:ELK 功能强但资源消耗大,Loki 轻量但搜索能力弱。根据团队规模选型。
下一章,咱们会深入聊 链路追踪。到时候你会看到,trace_id 这个小小的字段,在分布式系统里能发挥多大的作用。