5237 字
26 分钟
监控:日志收集与分析

Metrics 告诉我们“系统发生了什么变化”,而 Logs 更擅长告诉我们“具体发生了什么”。

日志收集的核心并不是简单地把日志文件复制到另一台机器,而是建立一条从产生、采集、解析、传输、存储到查询分析的完整链路。

一、日志与日志系统#

1.1 什么是日志#

日志(Log)是系统运行过程中产生的事件记录。

例如:

2026-09-14 10:32:11 INFO Application started
2026-09-14 10:32:15 INFO Request GET /api/users
2026-09-14 10:32:18 ERROR Database connection refused

一条日志通常包含:

时间
级别
来源
消息
上下文

例如:

2026-09-14 10:32:18
│
├── 时间
├── ERROR
├── backend
└── Database connection refused

日志和 Metrics 的区别可以简单理解为:

Metrics
→ 数值化状态
Logs
→ 具体事件记录

例如:

CPU = 95%

属于:

Metric

而:

OutOfMemoryError: Java heap space

属于:

Log

1.2 为什么需要日志#

当监控发现:

HTTP 5xx ↑

Metric 可以告诉我们:

错误率升高了

但还需要知道:

什么请求失败?
为什么失败?
哪个服务失败?
什么时候开始?
异常信息是什么?

这时就需要日志。

因此:

Metrics
│
▼
发现异常
│
▼
Logs
│
▼
分析原因

这也是实际运维中:

监控
+
日志

经常配合使用的原因。

二、日志的来源#

一个 Linux 系统中,日志可能来自多个位置。

Logs
│
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
System Application Container
│ │ │
journald log file stdout
syslog stdout stderr
kernel stderr

2.1 系统日志#

Linux 本身会产生:

内核事件
服务状态
认证信息
启动信息
系统错误

现代 Linux 系统中常见来源之一是:

systemd-journald

查看:

Terminal window
journalctl

例如查看:

Terminal window
journalctl -u nginx

只查看最近一小时:

Terminal window
journalctl --since "1 hour ago"

查看本次启动:

Terminal window
journalctl -b

2.2 文件日志#

很多应用会直接写入:

/var/log/

例如:

/var/log/
├── nginx/
├── mysql/
├── redis/
└── ...

Nginx 常见:

access.log
error.log

应用也可能:

/app/logs/application.log

因此日志采集器需要能够:

读取文件
持续跟踪文件
识别新增内容

2.3 标准输出与标准错误#

现代容器化应用非常常见的日志方式是:

stdout
stderr

例如:

Application
├── stdout
└── stderr
│
▼
Container Runtime

Kubernetes 官方也建议容器化应用将日志写入标准输出和标准错误,这也是 Kubernetes 日志收集最常见的基础方式。

Docker 同样提供专门的 Logging Driver 来处理容器的日志输出。Docker Engine 默认使用 json-file logging driver,但也支持 local、journald、syslog、fluentd 等方式。

三、日志级别#

应用通常会对日志进行分级。

常见级别:

TRACE
DEBUG
INFO
WARN
ERROR
FATAL

不同框架名称可能略有不同。

可以粗略理解:

DEBUG
→ 调试细节
INFO
→ 正常运行信息
WARN
→ 潜在问题
ERROR
→ 请求或操作发生错误
FATAL
→ 严重错误,可能导致程序退出

例如:

INFO
Application started
WARN
Connection pool is near capacity
ERROR
Database connection refused

3.1 为什么不能全部输出 DEBUG#

开发环境可能需要大量 DEBUG 日志。

但生产环境如果:

所有请求
+
所有变量
+
所有 SQL
+
大量调试信息

全部输出,就可能导致:

日志量暴增
磁盘增长
网络带宽增加
存储成本增加
查询困难

因此生产环境通常会根据实际需要设置日志级别。

四、日志格式#

4.1 非结构化日志#

最简单的日志:

2026-09-14 10:31:20 ERROR Database connection refused

人比较容易阅读,但程序解析起来比较麻烦。

4.2 结构化日志#

更适合集中式日志系统:

{
"timestamp": "2026-09-14T10:31:20Z",
"level": "ERROR",
"service": "backend",
"message": "Database connection refused"
}

结构化日志可以明确区分:

timestamp
level
service
message

后续查询时可以直接按照字段过滤:

level = ERROR
service = backend

而不是通过字符串搜索整行文本。

4.3 为什么结构化日志重要#

假设有:

100 万条日志

如果都是:

纯文本字符串

分析器通常需要先进行:

解析
提取字段

如果日志本身已经结构化:

JSON

那么采集系统更容易建立:

字段
索引
过滤条件
聚合

这会直接影响后面的集中式日志分析。

五、日志轮转 Log Rotation#

5.1 为什么需要日志轮转#

假设:

application.log

每天增长:

2 GB

一个月之后可能达到:

60 GB

如果一直写同一个文件:

磁盘最终会被写满

因此需要:

Log Rotation

典型过程:

application.log
│
▼
application.log.1
│
▼
application.log.2
│
▼
application.log.3.gz

旧日志可以:

压缩
保留
删除

5.2 logrotate#

Linux 中常见工具:

logrotate

可以根据:

时间
文件大小

轮转日志,并支持压缩、保留数量等操作。

例如配置:

/var/log/myapp/*.log {
daily
rotate 7
compress
missingok
notifempty
}

含义大致是:

每天轮转
保留 7 份
压缩旧日志
日志不存在不报错
空日志不轮转

5.3 日志轮转的一个常见坑#

应用打开了:

application.log

执行轮转后:

application.log
→ application.log.1

如果应用仍然持有原文件描述符,就可能出现:

文件名已经变了
但进程仍然写旧文件

因此某些应用轮转后需要:

reload
reopen
HUP

等机制重新打开日志文件。

所以:

Log Rotation
≠
只改文件名

还要考虑:

应用如何重新打开日志

六、日志收集#

6.1 什么是日志采集#

日志采集就是:

Log Source
│
▼
Collector / Agent
│
▼
Backend

例如:

/var/log/nginx/access.log
│
▼
Log Agent
│
▼
Central Log Server

日志采集器通常需要完成:

读取
解析
过滤
添加字段
缓存
批量发送

6.2 Agent#

Agent 通常运行在:

被监控服务器

上。

例如:

Server A
└── Log Agent
Server B
└── Log Agent
Server C
└── Log Agent

统一发送:

┌── Server A ── Agent ──┐
├── Server B ── Agent ──┤
└── Server C ── Agent ──┘
│
▼
Central Log Backend

这种方式非常适合大量服务器。

6.3 为什么通常使用 Agent#

如果没有 Agent:

Central Server
│
├── SSH Server A
├── SSH Server B
├── SSH Server C
└── ...

中心服务器主动去每台机器拉日志,管理会比较复杂。

使用 Agent:

Server
│
└── Agent
│
└──► Central Backend

日志产生在哪里,就在那里进行采集。

七、日志采集器应该做什么#

一个成熟的日志采集器通常不仅是:

cat log

而是完整的数据处理过程:

Log File
│
▼
Tail
│
▼
Parse
│
▼
Transform
│
▼
Enrich
│
▼
Buffer
│
▼
Send

7.1 Tail#

持续读取新增日志。

例如:

application.log

应用继续写:

Line 100
Line 101
Line 102
...

Agent 只读取新增内容,而不是每次都重新读取整个文件。

7.2 Position / Offset#

采集器需要知道:

“我已经读到哪里了?”

例如:

application.log
│
├── 已读取到 Line 1000
│
└── 新增 Line 1001+

因此通常需要保存:

文件位置
offset
inode / file identity

这样 Agent 重启后才能尽量从正确位置继续。

OpenTelemetry 的 filelog receiver 等日志采集能力同样包含文件跟踪、日志轮转识别和 checkpoint 等机制。

7.3 Parse#

例如原始日志:

127.0.0.1 - - [14/Sep/2026:10:30:12 +0800] "GET /api HTTP/1.1" 200 1234

采集器可以解析成:

client_ip = 127.0.0.1
method = GET
path = /api
status = 200
bytes = 1234

7.4 Enrich#

采集器还可以增加额外字段:

host
service
environment
container
pod
namespace

例如:

{
"service": "backend",
"environment": "production",
"host": "node-01",
"level": "ERROR",
"message": "Database connection refused"
}

这样后面的集中式系统就可以方便地进行过滤和聚合。

八、集中式日志#

8.1 为什么需要集中式日志#

假设有:

100 台服务器

每台都有:

/var/log/app.log

用户访问出现异常时,如果要求运维:

SSH 到 Server 1
SSH 到 Server 2
SSH 到 Server 3
...

逐台搜索:

ERROR

效率会非常低。

集中式日志就是:

Server 1 ─┐
Server 2 ─┤
Server 3 ─┤
Server 4 ─┤
▼
Central Backend
│
▼
Search

于是可以统一查询:

service = backend
level = ERROR
time > 10:30

8.2 集中式日志的核心能力#

一个日志平台通常需要:

采集
传输
存储
索引
查询
过滤
聚合
可视化
权限
保留策略

因此:

日志系统
≠
日志文件服务器

它实际上是一整套数据处理系统。

九、集中式日志的典型架构#

最简单:

┌── Agent ── Server A
│
Log Sources ────┼── Agent ── Server B
│
└── Agent ── Server C
│
▼
Central Backend
│
▼
Query

更完整:

Logs
│
▼
Collection
│
▼
Parsing
│
▼
Buffer / Queue
│
▼
Storage
│
▼
Index
│
▼
Search
│
▼
Visualization

中间还可以加入:

Filtering
Sampling
Enrichment
Routing

十、日志传输#

日志从 Agent 到中心系统,通常需要通过网络发送。

例如:

Agent
│
│ TCP / HTTP / gRPC / Syslog
▼
Log Backend

常见问题:

网络断开
连接超时
服务不可用
认证失败
数据发送过快

因此日志系统也会受到网络质量影响。

10.1 Buffer#

假设:

日志产生速度
>
日志发送速度

那么:

日志会积压

例如:

Producer
│
│ 10000 logs/s
▼
Agent
│
│ 5000 logs/s
▼
Backend

这时候需要考虑:

Memory Buffer
Disk Buffer
Queue
Backpressure

否则后端短暂故障可能直接造成日志丢失。

十一、日志可靠性#

11.1 至少要回答几个问题#

一个日志系统不能只问:

“能查到日志吗?”

还应该考虑:

日志会不会丢?
日志会不会重复?
日志会延迟多久?
后端挂了怎么办?
磁盘满了怎么办?
日志保留多久?

11.2 At-least-once 与重复日志#

日志采集中,经常需要在:

不丢日志

与:

不重复

之间做权衡。

例如:

发送成功

但 Agent 在收到确认前突然崩溃。

重启后可能重新发送之前的数据:

Log A
Log A

于是出现:

Duplicate

因此集中式日志系统经常需要依赖:

offset
ack
checkpoint
id

等机制提高可靠性。

对于日志而言:

至少一次

通常比:

完全不重复

更容易保证。

十二、Docker 日志#

容器环境通常会把应用输出写到标准输出和标准错误:

stdout
stderr

这里进一步从日志系统角度理解。

Container
│
├── stdout
└── stderr
│
▼
Docker Logging Driver
│
▼
Storage

Docker 当前默认 logging driver 为:

json-file

同时还提供:

local
syslog
journald
gelf
fluentd
...

等驱动。

查看:

Terminal window
docker info

也可以直接:

Terminal window
docker info --format '{{.LoggingDriver}}'

查看当前默认 Logging Driver。

12.1 Docker 日志磁盘问题#

容器日志如果持续增长:

Container
│
▼
Logs
│
▼
Host Disk
│
▼
100%

最终可能导致:

Docker 异常
数据库异常
应用写文件失败
整个服务器磁盘不足

因此:

容器日志
+
日志轮转
+
日志保留策略

必须一起考虑。

Docker 官方也特别提示日志文件可能造成宿主机磁盘耗尽,并提供 local logging driver 等机制减少这类风险。

十三、Kubernetes 日志#

Kubernetes 环境的日志结构比单机 Docker 更复杂。

Pod
│
├── Container A
└── Container B
│
▼
stdout
stderr
│
▼
Node-level logging
│
▼
Central Logging

Kubernetes 官方将:

Node-level logging agent

作为常见的集群级日志方案,并推荐在每个 Node 上运行一个日志 Agent;常见实现方式是使用 DaemonSet,使每个节点运行一个 Agent。

13.1 为什么不能只依赖 Pod 本地日志#

Pod 可能:

被删除
被重新调度
Node 故障
Container 重启

如果日志只保存在:

Pod / Node 本地

就可能随着运行环境消失。

因此集群日志应该尽量做到:

Pod Logs
│
▼
Node Agent
│
▼
Central Backend

使日志生命周期独立于:

Pod
Container
Node

Kubernetes 官方也明确指出,集群级日志应该拥有独立于 Node、Pod 和 Container 生命周期的存储与管理方式。

十四、Node Agent、Sidecar 与直接上报#

Kubernetes 集群级日志常见有三种基本思路。

14.1 Node-level Agent#

Node
│
├── Pod A
├── Pod B
├── Pod C
│
└── Log Agent
│
▼
Backend

优点:

每个 Node 一个 Agent
应用基本无需修改

这也是非常常见的方案。

14.2 Sidecar#

Pod
├── Application
└── Log Sidecar
│
▼
Backend

适合:

应用只能输出文件
不同日志需要特殊处理

但代价是:

每个 Pod 增加额外容器
CPU / Memory 增加
配置复杂

14.3 应用直接发送#

Application
│
▼
Log Backend

应用自己负责:

发送
重试
认证

这样可以减少中间层,但会让应用与日志基础设施耦合。

因此:

Node Agent
→ 通用性强
Sidecar
→ 针对单个工作负载定制
Direct
→ 应用主动管理日志输出

十五、日志解析与多行日志#

15.1 为什么多行日志是问题#

很多异常不是一行:

ERROR Application failed

而是一整个 Stack Trace:

ERROR Application failed
java.lang.RuntimeException: ...
at com.example.App.run(App.java:100)
at com.example.Main.main(Main.java:20)
...

如果采集器简单按:

一行 = 一条日志

最终可能变成:

Log 1 → ERROR Application failed
Log 2 → java.lang.RuntimeException
Log 3 → at com.example.App.run
Log 4 → at com.example.Main.main

原本的一次异常就被拆散了。

因此日志采集器需要支持:

Multiline Parsing

把多行内容重新组合:

Stack Trace
│
▼
Single Log Record

15.2 多行解析要谨慎#

如果正则配置错误:

正常日志

可能被错误合并成:

一条超长日志

所以多行规则需要根据具体应用日志格式设计。

十六、时间戳与时区#

日志分析中非常重要的一项信息:

Timestamp

例如:

2026-09-14 10:30:00

如果不同服务器:

Server A → UTC
Server B → UTC+8
Server C → UTC+9

集中后可能导致:

事件顺序混乱

因此日志系统应该明确:

时间格式
时区
时间同步

生产系统通常需要保证服务器时钟同步,否则:

Log
+
Metric
+
Trace

之间可能难以正确关联。

十七、日志与敏感信息#

日志里非常容易出现:

密码
Token
Cookie
Session ID
身份证号码
手机号
邮箱
数据库连接信息

这是一类很严重的问题:

应用正常运行
↓
日志记录敏感信息
↓
集中式日志平台
↓
更多人可以查询

于是敏感信息的暴露范围反而扩大。

因此日志系统应该遵循:

不要记录不必要的敏感信息

并同时考虑:

脱敏
权限控制
加密传输
访问审计
保留期限

例如:

password=123456

不应该直接写进生产日志。

更合理:

password=******

或者根本不记录。

十八、日志保留策略#

日志并不是:

保存越久越好

因为日志量可能非常大。

例如:

每天 20 GB

一年就是:

7 TB+

因此需要制定:

Retention Policy

例如:

热数据
→ 7 天
温数据
→ 30 天
归档
→ 180 天

具体策略取决于:

业务需求
合规要求
成本
排障周期
审计要求

同时需要明确:

删除
压缩
归档

之间的区别。

十九、日志系统的性能问题#

日志系统本身也可能成为性能瓶颈。

主要观察:

日志产生速率
采集速率
传输速率
写入速率
查询速率
存储容量

例如:

Application
│
│ 50 MB/s
▼
Agent
│
│ 40 MB/s
▼
Backend

那么:

10 MB/s

会持续积压。

进一步:

Buffer
→ 越来越大

最后可能:

Agent 磁盘写满

因此日志系统也需要监控:

Queue Length
Dropped Logs
Send Errors
Backend Latency
Disk Usage

这就是:

“监控日志系统本身”

二十、日志故障排查#

日志系统出现问题时,可以按照:

Source
↓
Agent
↓
Network
↓
Backend
↓
Query

逐层排查。

20.1 Agent 没有采集#

例如:

application.log
有新日志

但中心平台没有。

首先:

文件路径对吗?

然后:

Agent 是否运行?

再:

Agent 是否读取到新增内容?

然后:

offset 是否正确?

20.2 日志采集后没有发送#

查看:

Agent Logs

检查:

连接
认证
TLS
后端地址
队列

20.3 网络问题#

检查:

Terminal window
ss -ntp

以及:

Terminal window
nc -vz <backend> <port>

如果使用 HTTP,可以:

Terminal window
curl -v http://<backend>:<port>

20.4 后端收到了,但查不到#

这时继续检查:

索引
时间范围
字段
解析
写入状态
查询条件

一个非常常见的问题:

日志实际上已经写入

但 Dashboard 查询的:

时间范围

不正确。

二十一、一个完整的集中式日志案例#

假设有三台 Web Server:

Server 1
Nginx
Server 2
Nginx
Server 3
Nginx

每台都有:

access.log
error.log

架构:

Server 1
├── access.log
├── error.log
└── Agent
│
│
Server 2│
├── access.log
├── error.log
└── Agent
│
│
Server 3│
├── access.log
├── error.log
└── Agent
│
└──────────────┐
▼
Central Log Backend
│
▼
Search
│
▼
Visualization

最终可以统一查询:

service = nginx
status >= 500
time = last 1 hour

得到:

Server 1 → 502
Server 2 → 500
Server 3 → 502

然后再进一步根据:

upstream
request_uri
client_ip
response_time

定位问题。

二十二、日志与 Metrics 的结合#

假设 Prometheus 发现:

HTTP 500 ↑

Grafana 显示:

5xx
│
│ /\
│ / \
│______/ \____

这个时候可以根据时间:

10:30

去查询日志:

10:30 ERROR Database connection refused

于是:

Metric
→ 告诉你异常从什么时候开始
Log
→ 告诉你具体发生了什么

这就是可观测性中非常核心的协同方式。

二十三、日志与 Trace 的关系#

随着系统变成微服务:

Client
│
▼
API Gateway
│
▼
Service A
│
▼
Service B
│
▼
Database

一次用户请求可能经过:

A
B
C
D

此时只搜索:

ERROR

可能找到数千条日志。

如果日志中包含:

trace_id
span_id

就可以:

Trace ID = abc123

查询与这一次请求有关的日志:

Service A
Service B
Service C

于是:

Metrics
│
▼
异常发生
Logs
│
▼
具体错误
Traces
│
▼
请求经过哪些服务

OpenTelemetry 当前的日志设计也非常重视与其他遥测信号的关联,并支持把 Trace ID、Span ID 等上下文关联到日志中。

不过:

Trace、OpenTelemetry 和完整的 Metrics / Logs / Traces 体系属于进阶主题。

二十四、常见日志采集工具#

日志采集领域存在大量工具。

典型可以分成:

轻量 Agent
│
├── Fluent Bit
├── Vector
└── Filebeat

以及:

Telemetry Collector
│
└── OpenTelemetry Collector

例如 Fluent Bit 可以作为轻量级日志采集器,负责:

Input
→ Filter
→ Output

而 OpenTelemetry Collector 则进一步提供:

Receiver
→ Processor
→ Exporter

这样的通用遥测处理管道,并支持日志数据。

OpenTelemetry 官方日志规范明确支持:

文件日志
日志轮转
文件位置 checkpoint
Syslog
解析
处理
导出

并强调与现有日志系统和日志库进行兼容。

二十五、日志系统的整体模型#

到这里可以把日志系统串起来:

Application
│
┌────────┴────────┐
▼ ▼
Log File stdout
│ │
└────────┬────────┘
▼
Agent
│
┌─────────┼─────────┐
▼ ▼ ▼
Parse Filter Enrich
│ │ │
└─────────┼─────────┘
▼
Buffer
│
▼
Network
│
▼
Central Backend
│
┌─────────┼─────────┐
▼ ▼ ▼
Storage Index Query
│
▼
Visualization

因此集中式日志系统真正解决的是:

统一采集
统一存储
统一检索
统一分析

二十六、日志系统与 ELK / Elastic Stack#

后续文章会进入:

ELK / Elastic Stack

最经典的组合:

Log
│
▼
Logstash
│
▼
Elasticsearch
│
▼
Kibana

可以暂时这样理解:

Logstash
→ 日志采集 / 处理 / 转发
Elasticsearch
→ 日志存储 / 索引 / 查询
Kibana
→ 日志可视化 / 分析

不过现代 Elastic Stack 的实际架构可以加入:

Beats
Elastic Agent
Logstash
Elasticsearch
Kibana

等不同组件。

因此这一篇只建立:

日志为什么要集中化

下一篇再重点研究:

Elasticsearch
Logstash
Kibana

的具体实现。

二十七、日志收集与分析的运维思维#

从运维角度,一条日志真正的生命周期是:

产生
↓
采集
↓
解析
↓
传输
↓
存储
↓
索引
↓
查询
↓
分析
↓
保留 / 删除

任何一环出现问题,都可能导致:

日志丢失
日志延迟
查询不到
磁盘爆满
成本增加

所以排查日志问题时,不应该只检查:

“日志平台有没有这条日志”

而应该从源头一路追:

Application
↓
Log Source
↓
Agent
↓
Buffer
↓
Network
↓
Backend
↓
Index
↓
Query

二十八、总结#

日志系统可以先记住下面这条主线:

Log Source
│
▼
Collection Agent
│
├── Tail
├── Parse
├── Filter
├── Enrich
└── Buffer
│
▼
Central Backend
│
├── Storage
├── Index
└── Query
│
▼
Visualization

Linux 环境中:

systemd-journald
logrotate
应用日志文件
stdout / stderr

解决的是:

日志产生与本地管理

而集中式日志进一步解决:

多服务器
多容器
多服务
统一采集
统一搜索

最终可以把三类可观测数据暂时区分为:

Metrics
→ 数值变化
→ Prometheus
Logs
→ 具体事件
→ 日志系统 / ELK
Traces
→ 请求调用链
→ OpenTelemetry 等

下一阶段再把它们逐渐关联起来:

Metrics
│
▼
发现异常
│
▼
Logs
│
▼
定位错误
│
▼
Traces
│
▼
定位调用链上的具体环节

日志收集不是“把文件搬到另一台机器”,而是建立一条可持续、可检索、可分析的数据管道。对于运维而言,真正重要的是知道日志从哪里产生、经过哪些环节、在哪里可能丢失,以及如何最终利用日志定位故障。

外部参考#

监控:日志收集与分析
https://tamakara.top/posts/学习笔记/监控日志收集与分析基础/
作者
魂辛カラ
发布于
2026-09-14
许可协议
CC BY-NC-SA 4.0