日志产生源与采集:从哪里来,怎么收

做日志管理的第一件事,就是搞清楚日志到底在哪儿。你想想看,一个中型公司的 IT 环境,少说有几十台服务器、几台网络设备,现在再加上容器集群,日志源五花八门。我最早接手过一个项目,光找日志文件就花了三天——各部门放的路径都不一样,有的在 /var/log/app/,有的在 D:\Logs\,还有的直接写到数据库里。所以这一章,咱们先把“从哪里来”和“怎么收”理清楚。

常见的日志源

日志源按设备类型可以分成三大类。每类的格式、频率、采集方式都不一样,别指望一把抓。

设备类型 典型设备/实例 常见日志内容 默认格式 采集难度
服务器 Linux/Windows 物理机或虚机 系统日志(syslog、EventLog)、应用日志(Web、数据库、中间件) 文本行、JSON、CSV、二进制 低
网络设备 路由器、交换机、防火墙、负载均衡 Syslog(接口状态、流量、安全事件) RFC 3164/5424 syslog 中
容器 Kubernetes Pod、Docker 容器 stdout/stderr 日志、容器事件、Node 日志 JSON(docker log driver) 较高

注意:网络设备日志常以 UDP 发送(丢包不重传),我曾经遇到过防火墙在高峰期丢 40% 的日志,后来改成 TCP 和本地缓存才解决。如果你对完整性要求高,别迷信 UDP。

服务器日志——最传统也最杂

服务器日志我见得最多。Linux 下 /var/log/ 里的东西,Windows 下事件查看器里的记录,还有各种中间件自己写的日志文件。个人习惯是先把所有应用日志标准化——都写成 JSON 格式,至少带上时间戳、级别、服务名、请求 ID。否则采集上来也是一堆乱麻。

这里有个坑:很多开发者喜欢把日志打到 console(标准输出)然后重定向到文件。这在容器时代很常见,但如果你直接采集文件,磁盘满了日志丢了都不知道。嗯,我在一次大促时就吃了这个亏——Java 应用日志只写 stdout,采集器只监控 /app/logs/*.log,结果两套没对上,故障排查晚了半小时。

网络设备日志——UDP 是常态,可靠实现靠方案

网络设备大多支持 RFC 3164 或 5424 的 syslog 协议。默认走 UDP 514 端口,发送即忘,不关心你是否收到。我建议:

  • 如果不差带宽,改成 TCP 或 RELP(Reliable Event Logging Protocol)
  • 或者用 syslog-ng/rsyslog 的前端做本地缓冲 + 重传
  • 防火墙日志量极大(每秒几万条),采集时别忘了做聚合过滤,只保留安全事件和异常流量

我的经验:网络设备的日志时间戳经常不准(设备时钟不同步)。采集后务必在管道里加一步时间戳归一化,我习惯统一转为 UTC 毫秒时间戳,否则跨设备关联时你会疯掉。

容器日志——让采集器适配动态环境

容器环境下,Pod 随时创建销毁,日志文件也随容器生命周期走。Kubernetes 默认把容器 stdout/stderr 写入宿主机的 /var/log/containers/,文件名为 <pod名>_<命名空间>_<容器名>-xxx.log。这种命名虽然长,但包含了足够信息。

我的建议是:

  • 如果集群规模小于 50 节点,用 DaemonSet 部署 Filebeat 或 Fluentd,每个节点一个采集器
  • 如果节点数多(几百上千),先让容器日志通过 Fluentd 写入 Kafka 缓冲,再通过 Logstash 消费——别让采集器直接打后端存储,你扛不住
  • 容器日志的行格式要统一,最好 JSON,用 --log-opt max-size=10m max-file=3 控制滚卷,防止单个文件撑爆磁盘

核心逻辑:容器日志采集最关键的是“动态感知”和“标签注入”。采集器自动识别新 Pod,并注入 Pod 名、命名空间、标签等元数据。否则日志到了后端你根本不知道是哪来的。

采集工具三驾马车:Filebeat、Fluentd、Logstash

市面上主流工具就这三家。我用过的优先级是:轻量优先 → Filebeat,复杂管道 → Logstash,统一生态 → Fluentd。下面逐个说。

Filebeat

Elastic 公司出品,Go 语言写,内存占用极低(几十 MB)。我习惯用它做日志的“最后一公里”采集——在每台服务器上只部署 Filebeat,它负责读文件、压缩、加密发送到 Kafka 或 Logstash。

配置很简单,比如 collect 一个 JSON 日志文件:

filebeat.inputs:
- type: log
  enabled: true
  paths:
    - /var/log/app/*.log
  json.keys_under_root: true
  json.message_key: message

output.kafka:
  hosts: ["kafka01:9092", "kafka02:9092"]
  topic: "app-log"

Filebeat 有个自带的 harvester 机制,不管文件怎么滚卷(rotation),它都能跟踪 inode 继续读。我曾用它连续跑过一年零故障。

Fluentd

用 Ruby 写的,插件生态超级丰富(官方 1000+ 个)。最大的优势是“统一日志层”——不管你从哪来(syslog、HTTP、tail),都能通过插件转成统一结构,再输出到几十种后端。我见过很多公司用 Fluentd 做 Kubernetes 的默认日志采集器,配合 fluent-plugin-kubernetes 可以自动注入 Pod 标签。

不过说实话,Fluentd 的内存和 CPU 开销比 Filebeat 大不少。如果你的服务器只有 1C2G,我建议别上 Fluentd,选 Filebeat 更合适。

Logstash

ELK 全家桶里的“管道处理”角色。Java 写的,性能一般但功能最强。能做复杂的过滤、转换、富化——正则替换、GeoIP 地址解析、多行合并、时间戳解析等等。我的用法:把 Logstash 放在 Kafka 后面,专门做流式处理,然后输出给 Elasticsearch。

Logstash 有个大问题:吃内存。你想想看,每启动一个 pipeline 就要一个线程,内存轻松上 GB。我一般建议只有处理逻辑复杂时才用 Logstash,简单场景放 Filebeat 手里就行。

特性 Filebeat Fluentd Logstash
语言 Go Ruby / C Java
资源占用 极低 中等 较高
处理能力 弱(仅采集+简单过滤) 中(丰富插件) 强(完整 pipeline)
输出目的地 Elasticsearch、Kafka、Logstash 几乎所有(插件) ELK 生态为主
容器生态 有 Helm chart 官方 Kubernetes 插件 有但重
典型场景 轻量采集器 统一日志层 复杂数据管道

采集架构的选择思路

我个人习惯遵循“分层采集”原则:

  1. 边缘层:每台机器/容器节点部署轻量采集器(Filebeat/Fluentd),只做文件读取、简单字段提取、压缩发送
  2. 缓冲层:使用 Kafka (或 Redis)做消息队列,削峰填谷,保证日志不丢失
  3. 处理层:Logstash 或 Fluentd 聚合节点从缓冲层消费,进行过滤、转换、富化
  4. 存储层:发送到 Elasticsearch、S3、HDFS 等存储系统

下面这张图能帮你快速理解采集链路:

日志采集分层架构 日志源 服务器 系统日志 / 应用日志 网络设备 Syslog (UDP/TCP) 容器 / K8s stdout/stderr 采集工具 Filebeat 轻量 / Go / 边缘部署 Fluentd 插件丰富 / 统一层 Logstash 强管道处理 / Java 缓冲层 Kafka / Redis 处理层 Logstash / Fluentd 聚合节点 存储层 Elasticsearch S3 / HDFS(归档) 提示:小规模部署可省略缓冲层(Kafka),直接由 Filebeat 发往 Logstash 或 ES。但生产环境建议带缓冲层以防峰值丢失日志。

避坑指南与个人习惯

我曾经在一个云原生项目里犯过这样的错:同时用 Filebeat + Logstash 双向传输日志,结果没有做重试和 ack 机制,磁盘故障导致日志永久丢失。从那以后我养成了两个习惯:

  • 所有生产环境的采集配置必须加 close_inactive 和 clean_removed(Filebeat)或 flush_interval(Fluentd),防止文件句柄泄漏
  • 采集链路中至少有一个持久化缓冲(Kafka 或 Redis),哪怕只是单点测试,也先把消息队列搭好再调后续

另外别忽视日志文件的滚动(rotation)。很多系统默认的 logrotate 配置会把滚动后的日志文件改名、压缩,采集器如果未及时重新打开新文件,会漏数据。Filebeat 的 close_removed 和 harvester_limit 参数就是干这个用的。记得根据日志滚卷频率调整采集器的扫描间隔。

一个小技巧:在采集侧不管用哪个工具,都先做“结构化”处理——把原始日志转成 JSON,然后加一个 @timestamp 字段(采集时间)。这样后面压缩归档时,时间线非常清晰,压缩比也更高(因为可以按天分区)