日志还散落在各台机器上吗
微服务之后,一次请求的日志分散在网关、订单、库存、支付四五个容器里,ssh 上去 grep 的做法很快就不可行了。日志聚合要解决三件事:把日志集中收走、按服务和环境检索、能按 traceId 把一次请求串起来。
2025 年主流还是两条路线:ELK(Elasticsearch + Logstash/Filebeat + Kibana)和 Loki + Grafana。前者功能全、全文检索强,后者轻量、成本低、和指标面板放在一起。
ELK 和 Loki 怎么选
| 维度 | ELK | Loki |
|---|---|---|
| 索引方式 | 全文倒排索引 | 只索引标签,日志正文压缩存储 |
| 查询能力 | 强,支持复杂聚合 | LogQL,适合按标签过滤 + 关键字 |
| 存储成本 | 高 | 低,通常是 ELK 的几分之一 |
| 运维复杂度 | 高,ES 集群要调 | 低,Loki 单机也能跑 |
| 典型场景 | 审计、复杂检索、安全分析 | 应用日志、排障、和指标联动 |
如果团队已经有 Grafana 和 Prometheus,Loki 的边际成本最低;如果要做合规审计、复杂字段聚合,ELK 更合适。也有团队两者并用:应用日志进 Loki,审计日志进 ES。
先让日志变成结构化 JSON
不管后端选哪个,第一步都是让应用输出 JSON。纯文本日志在采集端要靠正则解析,字段一改就崩;JSON 每个字段天然可查:
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>traceId</includeMdcKeyName>
<customFields>{"app":"order-service","env":"prod"}</customFields>
</encoder>
</appender>
依赖是 net.logstash.logback:logstash-logback-encoder。Spring Boot 3.4 开始内置了结构化日志,配置 logging.structured.format.console=ecs 或 logstash 就能输出对应格式,新项目可以优先用原生能力,少引一个依赖。
几个字段建议固定下来:timestamp 用 ISO-8601 带时区、level、logger、traceId、app、env。时间统一用 UTC 存储,展示时再转本地时区,否则跨时区排查会对不上。
采集:Filebeat 还是 Promtail
Filebeat 走 ELK,Promtail 走 Loki,配置思路类似:指定日志路径、打标签、送到后端。
server:
http_listen_port: 9080
positions:
filename: /var/lib/promtail/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: spring-boot
static_configs:
- targets: [localhost]
labels:
app: order-service
env: prod
__path__: /var/log/app/*.log
标签只放低基数字段:app、env、level 可以,traceId、userId、orderNo 绝对不行。Loki 的标签组合会生成独立的数据流,高基数标签会让索引和内存直接失控。这类字段放在日志正文里,用 |= 过滤即可。
容器环境更推荐让应用直接输出到 stdout,由采集器读 /var/log/containers/*.log,避免在容器里写文件再挂卷。K8s 下可以用 DaemonSet 采集,也可以用 sidecar,前者资源利用率更高。
常见坑
坑一:多行堆栈被拆成很多条。 异常栈每行都是一条独立日志,检索时只能看到第一行。要么用 JSON 编码(堆栈作为一个字段),要么在采集端配 multiline,用 firstline 匹配 ^\d{4}-\d{2}-\d{2} 这类时间开头。
坑二:同步 Appender 拖慢业务。 磁盘慢或者采集端阻塞时,日志写入会卡住业务线程。用 AsyncAppender 包一层,设置队列大小和 discardingThreshold,并接受“极端情况下丢日志”的取舍。
坑三:日志量突增打爆后端。 一个循环里的 log.info 就能产生每秒几万条。除了修代码,还要在采集端做限流、在应用端按级别采样,并给 Loki/ES 配 ingestion rate limit。
坑四:采集器重复采集。 Filebeat 的 registry、Promtail 的 positions 文件丢失后,会从头再读一遍,导致重复日志。挂载持久化卷,升级时保留 positions。
坑五:敏感信息进日志。 手机号、身份证、密码、Token 一旦进了 ES,删除成本极高。在 encoder 或采集端做脱敏,别指望“以后再说”。
排查步骤
- 先确认应用本地日志文件或 stdout 有没有输出,排除 Appender 配置问题。
- 看采集器 targets 页面,确认文件被 tail 到、没有报权限错误。
- 在后端按
app标签查最近 5 分钟,确认数据到达;没有就查采集器到后端的网络和认证。 - 用 traceId 搜一条具体请求,确认字段被正确解析,不是全塞在
message里。 - 最后看存储水位和保留策略,很多“查不到旧日志”只是被 ILM 或 retention 删掉了。
评论