systemd 教程 / 日志高级配置(journald)
日志高级配置(journald)
概述
systemd-journald 是 systemd 的日志服务守护进程,负责收集、存储和管理系统日志。与传统的 rsyslog 不同,journald 使用二进制格式存储日志,支持结构化查询、速率限制、日志签名等高级功能。本文深入介绍 journald 的高级配置和生产实践。
journald.conf 配置详解
配置文件位置
/etc/systemd/journald.conf # 主配置文件
/etc/systemd/journald.conf.d/*.conf # drop-in 配置
/run/systemd/journald.conf.d/*.conf # 运行时配置
[Journal] 段完整参数
# /etc/systemd/journald.conf
[Journal]
# 存储模式
Storage=auto
# 持久化目录
# SystemJournalPath=/var/log/journal
# RuntimeJournalPath=/run/log/journal
# 压缩
Compress=yes
# 日志大小限制
SystemMaxUse=500M
SystemKeepFree=1G
SystemMaxFileSize=50M
SystemMaxFiles=100
# 运行时日志大小限制
RuntimeMaxUse=200M
RuntimeKeepFree=100M
RuntimeMaxFileSize=25M
RuntimeMaxFiles=50
# 日志保留时间
MaxRetentionSec=3month
MaxFileSec=1week
# 速率限制
RateLimitIntervalSec=30s
RateLimitBurst=10000
# 转发到 console
ForwardToConsole=no
ForwardToWall=yes
# TTY
# TTYPath=/dev/console
# 日志级别
# MaxLevelStore=debug
# MaxLevelSyslog=debug
# MaxLevelConsole=notice
# MaxLevelWall=emerg
# 同步写入
SyncIntervalSec=5m
# 分片大小
SplitMode=uid
# Seal(前向安全密封)
Seal=yes
# 读取权限
ReadKMsg=yes
# Audit
Audit=yes
存储模式详解
Storage 参数
| 值 | 说明 |
|---|---|
volatile | 仅存储在内存中(/run/log/journal/) |
persistent | 持久化存储到磁盘(/var/log/journal/) |
auto | 如果 /var/log/journal/ 存在则持久化,否则仅内存 |
none | 不存储日志(不推荐) |
volatile 模式
日志仅存储在内存中,重启后丢失:
[Journal]
Storage=volatile
RuntimeMaxUse=200M
适用场景:
- 临时系统或容器
- 日志已转发到远程服务器
- 资源受限的嵌入式设备
persistent 模式
日志持久化到磁盘,重启后保留:
# 创建持久化日志目录
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
# 重启 journald 生效
sudo systemctl restart systemd-journald
[Journal]
Storage=persistent
SystemMaxUse=2G
SystemKeepFree=4G
SystemMaxFileSize=100M
💡 提示:在生产环境中,建议使用 persistent 模式,便于日志审计和故障排查。
日志大小管理
参数说明
| 参数 | 说明 | 建议值 |
|---|---|---|
SystemMaxUse | 持久化日志最大使用空间 | 500M ~ 4G |
SystemKeepFree | 保持的最小可用空间 | 1G ~ 5G |
SystemMaxFileSize | 单个日志文件最大大小 | 50M ~ 200M |
SystemMaxFiles | 日志文件最大数量 | 50 ~ 200 |
RuntimeMaxUse | 运行时日志最大空间 | 100M ~ 500M |
RuntimeKeepFree | 运行时保持的可用空间 | 50M ~ 200M |
MaxRetentionSec | 日志保留时间 | 3month / 6month / 1year |
MaxFileSec | 单个日志文件的时间跨度 | 1day / 1week / 1month |
生产环境配置示例
# /etc/systemd/journald.conf.d/production.conf
[Journal]
Storage=persistent
Compress=yes
# 限制日志总大小
SystemMaxUse=4G
SystemKeepFree=10G
SystemMaxFileSize=200M
SystemMaxFiles=50
# 保留 6 个月
MaxRetentionSec=6month
MaxFileSec=1week
# 速率限制
RateLimitIntervalSec=30s
RateLimitBurst=20000
# 查看当前日志大小
journalctl --disk-usage
# 查看日志统计信息
journalctl --header
手动清理日志
# 清除所有日志(危险!)
sudo journalctl --vacuum-time=1s
# 保留最近 7 天的日志
sudo journalctl --vacuum-time=7d
# 限制日志大小为 500M
sudo journalctl --vacuum-size=500M
# 限制日志文件数量
sudo journalctl --vacuum-files=10
# 清除特定服务的日志
sudo journalctl --unit=nginx.service --vacuum-time=1d
速率限制
防止日志风暴
当服务出现错误循环时,可能会产生大量日志,导致磁盘空间耗尽:
[Journal]
# 30 秒内最多 10000 条日志
RateLimitIntervalSec=30s
RateLimitBurst=10000
参数说明
| 参数 | 说明 | 默认值 |
|---|---|---|
RateLimitIntervalSec | 速率限制的时间窗口 | 30s |
RateLimitBurst | 时间窗口内最大日志条数 | 10000 |
查看被限制的日志
# 查看 journald 自身状态
journalctl --header
# 查看被丢弃的日志条数
sudo journalctl -b | grep "Suppressed"
⚠️ 注意:如果日志被速率限制,会在日志中出现类似以下消息:
Jun 15 10:23:45 hostname systemd-journald[123]: Suppressed 5432 messages from user.slice
调整速率限制
对于日志量大的服务(如 Web 服务器),可以适当提高限制:
[Journal]
RateLimitIntervalSec=10s
RateLimitBurst=50000
💡 提示:如果需要完全禁用速率限制(不推荐),设置 RateLimitBurst=0。
日志转发
转发到控制台
[Journal]
ForwardToConsole=yes
TTYPath=/dev/console
MaxLevelConsole=info
转发到 syslog
[Journal]
ForwardToSyslog=yes
转发到内核日志缓冲区
[Journal]
ForwardToKMsg=no
转发到 wall(终端广播)
[Journal]
ForwardToWall=yes
远程日志
systemd-journal-remote
将日志从多台机器集中到一台日志服务器。
日志服务器配置
# 安装 systemd-journal-remote
sudo apt install systemd-journal-remote # Debian/Ubuntu
sudo dnf install systemd-journal-remote # Fedora/RHEL
# /etc/systemd/journal-remote.conf
[Remote]
# 监听端口
SplitMode=host
ServerKeyFile=/etc/ssl/private/journal-remote.key
ServerCertificateFile=/etc/ssl/certs/journal-remote.pem
TrustedCertificateFile=/etc/ssl/ca/trusted.pem
# 启动接收服务
sudo systemctl enable --now systemd-journal-remote.socket
sudo systemctl enable --now systemd-journal-remote.service
客户端配置(上传日志)
# /etc/systemd/journal-upload.conf
[Upload]
URL=https://logserver.example.com:19532
ServerKeyFile=/etc/ssl/private/journal-upload.key
ServerCertificateFile=/etc/ssl/certs/journal-upload.pem
TrustedCertificateFile=/etc/ssl/ca/trusted.pem
# 启动上传服务
sudo systemctl enable --now systemd-journal-upload.service
简化 HTTP 模式
不使用 TLS 的简化配置(仅适合内网):
# /etc/systemd/journal-upload.conf
[Upload]
URL=http://logserver.example.com:19531
⚠️ 注意:HTTP 模式不加密,仅适合可信内网环境。
日志签名(Forward Secure Sealing)
概念
Forward Secure Sealing(FSS)为日志提供完整性验证,确保日志在写入后不能被篡改。
配置 FSS
# 生成密封密钥
sudo journalctl --setup-keys
# 输出:
# Accepted sealing key is stored in /var/log/journal/*/fss.
# Verification key is: a]xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx
# Please write down the verification key and store it in a safe place.
# 验证日志完整性
sudo journalctl --verify
# 使用验证密钥验证
sudo journalctl --verify-key=xxxx-xxxx-xxxx-xxxx
# /etc/systemd/journald.conf
[Journal]
Seal=yes
⚠️ 注意:验证密钥必须安全保存,否则无法验证日志完整性。
日志导出与备份
导出为文本格式
# 导出所有日志
journalctl > all-logs.txt
# 导出特定时间段的日志
journalctl --since "2026-01-01" --until "2026-01-31" > january-logs.txt
# 导出特定服务的日志
journalctl -u nginx.service > nginx-logs.txt
# 导出本次启动的日志
journalctl -b > boot-logs.txt
导出为 JSON 格式
# JSON 格式导出
journalctl -o json > logs.json
# JSON-pretty 格式
journalctl -o json-pretty > logs-pretty.json
# 使用 jq 查询 JSON 日志
journalctl -o json | jq 'select(.PRIORITY <= 3)'
导出为二进制格式
# 导出原始日志文件
sudo cp -r /var/log/journal/ /backup/journal-backup/
# 仅导出特定机器的日志
sudo cp /var/log/journal/$(machine-id) /backup/
从备份恢复
# 将备份日志放到临时目录
sudo journalctl --directory=/backup/journal-backup/ --file=myfile.journal
# 搜索备份日志
sudo journalctl --directory=/backup/journal-backup/ -u nginx.service
日志 API(libsystemd sd_journal)
C 语言示例
#include <systemd/sd-journal.h>
#include <stdio.h>
int main(void) {
sd_journal *j;
int r;
// 打开日志
r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
if (r < 0) {
fprintf(stderr, "Failed to open journal: %s\n", strerror(-r));
return 1;
}
// 添加过滤条件
sd_journal_add_match(j, "UNIT=nginx.service", 0);
// 遍历日志
SD_JOURNAL_FOREACH(j) {
const char *data;
size_t length;
sd_journal_get_data(j, "MESSAGE", (const void **)&data, &length);
printf("%.*s\n", (int)length, data);
}
sd_journal_close(j);
return 0;
}
# 编译
gcc -o journal-reader journal-reader.c -lsystemd
Python 示例
import subprocess
import json
def query_journal(unit=None, since=None, priority=None):
cmd = ['journalctl', '-o', 'json', '--no-pager']
if unit:
cmd.extend(['-u', unit])
if since:
cmd.extend(['--since', since])
if priority:
cmd.extend(['-p', str(priority)])
result = subprocess.run(cmd, capture_output=True, text=True)
logs = []
for line in result.stdout.strip().split('\n'):
if line:
logs.append(json.loads(line))
return logs
# 使用示例
errors = query_journal(unit='nginx.service', priority=3, since='1 hour ago')
for entry in errors:
print(f"[{entry.get('_HOSTNAME', 'unknown')}] {entry.get('MESSAGE', '')}")
sd_journal API 关键函数
| 函数 | 说明 |
|---|---|
sd_journal_open() | 打开日志 |
sd_journal_close() | 关闭日志 |
sd_journal_next() | 移动到下一条 |
sd_journal_get_data() | 获取字段数据 |
sd_journal_add_match() | 添加过滤条件 |
sd_journal_add_disjunction() | 添加 OR 条件 |
sd_journal_seek_head() | 跳转到开头 |
sd_journal_seek_tail() | 跳转到末尾 |
sd_journal_get_realtime_usec() | 获取时间戳 |
sd_journal_get_cutoff_realtime_usec() | 获取日志时间范围 |
日志集中收集方案
方案 1:ELK Stack(Elasticsearch + Logstash + Kibana)
systemd-journald ──▶ journal-remote ──▶ Logstash ──▶ Elasticsearch ──▶ Kibana
方案 2:Loki + Grafana
# 使用 promtail 收集日志
# promtail 配置
scrape_configs:
- job_name: journal
journal:
json: false
max_age: 12h
path: /var/log/journal
labels:
job: systemd-journal
relabel_configs:
- source_labels: ['__journal__systemd_unit']
target_label: 'unit'
方案 3:rsyslog 转发
# /etc/rsyslog.d/journal.conf
# 从 journald 读取日志
$ModLoad imjournal
$WorkDirectory /var/lib/rsyslog
$StateFile imjournal.state
$IMJournalStateFile imjournal.state
# 转发到远程 syslog
*.* @@logserver.example.com:514
常用命令汇总
# 查看日志
journalctl # 所有日志
journalctl -b # 本次启动
journalctl -b -1 # 上次启动
journalctl -u nginx.service # 特定服务
journalctl -p err # 错误级别
journalctl --since "1 hour ago" # 最近1小时
journalctl --since "2026-01-01" # 指定日期起
journalctl -f # 实时跟踪
# 日志管理
journalctl --disk-usage # 查看磁盘占用
journalctl --vacuum-time=7d # 清理旧日志
journalctl --vacuum-size=500M # 限制大小
journalctl --verify # 验证完整性
# 输出格式
journalctl -o short # 短格式(默认)
journalctl -o verbose # 详细格式
journalctl -o json # JSON 格式
journalctl -o json-pretty # JSON 格式化
journalctl -o cat # 仅消息内容
# 服务管理
sudo systemctl restart systemd-journald
sudo systemctl status systemd-journald