Linux 基础体系 · 第 22/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。

Linux 日志体系:journald、rsyslog、轮转、结构化采集和磁盘治理

日志系统的职责不只是“把字符串写入文件”。在生产环境中,日志同时承担故障诊断、审计追踪、容量消耗、跨主机传输和安全取证等任务。要正确设计日志体系,必须区分以下几类问题:

  • 日志由谁接收:应用、内核、systemd 服务还是网络设备;
  • 日志以什么形式保存:二进制 journal、纯文本、JSON Lines 或远程消息;
  • 日志何时删除:按时间、大小、文件数还是磁盘剩余空间;
  • 日志在磁盘、进程和网络故障时如何变化;
  • 结构化字段是否仍然存在,还是已经退化成一段需要正则解析的文本。

现代主流 Linux 发行版通常同时包含 systemd-journald,而是否安装、启用 rsyslog 则取决于发行版和安装 profile。两者不是同一个组件,也不是天然必须同时使用。


一、先建立整体模型:日志从哪里来,经过哪些边界

一个典型的 systemd 主机可能存在以下数据流:

flowchart LR
    A[应用 stdout/stderr] --> B[systemd 服务管理器]
    B --> C[journald]
    K[内核日志] --> C
    S[本地 syslog socket /dev/log] --> C
    C --> D[/run/log/journal 或 /var/log/journal]
    C --> E[ForwardToSyslog / journal socket]
    E --> F[rsyslog]
    F --> G[文本文件]
    F --> H[远程 TCP/TLS]
    G --> I[logrotate]
    I --> G
    C --> J[journalctl 查询/导出]

这里有几个容易混淆的边界:

  1. journald 负责接收和保存 journal 记录;
  2. rsyslog 是另一套日志接收、过滤、队列、转发和写文件系统;
  3. journalctl 只是查询和管理 journal 的客户端,不是日志守护进程;
  4. logrotate 主要负责文本日志文件的轮转,不负责管理 journal 文件;
  5. systemd 可以把服务的标准输出和标准错误接入 journald,但它本身并不等于 journald。

因此,“启用 rsyslog 后日志就不再进入 journald”通常是错误理解。只要服务的输出仍由 systemd 捕获,日志通常会先进入 journald;是否再传给 rsyslog,取决于转发和输入配置。


二、journald:systemd 原生的日志接收和存储系统

2.1 journald 接收什么

systemd-journald 是 systemd 提供的日志服务,常见输入包括:

  • systemd 管理服务的 stdoutstderr
  • 内核日志;
  • /dev/log 等本地 syslog socket;
  • native journal socket;
  • 某些程序使用 sd_journal_send() 写入的带字段日志。

对于一个服务:

[Service]
ExecStart=/usr/local/bin/myapp
StandardOutput=journal
StandardError=journal

StandardOutput=journal 表示服务的标准输出连接到 journal。现代 systemd 中这通常是默认行为,但显式写出有助于说明意图。

如果程序执行:

fprintf(stdout, "connected to database\n");

journald 至少会保存一条包含 MESSAGE=connected to database 的记录。与此同时,systemd 还可以附加一些可信字段,例如:

  • _SYSTEMD_UNIT=myapp.service
  • _PID=...
  • _UID=...
  • _GID=...
  • _COMM=...
  • _EXE=...
  • _HOSTNAME=...
  • _BOOT_ID=...
  • _SYSTEMD_INVOCATION_ID=...

这些字段不是应用自己在消息正文中拼接出来的,而是日志接收链路或 systemd 根据进程上下文补充的。因此按 unit、PID、启动实例查询,通常比搜索文本中的服务名更可靠。

2.2 journal 的持久化位置

journald 有两类存储位置:

  • /run/log/journal:通常位于内存文件系统,重启后消失;
  • /var/log/journal:持久化到磁盘,重启后保留。

/etc/systemd/journald.conf/etc/systemd/journald.conf.d/*.conf 中的:

[Journal]
Storage=persistent

表示优先使用持久化存储。常见语义是:

  • persistent:优先保存到 /var/log/journal
  • volatile:只保存到 /run/log/journal
  • auto:如果持久化目录存在则持久化,否则使用运行时目录;
  • none:不保存本地 journal,但仍可能转发;
  • auto 是许多发行版的默认或常见选择,但实际默认值应以本机版本和配置为准。

在没有持久化目录的系统上,可以创建目录:

sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
sudo journalctl --flush

journalctl --flush 的作用是将运行时日志转移到持久化存储。在启用持久化后,可以用下面的命令检查:

journalctl --disk-usage

可能得到类似结果:

Archived and active journals take up 184.0M in the file system.

这只能说明 journal 文件占用的空间,不代表整个 /var/log 或整个文件系统的占用。

2.3 journal 不是普通文本文件

journal 文件是二进制、可索引的日志文件。使用 cat /var/log/journal/... 直接查看没有意义,正确入口是 journalctl

journalctl -u ssh.service --since "1 hour ago"
journalctl -p warning..alert -b
journalctl -f
journalctl -k
journalctl _PID=1234
journalctl _SYSTEMD_UNIT=myapp.service

这些命令的筛选条件含义不同:

  • -u 根据 _SYSTEMD_UNIT 查询;
  • -p 根据优先级查询;
  • -b 限定启动会话;
  • -k 只查看内核消息;
  • _PID=1234 使用结构化字段筛选;
  • -f 持续跟踪新日志。

查询最近一次启动中优先级为 warning 或更高的记录:

journalctl -b -p warning..alert -o short-iso

-o short-iso 只改变显示格式,不改变存储内容。若要导出结构化记录,应使用:

journalctl -u myapp.service -o json

输出通常是一行一个 JSON 对象,例如:

{"__CURSOR":"s=...","_SYSTEMD_UNIT":"myapp.service","PRIORITY":"6","MESSAGE":"connected to database","_PID":"812"}

字段是否存在取决于消息来源。不能假设所有记录都有 _SYSTEMD_UNIT_PID 或应用自定义字段。


三、journald 的字段、优先级和结构化信息

3.1 结构化日志是什么

结构化日志不是“日志内容长得像 JSON”这么简单。结构化日志要求事件由多个字段组成,例如:

{
  "timestamp": "2025-03-08T12:00:00Z",
  "level": "error",
  "service": "payment",
  "request_id": "abc123",
  "user_id": "u-42",
  "message": "database timeout",
  "timeout_ms": 3000
}

这里的 timeout_ms 是数值字段,request_id 是标识字段,message 是人类可读文本。下游系统可以按 service=paymentlevel=errortimeout_ms>1000 查询,而不必解析整段字符串。

journald 本身支持键值字段。应用可以使用 systemd 提供的接口写入自定义字段,例如字段名通常使用大写字母、数字和下划线,并避免与系统保留字段混淆。通过 shell 写入时,可以使用:

logger --journald <<'EOF'
MESSAGE=payment failed
PRIORITY=3
REQUEST_ID=abc123
ORDER_ID=order-001
EOF

查询自定义字段:

journalctl REQUEST_ID=abc123 -o json-pretty

前置条件是本机 logger 支持 --journald,不同 util-linux 版本的选项可能存在差异;可先执行:

logger --help

3.2 优先级不是字符串严重程度

syslog 优先级通常是数字 0 到 7:

数值 名称 含义
0 emerg 系统不可用
1 alert 必须立即处理
2 crit 严重错误
3 err 错误
4 warning 警告
5 notice 正常但重要
6 info 信息
7 debug 调试

journalctl -p warning 通常表示 warning 及更严重级别,而不是“只显示 warning”。若要明确范围:

journalctl -p warning..alert

应用自行把所有日志写成 error 会破坏告警质量;反过来,把真实错误写成 info 又会导致按优先级筛选时遗漏故障。

3.3 可信字段和不可信字段

由 systemd 或 journald 添加的 _PID_UID_EXE 等字段通常比应用正文中写入的 pid=... 更可信,因为正文可以任意伪造。但这不等于 journal 是不可篡改审计系统:

  • 具有足够权限的 root 可以删除或修改日志;
  • 本地磁盘损坏会导致日志丢失;
  • 日志在转发前可能已经被截断或限流;
  • 系统时间错误会影响时间排序;
  • 应用可以故意生成虚假业务字段。

需要强审计保证时,应配合远程只写存储、权限隔离、时间同步和访问审计,而不能只依赖本机 journal。


四、journald 的容量控制、限流与故障行为

4.1 典型配置

推荐使用独立的 drop-in 配置,而不是直接修改发行版主配置:

sudo mkdir -p /etc/systemd/journald.conf.d
sudo tee /etc/systemd/journald.conf.d/20-storage.conf >/dev/null <<'EOF'
[Journal]
Storage=persistent
SystemMaxUse=2G
SystemKeepFree=1G
MaxRetentionSec=30day
RateLimitIntervalSec=30s
RateLimitBurst=10000
ForwardToSyslog=no
EOF

然后检查并应用:

sudo systemd-analyze verify /etc/systemd/journald.conf.d/20-storage.conf
sudo systemctl restart systemd-journald

这些参数的关系不能简单理解成“日志最多占 2G”:

  • SystemMaxUse 限制持久化 journal 使用量;
  • SystemKeepFree 要求所在文件系统保留一定空闲空间;
  • MaxRetentionSec 限制日志保留时间;
  • RateLimitIntervalSecRateLimitBurst 控制重复日志的接收速率;
  • ForwardToSyslog 控制是否将 journald 记录转发到 syslog 接口。

当大小、保留时间和磁盘空闲条件同时存在时,实际占用受更严格的条件约束。并且,journal 的活动文件可能不能在当前写入时立即删除,因此短时间内看到的占用可能略高于期望值。

4.2 限流的直接代价是丢日志

假设:

RateLimitIntervalSec=30s
RateLimitBurst=10000

某个服务在 30 秒内产生 20,000 条重复消息,则超过 10,000 条的部分可能被抑制。限流的目的,是避免错误循环把整台机器的磁盘和 CPU 耗尽;它不是无损采集机制。

因此限流配置必须与日志重要性匹配:

  • 允许丢弃的高频 debug 日志可以限流;
  • 审计、计费、状态变更事件不应只依赖可能丢弃的本地日志;
  • 发现“日志突然变少”时,应检查 journald 的限流提示,而不是立即认为应用恢复正常。

限流还可能发生在其他层次:

  • 应用自身采样;
  • rsyslog 输入或动作队列;
  • 网络传输缓冲;
  • 日志平台接收端限流;
  • 磁盘写满后的写入失败。

“系统中存在日志文件”不等于“所有日志都已完整采集”。

4.3 journal 的轮转和清理

journald 有自己的文件轮转机制,不应使用 logrotate 直接操作 journal 文件。临时执行:

sudo journalctl --rotate
sudo journalctl --vacuum-time=14d
sudo journalctl --vacuum-size=1G

--rotate 先关闭当前活动文件并创建新的活动文件;--vacuum-* 再清理已经归档的旧文件。由于当前活动文件不能直接删除,通常先 rotate 再 vacuum 更容易达到预期效果。

查看当前占用:

journalctl --disk-usage

按文件数清理:

sudo journalctl --rotate
sudo journalctl --vacuum-files=10

这些命令会真实删除旧日志,生产环境执行前应确认保留要求和是否已有远程副本。临时 vacuum 不会永久替代配置;如果 SystemMaxUseMaxRetentionSec 等没有设置,下一次异常流量仍可能重新耗尽空间。


五、rsyslog:消息路由、过滤、队列和远程转发

5.1 rsyslog 解决什么问题

rsyslog 是传统 syslog 体系的现代实现,常见职责包括:

  • 接收 /dev/log 或网络 syslog;
  • 按 facility、severity、程序名和字段过滤;
  • 将日志写入文本文件;
  • 通过 TCP、UDP 或 TLS 转发;
  • 使用内存队列或磁盘队列吸收短时故障;
  • 按规则将不同应用写入不同文件。

它不是 journald 的“文件格式插件”。rsyslog 有自己的输入模块、规则处理和动作队列。

常见输入方式:

  • imuxsock:读取本地 syslog socket;
  • imjournal:从 systemd journal 读取;
  • imtcpimudp:接收网络 syslog;
  • imfile:监控普通文本文件。

5.2 避免重复采集

如果 journald 将消息转发到 rsyslog,同时 rsyslog 又通过 imjournal 读取同一批 journal,可能产生重复消息。实际发行版配置不同,必须检查:

grep -R -E 'imjournal|imuxsock|ForwardToSyslog' \
  /etc/rsyslog.conf /etc/rsyslog.d /etc/systemd/journald.conf \
  /etc/systemd/journald.conf.d 2>/dev/null

设计时应明确选择一种主要路径:

应用 stdout/stderr
    -> journald
    -> rsyslog 的 syslog 输入
    -> 文件或远端

或者:

应用 stdout/stderr
    -> journald
    -> rsyslog imjournal
    -> 文件或远端

不要在不了解发行版默认配置的情况下,同时启用多个路径。

5.3 用 rsyslog 将应用分流到独立文件

例如应用使用 logger -t myapp 或 syslog API 设置了程序名 myapp,可以创建:

sudo tee /etc/rsyslog.d/30-myapp.conf >/dev/null <<'EOF'
if ($programname == "myapp") then {
    action(type="omfile" file="/var/log/myapp.log")
    stop
}
EOF

验证语法:

sudo rsyslogd -N1

预期成功结果通常包含类似:

rsyslogd: End of config validation run. Bye.

然后重启或重新加载:

sudo systemctl restart rsyslog
sudo systemctl status rsyslog --no-pager

stop 表示匹配后停止继续处理后续规则,但不影响这条消息已经进入 journald。配置生效后,可以发送测试消息:

logger -t myapp -p user.info "structured path test"
sudo tail -n 1 /var/log/myapp.log

如果文件没有内容,应沿链路排查:

  1. logger 是否成功发送;
  2. journald 是否收到:journalctl -t myapp -n 10
  3. ForwardToSyslogimjournal 是否配置;
  4. rsyslog 是否运行;
  5. /var/log 权限和文件系统是否可写;
  6. 规则是否被更早的 stop 截断。

5.4 队列和故障路径

rsyslog 的动作可能暂时不可用,例如远端网络中断、目标文件系统只读或磁盘已满。此时动作可以进入队列:

  • 内存队列:速度快,但进程重启后可能丢失;
  • 磁盘辅助队列:能跨越较长故障,但会消耗磁盘;
  • 阻塞行为:可能反过来拖慢日志处理链路。

远程转发时,UDP 通常不提供可靠交付;TCP 能减少网络丢包,但仍需要处理连接断开、服务端拒收和队列溢出。TLS 解决传输机密性和服务器身份验证问题,但不自动保证消息永不丢失。

日志系统的可靠性必须明确目标:

可接受丢失:
  本地限流 -> 内存队列 -> UDP

更强传输保证:
  本地队列 -> TCP/TLS -> 远端持久化

后者仍不是绝对的“恰好一次”语义。断线重连、重试和进程恢复可能造成重复,接收端应使用时间戳、主机名、消息 ID 或应用 request ID 做去重和关联。


六、日志轮转:journald 的 vacuum 与文本文件的 logrotate

6.1 轮转不只是改文件名

日志轮转通常包含几个动作:

  1. 让写入者关闭或重新打开当前文件;
  2. 将当前文件改名,例如 app.log 变为 app.log.1
  3. 压缩旧文件;
  4. 删除超过保留数量或时间的文件;
  5. 创建新文件并设置权限。

对于 journald,这些动作由 journald 自己完成;对于 rsyslog 写出的普通文本文件,通常由 logrotate 完成。

6.2 一个文本日志轮转示例

/var/log/myapp.log {
    daily
    size 100M
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    create 0640 root adm
    sharedscripts
    postrotate
        systemctl kill -s HUP rsyslog.service >/dev/null 2>&1 || true
    endscript
}

语义是:

  • daily:轮转检查周期为每天;
  • size 100M:超过大小时允许触发轮转,具体是否立即执行还受 logrotate 调度影响;
  • rotate 14:保留 14 个旧文件;
  • create 0640 root adm:创建新文件时的权限和所有者;
  • compress:压缩旧文件;
  • delaycompress:最新的旧文件暂不压缩;
  • postrotate:轮转后通知 rsyslog 重新打开文件。

执行前先预演:

sudo logrotate -d /etc/logrotate.conf

-d 只显示计划,不真正执行。强制执行一次:

sudo logrotate -f /etc/logrotate.conf

检查结果:

ls -lh /var/log/myapp.log*
sudo journalctl -u rsyslog.service -n 50 --no-pager

6.3 为什么不优先使用 copytruncate

copytruncate 会先复制当前文件,再把原文件截断。它不要求日志进程重新打开文件,因此对无法重载的程序比较方便,但存在并发窗口:

  • 复制期间新写入的数据可能落在复制前后不同位置;
  • 截断期间可能丢失一部分写入;
  • 高并发日志下容易出现重复或缺失;
  • 大文件复制会造成额外 I/O。

如果写入者支持 HUP、专用 reopen 命令或平滑重载,优先使用“改名 + 新建 + 通知重新打开”。如果必须使用 copytruncate,应明确接受其非原子性,并通过测试确认应用行为。

还有一个常见问题:进程继续持有已经删除的旧文件。此时 du 看不到该文件,但 df 仍显示空间未释放。可以检查:

sudo lsof +L1

如果看到 rsyslog 或应用持有 (deleted) 文件,通常需要让进程重新打开日志,或者重启相关服务。


七、结构化采集:保留字段比“事后解析文本”更重要

7.1 stdout JSON 与 journal 字段不是同一层

应用可以向 stdout 输出一行 JSON:

{"level":"error","request_id":"abc123","latency_ms":3021,"message":"database timeout"}

journald 可能把整行放在:

MESSAGE={"level":"error","request_id":"abc123",...}

这仍然比普通文本更容易下游解析,但从 journald 视角看,JSON 只是 MESSAGE 字符串;request_id 并没有自动变成 journal 顶层字段。

如果应用能直接通过 journal API 写入字段,则查询更自然:

REQUEST_ID=abc123
LATENCY_MS=3021
MESSAGE=database timeout

工程上常见的两种策略是:

  • 应用直接写 journal 字段;
  • 应用输出 JSON,由采集器解析 MESSAGE 后建立下游字段。

不能因为命令 journalctl -o json 能输出 JSON,就认为原始日志已经具备完整结构化语义;这个选项也会把普通文本包装在 MESSAGE 字段中。

7.2 使用 journalctl 导出结构化数据

将最近一小时的服务日志导出为 JSON:

journalctl -u myapp.service \
  --since "1 hour ago" \
  -o json \
  --no-pager > myapp.journal.jsonl

这里每行通常是一个 JSON 对象,适合被脚本或日志管道逐行读取。若要保留所有字段并供程序消费,应避免使用 short 等面向人类阅读的格式。

检查某条记录的完整字段:

journalctl -u myapp.service -n 1 -o json-pretty

json-pretty 适合人工检查,不适合严格的 JSON Lines 流程,因为格式化后的一个对象可能跨越多行。

7.3 结构化日志的边界

结构化日志也有失败模式:

  • 多行异常堆栈破坏“一行一个事件”的约定;
  • 用户输入中的换行和引号未正确转义;
  • 字段类型不一致,例如同一字段有时是数字、有时是字符串;
  • request ID 在服务之间没有传递;
  • 时区、时间精度和时钟跳变未定义;
  • 把密码、令牌和完整请求体写入日志造成泄露。

“把所有内容编码成 JSON”并不能自动解决这些问题。需要明确字段 schema、敏感字段策略、最大消息长度和异常堆栈编码方式。对于机器采集,建议确保每条事件包含稳定的事件时间、服务标识、严重程度和关联 ID;对于人类诊断,再保留可读的 message


八、磁盘治理:从配额公式到故障恢复

8.1 先算总预算,而不是只设置一个参数

设日志所在文件系统可用于日志的总预算为 BB,需要保留的安全空闲空间为 FF,journald 配额为 JJ,rsyslog 文本日志和轮转文件的上限为 RR,队列和临时文件可能使用 QQ,则必须满足:

J+R+QBFJ + R + Q \leq B - F

直觉是:只限制 journald 并不能限制 /var/log/app.log、压缩文件、rsyslog 磁盘队列和应用自建日志目录。

例如:

文件系统容量:20 GiB
要求保留空闲:4 GiB
日志预算:16 GiB

journald:2 GiB
应用文本日志:8 GiB
rsyslog 磁盘队列:4 GiB
临时和误差:2 GiB

总和为 16 GiB,刚好达到预算上限。生产中还应为文件系统元数据、inode、系统升级和临时文件留出额外空间,而不是把所有可见空间都分给日志。

8.2 监控不能只看 df

至少应同时观察:

df -h /var
df -i /var
journalctl --disk-usage
sudo du -xhd1 /var/log | sort -h
sudo lsof +L1

四个命令分别回答不同问题:

  • df -h:文件系统块空间是否接近耗尽;
  • df -i:inode 是否耗尽;
  • journalctl --disk-usage:journal 文件占用多少;
  • du:目录中可见文件占用多少;
  • lsof +L1:是否有进程持有已删除但未释放的文件。

如果 df 很高而 du 很低,常见原因是 deleted-open 文件;如果 journalctl --disk-usage 很低但 /var 很满,问题可能在应用文件、rsyslog 队列、容器日志或其他目录。

8.3 磁盘写满时会发生什么

磁盘耗尽不是单纯的“日志停止写入”:

  1. journald 可能无法继续持久化记录;
  2. rsyslog 的文件动作会失败并进入重试或暂停;
  3. 应用写文件可能失败;
  4. systemd 服务的标准输出管道可能受到接收端处理能力影响;
  5. 数据库、包管理器和临时文件操作也可能失败;
  6. 如果日志和业务数据位于同一文件系统,日志可能间接导致业务中断。

应急清理应先确认日志来源,再选择动作:

sudo journalctl --rotate
sudo journalctl --vacuum-size=500M

对于文本日志,应优先执行正常轮转;不要直接删除当前正在写入的文件。若必须立即释放空间,删除旧的压缩轮转文件通常比删除活动文件风险低。任何清理动作之后都要验证:

df -h /var
systemctl --failed
journalctl -p err..alert -b --no-pager

如果发现服务已经失败,磁盘恢复只是第一步,还需要确认服务是否因写日志失败、数据库无法写入或其他连带错误而退出。


九、权限、敏感信息和远程采集

9.1 读取权限不是统一的

普通用户通常不能读取所有 journal 字段。常见做法是将诊断用户加入 systemd-journal 组:

sudo usermod -aG systemd-journal alice

用户重新登录后检查:

id alice
sudo -u alice journalctl -n 10

发行版对日志目录组、ACL 和默认权限可能不同。/var/log/myapp.log0640 root adm 也只是示例,实际读取组应根据本机运维角色设置。日志经常包含 IP、路径、用户标识、请求参数和异常堆栈,不能因为它是“诊断数据”就默认对所有用户开放。

9.2 远程传输的取舍

远程发送前应考虑:

  • 日志是否包含凭据、令牌、Cookie 或个人数据;
  • 是否需要 TLS;
  • 是否验证远端证书和主机身份;
  • 本地队列最多占用多少空间;
  • 远端不可用时是阻塞、丢弃还是落盘;
  • 是否需要去重和重放检测;
  • 远端时间和本机时间是否同步。

远程采集不是本地轮转的替代品。远端网络中断时,本地仍需要有有限的缓冲和清理策略;否则为了“保证不丢日志”而无限增长的队列,最终会把本机磁盘耗尽。


十、一个可验证的端到端排查流程

myapp.service 为例,可以按数据流逐层验证:

第一步:确认服务输出路径

systemctl cat myapp.service
systemctl status myapp.service --no-pager

检查 StandardOutputStandardError 是否被重定向到文件、journal 或其他目标。

第二步:确认 journald 是否收到

journalctl -u myapp.service -n 20 --no-pager
journalctl -u myapp.service -f

如果服务状态正常但这里没有日志,检查应用是否真的写 stdout/stderr、是否自行写了其他文件,以及服务是否运行在容器或独立日志命名空间中。

第三步:确认结构化字段

journalctl -u myapp.service -n 1 -o json-pretty

重点看:

  • _SYSTEMD_UNIT
  • _PID
  • PRIORITY
  • MESSAGE
  • 应用自定义字段;
  • 时间字段和时区。

如果 MESSAGE 是一整段 JSON,需要确认下游是否会再次解析它。

第四步:确认 rsyslog 是否收到

systemctl status rsyslog --no-pager
sudo rsyslogd -N1
logger -t myapp "rsyslog path test"
journalctl -t myapp -n 5 --no-pager

如果 journald 有测试消息而文本文件没有,继续检查 rsyslog 输入模块、规则顺序、stop、文件权限和 action 错误。

第五步:确认轮转后写入仍正常

sudo logrotate -f /etc/logrotate.conf
logger -t myapp "post-rotation test"
tail -n 5 /var/log/myapp.log
ls -l /var/log/myapp.log*

如果消息仍写入旧文件,说明进程没有重新打开文件;如果新文件权限错误,检查 create 配置和 rsyslog 运行身份。

第六步:确认磁盘清理有效

journalctl --disk-usage
df -h /var
sudo du -xhd1 /var/log | sort -h
sudo lsof +L1

清理后如果 df 没有下降,优先检查 deleted-open 文件;如果 du 仍然很高,说明还有其他目录或轮转文件占用空间。


十一、常见误解与生产取舍

误解一:journal 是文本文件,所以可以用 grep 直接处理

journal 的存储格式是二进制。应该使用 journalctl 的过滤和输出选项;若要交给脚本,选择 -o json 或其他稳定格式。直接依赖默认人类可读输出,容易因版本、字段显示和多行文本变化而失效。

误解二:logrotate 可以管理所有 Linux 日志

logrotate 适合普通文本文件,journald 文件由 journald 管理。对 journal 文件使用通用文件轮转可能破坏索引、并发写入或权限语义。两种日志存储必须分别配置和验证。

误解三:启用 rsyslog 就能保证日志不丢

journald 限流、rsyslog 队列溢出、磁盘满、网络中断和远端拒收都可能丢日志。可靠性来自完整链路设计,而不是某一个守护进程名称。

误解四:轮转数量就是磁盘上限

rotate 14 只约束匹配到的文件集合。应用可能写入其他文件,rsyslog 可能有磁盘队列,journal 也有自己的目录。磁盘治理必须按文件系统总量核算。

误解五:日志越详细越好

详细日志提高诊断能力,也提高磁盘、CPU、网络和隐私风险。更合理的做法是区分:

  • 默认生产级别;
  • 临时 debug 开关;
  • 高价值审计事件;
  • 高频重复事件的采样与限流;
  • 故障期间的临时扩容和事后恢复。

最终目标不是让机器“永远保存所有字符串”,而是让日志在可接受的成本和风险内,保留足以解释系统状态变化的证据。journald 适合统一接收和按字段查询,rsyslog 适合兼容 syslog、路由、写文件和远程转发;journal vacuum 与 logrotate 分别管理不同存储;结构化字段决定后续检索质量;磁盘预算和故障路径则决定整套日志系统是否能在生产事故中继续工作。


系列导航与关联阅读

官方资料

本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。