ElIauk

日志聚合 ELK/Loki 入门

日志还散落在各台机器上吗 微服务之后,一次请求的日志分散在网关、订单、库存、支付四五个容器里,ssh 上去 grep 的做法很快就不可行了。日志聚合要解决三件事:把日志集中收走、按服务和环境检索、能按 traceId 把一次请求串起来。 2025 年主流还是两条路线:ELK(Elasticsearc

评论
日志聚合 ELK/Loki 入门

日志还散落在各台机器上吗

微服务之后,一次请求的日志分散在网关、订单、库存、支付四五个容器里,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 或采集端做脱敏,别指望“以后再说”。

排查步骤

  1. 先确认应用本地日志文件或 stdout 有没有输出,排除 Appender 配置问题。
  2. 看采集器 targets 页面,确认文件被 tail 到、没有报权限错误。
  3. 在后端按 app 标签查最近 5 分钟,确认数据到达;没有就查采集器到后端的网络和认证。
  4. 用 traceId 搜一条具体请求,确认字段被正确解析,不是全塞在 message 里。
  5. 最后看存储水位和保留策略,很多“查不到旧日志”只是被 ILM 或 retention 删掉了。

参考

评论

推荐阅读

订阅邮件

订阅更新,及时收到最新文章。

作者