4、日志分析平台:ELK/EFK 架构搭建、日志采集与清洗、日志关联分析

日志这东西,平时没人搭理它,一出问题它就是救命稻草。我在一线摸爬滚打这些年,见过太多系统崩了却找不到根因的惨案。说白了,没有一套好用的日志平台,你就是在黑灯瞎火里修机器。

今天咱们聊聊 ELK 和 EFK。这两个东西本质是一回事,只是把 Elasticsearch 和 Kibana 中间的 Logstash 换成了 Fluentd。我个人更偏爱 EFK,因为 Fluentd 的资源占用更轻,在容器化环境里特别香。

4.1 架构选型:ELK 还是 EFK?

先看一张对比表,你心里就有数了:

对比项 ELK (Logstash) EFK (Fluentd)
资源消耗 较高,Java 堆内存大户 较低,Ruby + C 插件
插件生态 丰富,但配置复杂 极丰富,社区活跃
数据吞吐 单机 3000~5000 条/秒 单机 10000+ 条/秒
配置难度 中等,需懂 Grok 正则 简单,YAML 配置
容器化友好度 一般,镜像较大 优秀,官方 DaemonSet 支持

我建议:如果你团队里 Java 功底扎实,选 ELK 没问题。但如果你搞微服务、K8s,直接上 EFK,省心太多。

4.2 日志采集与清洗

采集是第一步,也是最容易翻车的一步。我曾经见过一个项目,日志全量采集,结果 ES 集群直接被打挂。嗯,这里要注意:一定要做限流和过滤

4.2.1 Filebeat 轻量采集

我个人习惯用 Filebeat 做边缘采集,它只负责读文件、发数据,不干别的。配置很简单:

filebeat.inputs:
- type: log
  enabled: true
  paths:
    - /var/log/app/*.log
  multiline.pattern: '^\d{4}-\d{2}-\d{2}'
  multiline.negate: true
  multiline.match: after

output.elasticsearch:
  hosts: ["http://es-cluster:9200"]
  index: "app-logs-%{+yyyy.MM.dd}"

看到那个 multiline 配置了吗?这是处理 Java 异常堆栈的关键。没有它,一条堆栈会被拆成几十条日志,你根本没法关联分析。

4.2.2 Logstash 数据清洗

数据到了 Logstash,就该做清洗了。我常用的套路是:

  • 删除无用字段:比如 beat.hostname@version,省空间也省性能
  • 时间戳标准化:把各种奇葩时间格式统一成 ISO8601
  • IP 地理信息:用 GeoIP 插件把 IP 转成经纬度

举个例子,把 Nginx 日志解析成结构化数据:

filter {
  grok {
    match => { "message" => "%{COMBINEDAPACHELOG}" }
  }
  date {
    match => [ "timestamp", "dd/MMM/yyyy:HH:mm:ss Z" ]
    target => "@timestamp"
  }
  geoip {
    source => "clientip"
    target => "geo"
  }
  mutate {
    remove_field => ["message", "beat", "tags"]
  }
}
小技巧:Grok 调试很痛苦。我一般先在 Kibana 的 Grok Debugger 里调好正则,再写到配置文件里。别在生产环境上试,你会后悔的。

4.3 日志关联分析

日志采集清洗完了,怎么用起来?这才是重头戏。我把它分成三个层次:

4.3.1 单维度检索

最简单的用法。比如查某个用户 ID 的所有操作记录:

GET /app-logs-2024.01.15/_search
{
  "query": {
    "term": {
      "user_id.keyword": "u_123456"
    }
  }
}

这招在排查用户反馈问题时特别管用。我经常在 Kibana 的 Discover 页面直接搜,几秒钟就能定位到问题。

4.3.2 多维度聚合

单条日志没意义,聚合起来才能看到全貌。比如统计每个 API 的 P99 延迟:

GET /app-logs-*/_search
{
  "size": 0,
  "aggs": {
    "by_api": {
      "terms": { "field": "api_path.keyword", "size": 50 },
      "aggs": {
        "p99": {
          "percentiles": {
            "field": "response_time",
            "percents": [99]
          }
        }
      }
    }
  }
}

核心思路:日志关联分析的本质,就是把散落在不同服务、不同时间点的日志,通过 trace_id、user_id、order_id 等关联字段串起来。没有关联字段,日志就是一堆废纸。

4.3.3 跨服务链路追踪

这是高阶玩法。微服务架构下,一个请求会经过 A→B→C→D 四个服务。如果每个服务各自打日志,你根本不知道问题出在哪一环。

我建议的做法是:

  1. 在网关层生成全局唯一的 trace_id
  2. 通过 HTTP Header 或 RPC 上下文透传给下游
  3. 所有服务打印日志时都带上这个 trace_id
  4. 在 Kibana 里直接搜 trace_id,就能看到完整的调用链

我曾经帮一个电商团队排查下单超时问题。他们日志里没有 trace_id,我花了整整两天才手工拼出调用链。后来加了 trace_id,同样的排查只需要 5 分钟。

4.4 避坑指南

我曾经踩过的坑,你千万别再踩:

  • 索引分片数别设太多:每个分片都是 ES 的一个 Lucene 实例,分片太多会导致集群脑裂。我一般一个索引设 3~5 个分片。
  • 日志级别别全开 DEBUG:生产环境开 DEBUG 日志,ES 磁盘一天就写满。我建议只保留 WARN 和 ERROR,DEBUG 日志按需开启。
  • 别把日志当数据库用:ES 不是关系型数据库,别做复杂的 JOIN 查询。日志就是日志,查完就删,别想着永久保存。
  • 冷热数据分离:近 7 天的日志放 SSD 热节点,7 天前的放 HDD 冷节点。我见过有人把所有数据放 SSD,一个月后磁盘 IO 直接打满。

4.5 总结

日志分析平台这事,说难不难,说简单也不简单。核心就三件事:采得到、洗得净、查得快

采得到靠 Filebeat 或 Fluentd,洗得净靠 Logstash 的 Grok 和 mutate,查得快靠 ES 的索引设计和聚合查询。这三环缺一不可。

最后送你一句话:没有日志的系统,就像没有黑匣子的飞机。出事了只能靠猜。趁现在系统还稳,赶紧把日志平台搭起来吧。