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 整体架构图
下面这张图,是我对日志监控体系的理解。它展示了从日志产生到告警通知的完整链路。
这张图里,我特意加了一个 Kafka。为什么?因为当日志量很大的时候,采集层和解析层之间需要缓冲。否则一旦 Logstash 挂了,日志就会丢失。Kafka 在这里起到了削峰填谷的作用。
避坑指南:我曾经在一个项目中,没有加消息队列,结果 Logstash 一次 OOM,丢了半小时的日志。从那以后,我只要做日志系统,必加 Kafka。哪怕流量不大,也留着这个通道,有备无患。
4.7 总结
日志监控体系,说白了就是四个环节:采、传、存、查。每个环节都有不同的技术选型。我的建议是:
- 采集层用 Filebeat 或 Fluentd,看团队习惯
- 传输层加个 Kafka,保底
- 解析层用 Logstash 或 Fluentd,把日志结构化
- 存储层用 Elasticsearch,查询快
- 告警层用 ElastAlert,规则要精
嗯,这套体系搭好了,以后出问题,你就能从容应对了。