3. 日志采集与结构化:从杂乱无章的日志到结构化数据

说实话,我见过太多线上事故了。系统挂了,第一反应就是查日志。结果呢?一堆文本文件散落在各个服务器上,grep 半天啥也找不到。那种感觉,就像在垃圾堆里找一枚针。

所以,日志采集和结构化,是数据质量监控的第一步。没有这一步,后面所有的告警、分析、可视化都是空中楼阁。今天我就聊聊,怎么用 ELK/EFK 栈把这堆「垃圾」变成「金矿」。

3.1 为什么日志必须结构化?

先看一个反面教材。这是我以前接手的一个老系统,日志长这样:

2024-01-15 10:23:45 ERROR user login failed
2024-01-15 10:23:46 WARN timeout detected
2024-01-15 10:23:47 INFO payment success for order 12345

你想想看,这种日志能干嘛?想统计「今天有多少次登录失败」,你得写正则去匹配。想查「某个订单的完整调用链」,你得肉眼去翻。说白了,这就是非结构化数据,机器读不懂,人也读得累。

我习惯把日志结构化成 JSON 格式:

{
  "@timestamp": "2024-01-15T10:23:45.123Z",
  "level": "ERROR",
  "logger": "com.api.auth.LoginService",
  "message": "user login failed",
  "user_id": "u_10086",
  "error_code": "AUTH_001",
  "duration_ms": 230,
  "trace_id": "abc123def456"
}

结构化之后,Elasticsearch 可以直接索引每个字段。你想查什么,一个 Query 就搞定。我在项目中遇到过,把日志从文本切到 JSON 后,排查问题的速度至少快了 3 倍。

核心原则:日志结构化不是「锦上添花」,而是「雪中送炭」。没有结构化的日志,数据质量监控就是空谈。

3.2 ELK vs EFK:怎么选?

ELK 是 Elasticsearch + Logstash + Kibana。EFK 是把 Logstash 换成了 Fluentd 或 Fluent Bit。我两个都用过,说说我的感受。

组件 优势 劣势 我的建议
Logstash 插件丰富,数据处理能力强 吃内存,Java 栈,启动慢 适合复杂的数据清洗场景
Fluentd 轻量,Ruby 写的,资源占用低 部分插件维护一般 适合容器化环境
Fluent Bit 极轻量,C 写的,性能怪兽 功能相对精简 适合边缘节点或嵌入式场景

我个人习惯:新项目优先上 EFK,尤其是用 Fluent Bit 做采集端。为什么?因为现在都是容器化部署,资源敏感。Fluent Bit 的内存占用只有 Logstash 的十分之一,性能却差不多。

避坑指南:我曾经在一个高并发场景下用 Logstash 采集日志,结果 Logstash 自己 OOM 了,导致日志丢失。后来换成 Fluent Bit + 缓冲队列,再也没出过问题。记住,采集端一定要有背压机制。

3.3 搭建 EFK 栈:从零到一

好,我们直接上手。假设你有三台机器:一台跑 Elasticsearch,一台跑 Kibana,一台跑 Fluent Bit。当然,生产环境建议 ES 集群,这里简化演示。

3.3.1 Elasticsearch 部署

ES 是核心存储和检索引擎。我习惯用 Docker 快速拉起:

docker run -d \
  --name elasticsearch \
  -p 9200:9200 \
  -p 9300:9300 \
  -e "discovery.type=single-node" \
  -e "ES_JAVA_OPTS=-Xms1g -Xmx1g" \
  -v es_data:/usr/share/elasticsearch/data \
  docker.elastic.co/elasticsearch/elasticsearch:8.11.0

嗯,这里要注意:ES 的 JVM 堆内存不要超过物理内存的 50%。我见过有人给 32G 机器分配了 24G 堆,结果 GC 频繁,集群直接挂了。留一半给操作系统做文件缓存,性能反而更好。

3.3.2 Kibana 部署

Kibana 是可视化层,没啥好说的,配好 ES 地址就行:

docker run -d \
  --name kibana \
  -p 5601:5601 \
  -e "ELASTICSEARCH_HOSTS=http://your-es-ip:9200" \
  docker.elastic.co/kibana/kibana:8.11.0

3.3.3 Fluent Bit 配置

这是关键。Fluent Bit 负责采集、解析、转发。我的配置文件长这样:

[SERVICE]
    flush        1
    log_level    info
    parsers_file parsers.conf

[INPUT]
    name        tail
    path        /var/log/app/*.log
    tag         app.*
    parser      json
    read_from_head true

[FILTER]
    name        modify
    match       app.*
    add         hostname ${HOSTNAME}
    add         env production

[OUTPUT]
    name        es
    match       *
    host        your-es-ip
    port        9200
    index       app-logs-%Y.%m.%d
    type        _doc
    generate_id true

这里有个小技巧:用 %Y.%m.%d 做索引后缀,每天自动滚动。我习惯保留 30 天数据,配合 ILM(索引生命周期管理)自动删除旧索引,省心。

注意:Fluent Bit 的 parser 必须和日志格式匹配。如果你的日志是 JSON,就用 parser json。如果是自定义格式,需要自己写 parser 配置文件。我曾经因为 parser 写错了,导致所有日志都进了 _text 字段,排查了半天。

3.4 日志结构化实战:从混乱到清晰

假设你的应用日志还是非结构化的,怎么办?别急,Fluent Bit 的 Filter 插件可以帮你做「在线清洗」。

举个例子,原始日志:

2024-01-15 10:23:45 ERROR [com.api.order] order_id=12345, user_id=u_10086, amount=99.9, message="payment timeout"

我们可以用 lua 或 grep 插件把它拆成结构化字段。我个人习惯用 lua,灵活:

[FILTER]
    name    lua
    match   app.*
    code    function parse_log(tag, timestamp, record)
                local msg = record["log"]
                local level, logger, rest = msg:match("(%w+) %[(%S+)%] (.+)")
                record["level"] = level
                record["logger"] = logger
                -- 继续解析 rest 字段...
                return 1, timestamp, record
            end

你看,这样就把杂乱无章的文本,变成了结构化的 JSON。ES 收到后,每个字段都可以独立搜索和聚合。

3.5 数据质量监控的基石:索引模板与 Mapping

日志进了 ES,不等于万事大吉。我见过最坑的事:ES 自动推断字段类型,把数字当成字符串。你想做聚合统计?没门。

所以,我习惯提前定义索引模板:

PUT _index_template/app-logs-template
{
  "index_patterns": ["app-logs-*"],
  "template": {
    "settings": {
      "number_of_shards": 3,
      "number_of_replicas": 1,
      "refresh_interval": "5s"
    },
    "mappings": {
      "properties": {
        "@timestamp": { "type": "date" },
        "level": { "type": "keyword" },
        "logger": { "type": "keyword" },
        "message": { "type": "text" },
        "duration_ms": { "type": "integer" },
        "amount": { "type": "float" },
        "user_id": { "type": "keyword" },
        "trace_id": { "type": "keyword" }
      }
    }
  }
}

这样,duration_ms 是整数,amount 是浮点数,level 是关键字。你想算平均响应时间、统计错误率,直接写 ES 聚合查询就行。

数据质量的第一道防线:在日志入口就做好结构化,比事后清洗成本低 10 倍。索引模板 + Mapping 就是你的「数据契约」,谁都不能乱来。

3.6 整体架构图

说了这么多,画个图总结一下。这是 EFK 栈的典型数据流:

EFK 日志采集与结构化架构图 应用服务 /var/log/app/*.log Fluent Bit 采集 + 解析 + 过滤 Elasticsearch 存储 + 索引 + 检索 Kibana 可视化 + 告警 关键说明: 1. Fluent Bit 从应用日志文件采集数据,支持 tail、syslog、tcp 等多种输入 2. 在 Fluent Bit 中完成日志解析(非结构化 → 结构化)和字段过滤 3. 结构化数据写入 ES,按日期滚动索引,配合 ILM 管理生命周期

3.7 避坑总结

最后,分享几个我踩过的坑:

  • 日志丢失:Fluent Bit 默认是异步模式,如果 ES 挂了,日志会丢。我习惯加一个 buffer 配置,用磁盘做缓冲,保证数据不丢。
  • Mapping 爆炸:ES 默认会动态生成字段。如果日志里有随机 key(比如 user_xxx),Mapping 会无限膨胀。我习惯用 dynamic: false 或 dynamic: strict 控制。
  • 时区问题:日志里的时间戳不带时区,ES 默认按 UTC 存。我习惯在 Fluent Bit 里统一转成 UTC,Kibana 展示时再按用户时区转换。
  • 性能瓶颈:Logstash 的 Grok 解析很慢。高并发场景下,我建议用 Fluent Bit 的 lua 或 regex 解析,性能好一个数量级。

嗯,日志采集和结构化,说白了就是「把垃圾变成黄金」。做好了,后面的数据质量监控才有根基。做不好,你就是在沙子上盖楼。