4、日志监控体系:ELK/EFK架构、日志采集(Filebeat/Fluentd)、日志清洗与结构化、异常日志告警

日志这东西,说实话,平时没人搭理它。但一旦线上出了故障,它就是唯一的救命稻草。我见过太多团队,服务器挂了,连个日志都捞不出来,只能靠猜。嗯,这章我们就聊聊怎么把日志管好。

4.1 为什么需要日志监控体系?

你想想看,一个生产环境跑着几十上百个服务。每个服务都在疯狂输出日志。如果没有统一的收集和分析平台,你只能一台台机器去翻文件。这效率,基本等于大海捞针。

我个人习惯是,日志体系必须满足三个核心诉求:

  • 集中采集:所有日志自动汇聚到一个地方
  • 快速检索:出问题时,几秒钟内找到关键信息
  • 实时告警:异常日志出现时,第一时间通知到人

核心观点:日志不是用来「看」的,是用来「查」和「告警」的。如果日志系统不能帮你快速定位问题,那它就是摆设。

4.2 ELK vs EFK:选哪个?

ELK 是 Elasticsearch + Logstash + Kibana 的组合。EFK 则是把 Logstash 换成了 Fluentd。两者核心区别在于日志采集和转发层。

我在项目中遇到过这样的选择困境。当时团队里有人坚持用 Logstash,有人力推 Fluentd。最后我们做了个对比测试:

特性 Logstash Fluentd
资源消耗 较高(Java 堆) 较低(Ruby/C)
插件生态 丰富 丰富
配置复杂度 中等 较低
性能 中等 较高
内存占用 默认 1GB+ 几十 MB

说白了,如果你的机器资源比较紧张,或者日志量特别大,Fluentd 更合适。如果团队对 Logstash 更熟悉,或者需要更复杂的过滤逻辑,Logstash 也没问题。我个人现在更倾向 EFK,因为 Fluentd 的轻量级特性在容器化场景下太香了。

4.3 日志采集:Filebeat vs Fluentd

采集层是日志体系的「触角」。它部署在每台机器上,负责把日志文件读出来,送到下游。

Filebeat

Filebeat 是 Elastic 自家的轻量级采集器。它的设计哲学就是「简单」。安装完,配个路径,直接就能用。

# filebeat.yml 示例
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://elasticsearch:9200"]

这里有个坑,我必须要说。多行日志的处理,比如 Java 的异常堆栈,Filebeat 默认是一行一行发的。如果不配置 multiline,一个异常会被拆成几十条记录,根本没法看。我曾经因为这个被坑过一次,排查了半天才发现是堆栈被拆散了。

Fluentd

Fluentd 的配置更灵活,但上手门槛稍高一点。它用 <source><match> 来定义数据流。

# fluentd.conf 示例
<source>
  @type tail
  path /var/log/app/*.log
  pos_file /var/log/fluentd/app.log.pos
  tag app.log
  <parse>
    @type json
  </parse>
</source>

<match app.log>
  @type elasticsearch
  host elasticsearch
  port 9200
  logstash_format true
</match>

我的建议:如果日志格式比较规整,用 Filebeat 就够了。如果需要对日志做复杂的解析和路由,Fluentd 更合适。别为了炫技而过度设计,简单就是美。

4.4 日志清洗与结构化

原始日志通常是纯文本,比如:

2024-01-15 10:23:45 ERROR [order-service] Failed to process order 12345: timeout

这种格式,你搜一下还行,但要做聚合分析就麻烦了。比如你想统计「每个服务的错误率」,纯文本根本没法算。

所以,我们需要把日志结构化。说白了,就是把一行文本拆成多个字段:

{
  "timestamp": "2024-01-15T10:23:45",
  "level": "ERROR",
  "service": "order-service",
  "message": "Failed to process order 12345: timeout",
  "order_id": "12345"
}

在 Logstash 里,可以用 grok 插件做解析:

filter {
  grok {
    match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} \[%{DATA:service}\] %{GREEDYDATA:message}" }
  }
  date {
    match => ["timestamp", "ISO8601"]
  }
}

在 Fluentd 里,可以用 parser 插件:

<filter app.log>
  @type parser
  key_name message
  <parse>
    @type regexp
    expression /^(?<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (?<level>\w+) \[(?<service>[^\]]+)\] (?<message>.*)$/
  </parse>
</filter>

注意:正则表达式写不好,性能会急剧下降。我建议先用简单的规则,等日志量大了再优化。别一开始就搞复杂的嵌套解析。

4.5 异常日志告警

日志收集好了,结构化了,最后一步就是告警。总不能让人盯着 Kibana 看吧?

常见的告警策略有几种:

  • 关键字匹配:出现「ERROR」、「FATAL」、「NullPointerException」等关键字就告警
  • 频率阈值:某个错误在 5 分钟内出现超过 100 次就告警
  • 新错误检测:出现从未见过的错误类型就告警
  • 趋势异常:错误率突然飙升 300% 就告警

在 Elasticsearch 里,可以用 Watcher 或者 ElastAlert 来实现。我个人更推荐 ElastAlert,因为它配置简单,而且支持多种规则类型。

# elatalert_rule.yaml 示例
name: "高频错误告警"
type: frequency
index: logstash-*
num_events: 50
timeframe:
  minutes: 5
filter:
- query:
    query_string:
      query: "level: ERROR"
alert:
- "slack"
slack_webhook_url: "https://hooks.slack.com/services/xxx"

这里有个经验之谈。告警一定要做「降噪」。我曾经见过一个团队,一天收到 2000 条告警,最后所有人都把告警群屏蔽了。嗯,这等于没有告警。所以,告警规则要精,不要多。宁可漏报,不要误报。

4.6 整体架构图

下面这张图,是我对日志监控体系的理解。它展示了从日志产生到告警通知的完整链路。

应用服务器 日志文件 Filebeat 或 Fluentd Kafka 消息队列 Logstash 解析/结构化 Elasticsearch 存储/索引 Kibana 可视化 ElastAlert 告警引擎 消费 查询 分析

这张图里,我特意加了一个 Kafka。为什么?因为当日志量很大的时候,采集层和解析层之间需要缓冲。否则一旦 Logstash 挂了,日志就会丢失。Kafka 在这里起到了削峰填谷的作用。

避坑指南:我曾经在一个项目中,没有加消息队列,结果 Logstash 一次 OOM,丢了半小时的日志。从那以后,我只要做日志系统,必加 Kafka。哪怕流量不大,也留着这个通道,有备无患。

4.7 总结

日志监控体系,说白了就是四个环节:采、传、存、查。每个环节都有不同的技术选型。我的建议是:

  • 采集层用 Filebeat 或 Fluentd,看团队习惯
  • 传输层加个 Kafka,保底
  • 解析层用 Logstash 或 Fluentd,把日志结构化
  • 存储层用 Elasticsearch,查询快
  • 告警层用 ElastAlert,规则要精

嗯,这套体系搭好了,以后出问题,你就能从容应对了。

专注资料整理