systemd journal 把消息与结构化元数据一起保存在日志文件中,因此可以查询服务输出,不必先为每种应用文本设计一套解析器。但让这些消息保留足够长时间,是另一项需要明确的要求。存储位置、空间策略、轮转方式与转发路径,决定了事故发生一段时间后还有哪些证据可查。查询方便,不等于历史永远完整,也不等于数据已经离开这台机器安全保存。
坏掉的样子
默认命名空间通常使用 Storage=auto:存在 /var/log/journal 时选择持久存储,否则使用运行时存储。Storage=volatile 把文件放在 /run/log/journal 下,并不是简单借用内核的文本环形缓冲区。这些运行时文件通常会在整台机器重新启动后消失,但重启 journald 服务是不同的事件,并不必然清空文件。排查“重启后日志去哪了”之前,必须明确重启的是哪个对象。
Storage=persistent 优先使用 /var/log/journal,早期启动阶段或持久位置不可写时仍可能使用运行时存储。满足文档规定条件时,flush 操作可以转移运行时数据。因此应检查真实文件和最终生效配置,不能只因为某份配置片段里出现了 persistent,就推定所有消息已经写在预期磁盘上。路径、挂载状态与执行时点都需要对应到实际记录。
journald 本身有默认空间限制和轮转行为,不会仅仅因为管理员没有写一个轮转任务就无限增长。不过,SystemMaxUse 也不是保证文件系统永不写满的精确硬配额。运行时存储有另一组设置,可用空间限制同时起作用,其他程序也会占用磁盘,而清理时只能删除已经归档的 journal 文件。把这些条件压缩成“设置一个上限就绝对安全”,会掩盖真正的磁盘压力来源。
快速验证
在由 systemd 管理的一次性 Linux 系统中,以管理员身份运行下面的 Python 脚本。记录环境为 Debian 12、systemd 252.39。脚本创建唯一日志命名空间,显式配置持久存储,通过临时服务写入两条无害消息,最后只删除该命名空间的数据。实验中的 vacuum 会有意删除自己刚写入的旧消息:
from pathlib import Path
import json,shutil,subprocess,uuid
name='xbcatm-journal-'+uuid.uuid4().hex[:8]
config=Path('/etc/systemd')/('journald@'+name+'.conf')
directory=Path('/var/log/journal')/(Path('/etc/machine-id').read_text().strip()+'.'+name)
assert not config.exists() and not directory.exists()
def run(*args):
result=subprocess.run(args,text=True,capture_output=True,timeout=15)
assert result.returncode==0,(args,result.stderr)
return result.stdout
def journal(*args):return run('journalctl','--namespace='+name,*args)
def emit(message):
run('systemd-run','--quiet','--wait','--collect','--unit='+name+'-'+message,
'--property=LogNamespace='+name,'--property=StandardOutput=journal',
'--property=SyslogIdentifier='+name,'/usr/bin/printf',message+'\n')
journal('--sync')
def messages():
rows=journal('--no-pager','-o','json','SYSLOG_IDENTIFIER='+name)
return [json.loads(line)['MESSAGE'] for line in rows.splitlines()]
config.write_text('[Journal]\nStorage=persistent\nSystemMaxUse=16M\nRuntimeMaxUse=8M\n')
try:
emit('old');journal('--flush');journal('--sync')
assert messages()==['old'] and list(directory.glob('*.journal'))
run('systemctl','restart','systemd-journald@'+name+'.service')
assert messages()==['old']
print('persistent namespace: message survives journald service restart')
journal('--vacuum-size=1')
assert messages()==['old']
print('vacuum cannot remove the active journal file to satisfy a one-byte target')
journal('--rotate');emit('new')
assert messages()==['old','new']
assert list(directory.glob('*@*.journal'))
journal('--vacuum-size=1')
assert messages()==['new'] and not list(directory.glob('*@*.journal'))
print('after rotation: archived old message removed; active new message remains')
print('persistent files observed under /var/log/journal; no reboot durability claim')
finally:
run('systemctl','stop','systemd-journald@'+name+'.service',
'systemd-journald@'+name+'.socket','systemd-journald-varlink@'+name+'.socket')
config.unlink(missing_ok=True)
for path in (directory,Path('/run/log/journal')/directory.name,Path('/run/systemd')/('journal.'+name)):
if path.exists():shutil.rmtree(path)
第一条消息位于该命名空间的 /var/log/journal 目录中,重启对应 journald 服务后仍然能够查到。这验证了选中的存储位置,以及消息跨越日志守护进程重启仍然存在;它没有模拟突然断电,也没有证明存储设备的持久性,更没有执行整台机器重启。说明验证范围,可以避免把一个真实但有限的结果扩大成并未做过的耐久性测试。
第一次要求把占用清到一个字节,消息依然可见,因为承载它的文件还在写入。目标数字很小,但 vacuum 不会为了达到数字而删除活动文件。这直接说明,清理命令接受一个空间目标,不代表最终磁盘占用一定低于它。运维脚本如果只看命令返回成功就报告“空间已经降到目标”,会漏掉这一差别。
随后脚本轮转日志,再写入新消息。轮转把先前文件关闭成归档,同时打开替代文件;第二次清理之前,两条消息都能查到。再次 vacuum 后,归档里的旧消息消失,活动文件中的新消息仍在。轮转与删除是两项不同操作,对排障证据的影响也不同,不能把轮转本身说成已经删掉历史。
所有 journal 命令都显式选择唯一命名空间,因此不会清理默认系统日志。删除测试目录前,会先停止对应的服务和套接字实例。如果查询时省略命名空间参数,就会看默认空间,可能完全找不到这些已经成功存储的记录。查询范围错误与数据未写入,应当分别排除。
按需要保留证据
保留策略应依据事故响应窗口和实际消息量制定。流量变化时,空间上限不能承诺固定保留多少天;按时间清理的上限也不会自动预留足够空间,让每条消息都活到那个年龄。应观察实际占用与最早可查记录,尤其是一次异常故障产生大量重复日志之后,否则名义上的保留天数可能远大于真正可查询的范围。
消息也可能在保留策略生效前就已丢失。速率限制、应用输出路由、访问权限和存储不可用,都会影响哪些消息最终进入 journal。应区分“没有匹配的已存记录”与“事件从未发生”,也不要因为本机日志有权限限制,就把凭据写进消息正文。结构化字段有助于检索,并不会消除内容泄漏风险。
如果记录必须在整机丢失后仍然存在,就需要独立的采集路径并验证它。配置了转发,不等于远端已经收到或保存消息;应使用可识别的无害事件检查交付、访问控制与远端保留窗口。本机可检索日志和独立保存的外部证据,服务于相关但不同的运维需要,两者都应有实际结果支持。
延伸阅读
来源
- systemd 252 journald.conf,存储选择、默认空间限制、剩余空间与保留策略,已核验 2026-09-12。
- systemd 252 journalctl,命名空间选择、轮转、同步与归档清理,已核验 2026-09-12。
- systemd 252 systemd-journald.service,日志命名空间、传输与持久文件位置,已核验 2026-09-12。