轻量日志收集实战:Loki 与 ELK 选型落地

当服务从一台机器扩展到十几台容器,再想靠 ssh 登录挨个翻 /var/log 已经不现实。日志收集的本质,是把散落在各处的文本流,汇聚成一条可被统一检索的时间线。本文对比 Loki 与 ELK 两条主流路线,并给出一套用 Docker Compose 十分钟搭起的轻量落地方案,帮你补齐可观测性里”日志”这一环。

一、为什么单台服务器的日志不够用了

日志的价值在于”出问题能回溯、平时能度量”。当系统还是单体时,一台机器一个文件尚可应付;一旦进入容器化或微服务,日志面临三个结构性难题:位置分散(Pod 随时漂移到任意节点)、生命周期短暂(容器删除即消失)、格式混乱(Nginx、应用、K8s 各有各的写法)。不解决收集,事故复盘就只能靠猜。

更关键的是日志与监控的协同。我们在Prometheus + Grafana 监控面板实战中解决了”指标”问题,但指标只能告诉你”QPS 掉了”,日志才能告诉你”因为哪个异常栈”。指标加日志,才是完整的可观测性底座。

二、Loki 与 ELK:两条主流路线的取舍

市面上的日志方案大致归为两派:以 Elasticsearch 为核心的 ELK(Elasticsearch + Logstash + Kibana),和以 Grafana Loki 为代表的”轻量标签派”。它们的根本差异在存储模型——ES 对全文字段建倒排索引,强大但吃资源;Loki 只对标签建索引,把日志原文压缩进对象存储,查询时按标签定位再全文扫描。

维度LokiELK(Elasticsearch)
索引方式仅标签索引,原文压缩存储全文倒排索引
资源占用低,适合中小团队高,堆内存消耗大
查询语言LogQL,类 PromQLKQL / Lucene 语法
生态协同与 Prometheus 天然一体Kibana 独立可视化
适用场景容器日志、运维排障全文检索、合规审计

一句话结论:如果你的诉求是”按服务/环境快速翻日志、和 Prometheus 共用一套面板”,Loki 性价比最高;如果需要对任意字段做复杂全文检索、做长期合规留存,ELK 更稳。下文以 Loki 为主讲实操。

三、Loki 实战:用 Docker Compose 十分钟搭起

Loki 的最小可用组合是三个组件:Loki(存储与查询入口)、Promtail(采集端,装在每台机器)、Grafana(可视化)。下面这份编排文件即可在本机或单节点服务器跑起来。

version: "3.8"
services:
  loki:
    image: grafana/loki:2.9.2
    command: -config.file=/etc/loki/local-config.yaml
    ports:
      - "3100:3100"
    volumes:
      - loki_data:/loki
  promtail:
    image: grafana/promtail:2.9.2
    volumes:
      - /var/log:/var/log:ro
      - ./promtail-config.yaml:/etc/promtail/config.yml:ro
    command: -config.file=/etc/promtail/config.yml
    depends_on:
      - loki
  grafana:
    image: grafana/grafana:10.2.2
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=change_me
    volumes:
      - grafana_data:/var/lib/grafana
    depends_on:
      - loki
volumes:
  loki_data:
  grafana_data:

启动后,在 Grafana 里添加 Loki 数据源(地址填 http://loki:3100),即可在 Explore 页写查询。如果是 K8s 环境,Promtail 用 DaemonSet 部署即可,每台节点自动采集,相关容器编排基础可参考Kubernetes 入门:Pod、Deployment 与 Service

四、Promtail 采集配置:标签决定查询效率

Loki 的设计哲学是”标签即索引”。标签加得合理,查询飞快;标签加得随意(比如把时间戳或用户 ID 当标签),会触发”标签爆炸”把内存撑爆。下面的配置把 jobserviceenv 作为固定标签,再用 pipeline 从日志行里抽出结构化字段。

server:
  http_listen_port: 9080
  grpc_listen_port: 0

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: nginx
    static_configs:
      - targets: [localhost]
        labels:
          job: nginx
          service: web-gateway
          env: prod
          __path__: /var/log/nginx/*.log
    pipeline_stages:
      - match:
          selector: '{job="nginx"}'
          stages:
            - regex:
                expression: '^(?P<ip>\S+) .* "(?P<method>[A-Z]+) (?P<path>\S+)" (?P<status>\d+)'
            - labels:
                status:
                method:

  - job_name: app
    static_configs:
      - targets: [localhost]
        labels:
          job: app
          service: order-svc
          env: prod
          __path__: /opt/app/logs/*.log

注意 pipeline_stages 里的 labels 只会把匹配到的有限枚举(如 HTTP 状态 200/404/500)提升为标签——这些是低基数维度,安全。而像 ip 这种高基数字段,只抽成日志内的结构化字段供全文检索,绝不提升为标签。

五、LogQL:像写 PromQL 一样查日志

LogQL 的语法刻意贴近 PromQL,会写 Prometheus 查询的人几乎零成本上手。基本形态是”标签选择器 + 管道操作符”。下面几个例子覆盖了 80% 的排障场景。

# 1. 筛选某服务全部日志
{service="order-svc", env="prod"}

# 2. 文本包含(不区分大小写)与排除
{service="web-gateway"} |= "timeout"
{service="web-gateway"} != "healthcheck"

# 3. 把 JSON 日志解析成字段,再按字段过滤
{job="app"} | json | level="ERROR" | err_code="DB_504"

# 4. 统计最近 5 分钟 5xx 速率(和 PromQL 同构)
sum by (status) (rate({job="nginx"} | json | status=~"5.." [5m]))

# 5. 错误日志条数折线
count_over_time({service="order-svc", level="ERROR"}[1h])

把第 4 条查询做成 Grafana 面板,就能和 Prometheus 的 QPS、延迟曲线叠在一起看——指标异常时,旁边就是同一时间窗的错误日志,排障闭环就此打通。Nginx 访问日志本身的格式治理,可配合Nginx 限流防刷实战里的访问日志分析一起做。

六、ELK 路线:什么时候它更合适

当需求越过”排障翻日志”,进入”对任意业务字段做全文检索”或”安全合规审计留存数年”时,Elasticsearch 的全文索引优势就显现了。ELK 的采集端用 Filebeat,比 Promtail 更轻,且对各类中间件(MySQL、Redis、Kafka)有现成模块。

# filebeat.yml 关键片段
filebeat.inputs:
  - type: filestream
    id: nginx-access
    paths:
      - /var/log/nginx/access.log
    fields:
      service: web-gateway
      env: prod

output.elasticsearch:
  hosts: ["https://es.internal:9200"]
  username: ${ES_USER}
  password: ${ES_PWD}
  indices:
    - index: "nginx-log-%{+yyyy.MM.dd}"

setup.template.name: "nginx-log"
setup.template.pattern: "nginx-log-*"

Filebeat 用 filestream 输入自动记录读取位点,重启不丢不重;输出直接进 ES 并按天建索引,配合 ILM 生命周期策略滚动清理。如果你的技术栈里已经有 Kafka,还可以让 Filebeat 先发到 Kafka 做削峰,再经 Logstash 落库,这一层消息缓冲的思路在Kafka 入门实战里有更系统的说明。

七、生产避坑清单

日志系统上线后最容易踩的坑,往往不在搭建而在治理。下面几条是反复验证过的经验。

  • 防标签爆炸:只把低基数的环境/服务/状态提升为标签,高基数字段(用户 ID、IP、URL)只做日志内字段,绝不当标签。
  • 设保留期:Loki 配 compactor 保留 30 天,ES 配 ILM 滚动删除,否则磁盘会被历史日志缓慢吃满。
  • 采集端资源上限:Promtail / Filebeat 给足内存上限但别贪大,避免和宿主机业务抢资源。
  • 日志分级:DEBUG 只开发环境开,生产默认 INFO 起,ERROR 单独建索引或告警,减少噪声。
  • 权限与脱敏:日志里常带手机号、Token,采集管道要加脱敏 stage,访问 Grafana 走账号鉴权,安全审计可结合服务器安全加固里的账号与防火墙策略。
  • 告警闭环:用 Grafana 或 ElastAlert 对 ERROR 速率设阈值告警,别让日志系统只用于事后翻查。

日志收集没有银弹,但选型有章法:中小团队、容器环境、要和 Prometheus 协同,Loki 是最省心的起点;全文检索与合规留存需求重,ELK 更稳妥。无论哪条路,标签治理、保留期、脱敏与告警这四件事做到位,日志才会从”出事后才看的文件”变成”平时就在帮你盯系统”的资产。把日志面板接进日常 oncall,排障时间通常能砍掉一大半。

上一篇 JVM 内存模型与 GC 调优:堆结构与回收参数实战
下一篇 MyBatis与JPA性能优化:N+1、批量与缓存实战