1. 为什么你查不到日志?——从“system has not been booted with systemd as init system”说起
刚接触 Linux 系统管理的人,十有八九会在
journalctl
上栽第一个跟头。输入命令,回车,结果弹出一行红字:
Failed to connect to bus: Host is down
或者更常见、更让人摸不着头脑的这句:
System has not been booted with systemd as init system (PID 1). Can't operate.
这句话不是报错,是判决书——它直接宣告:你当前运行的系统,压根没用
systemd
作为初始化进程(init)。而
journalctl
是
systemd
的原生日志子系统,它不认别的“老板”。就像你拿着地铁卡去坐高铁,闸机不会吞卡,但会亮红灯告诉你:“此卡仅限地铁使用”。
这不是你的操作问题,而是环境前提被忽略了。很多新手在虚拟机里装了 Alpine Linux、Devuan 或者某些极简嵌入式发行版,它们默认用的是
OpenRC
、
runit
或
sysvinit
;还有人在 WSL1(Windows Subsystem for Linux v1)里跑 Ubuntu,WSL1 不启动真正的 init 进程,
PID 1
是
init
的占位符,
systemd
根本没机会加载。这时候你再怎么
sudo journalctl -u ssh
,它都只会礼貌而坚定地拒绝你。
我第一次遇到这个提示是在给客户部署一个国产 Linux 发行版时。客户提供的镜像基于 OpenEuler,但启用了
systemd
的兼容模式,实际
PID 1
却是
upstart
的变体。我们花了三小时排查服务启动失败,最后发现
journalctl
命令根本没连上日志总线——因为
systemd-journald.service
压根没起来。
ps -p 1 -o comm=
输出的是
init
,不是
systemd
。
所以,在谈“怎么用
journalctl
”之前,必须先做一道选择题:
你的系统是否真的以
systemd
为 init?
验证方法极其简单,就一条命令:
ps -p 1 -o comm=
如果输出是
systemd
,恭喜,你可以继续往下看;如果输出是
init
、
runit
、
openrc
、
upstart
或其他任何东西,请立刻停下。这不是
journalctl
不好用,而是你拿错了钥匙。此时你应该去查对应 init 系统的日志机制:
OpenRC
用
/var/log/
下的纯文本文件,
runit
依赖
svlogd
,
sysvinit
则靠
rsyslogd
或
syslog-ng
。强行硬套
journalctl
,只会让你陷入“命令能敲、结果为空、怀疑人生”的死循环。
提示:WSL2 用户请放心,它支持完整的
systemd(需在/etc/wsl.conf中启用),而 WSL1 永远不支持。如果你在 WSL 中看到Can't operate,第一反应不是重装系统,而是检查wsl --list --verbose确认版本,并升级到 WSL2。
2. journalctl 不是 cat,它是日志的“全息投影仪”
很多人把
journalctl
当成
cat /var/log/syslog
的替代品,这是对它能力的最大低估。
cat
是单向读取一个静态文件;
journalctl
是与一个持续运行、结构化、带元数据、可实时订阅的日志守护进程(
systemd-journald
)进行双向通信。它不是在“看日志”,而是在“调阅档案卷宗”。
它的核心能力体现在三个维度上,每个维度都彻底颠覆了传统日志查看方式:
第一,结构化元数据(Structured Metadata)
每条日志条目都不是一行纯文本,而是一个键值对集合。除了
MESSAGE=
(你看到的正文),还自带
PRIORITY=
(日志级别)、
SYSLOG_IDENTIFIER=
(服务名)、
UNIT=
(systemd 单元名)、
BOOT_ID=
(本次启动唯一ID)、
_HOSTNAME=
、
_PID=
、
_UID=
、
_COMM=
(进程名)等数十个字段。这些字段不是装饰,是精准过滤的基石。比如你想查所有由
nginx
进程产生的、且 UID 为 33(www-data)的错误日志,传统
grep
要写三重管道,而
journalctl
一行搞定:
journalctl SYSLOG_IDENTIFIER=nginx _UID=33 PRIORITY=3
第二,实时流式订阅(Live Streaming)
tail -f
只能追加新行;
journalctl -f
则能实时接收
journald
推送的、经过完整解析和格式化的结构化事件流。它甚至能跨重启保持连接——只要
journald
在运行,
-f
就不会断。我在调试一个每小时才触发一次的定时任务时,就开着
journalctl -u my-cron-job -f
,然后去喝杯咖啡,回来时日志已经带着时间戳、进程ID、退出码,整整齐齐躺在终端里,比守着
tail
强十倍。
第三,持久化与滚动策略(Persistent Storage & Rotation)
journald
默认将日志存于内存(
/run/log/journal/
),系统重启即丢。但一旦你创建了
/var/log/journal/
目录并重启
journald
,它就会自动将日志持久化到磁盘,并按大小(默认 10% 磁盘空间或 4G)和时间(默认保留最近 2 周)自动轮转。这意味着
journalctl
能查到的不仅是“现在”,更是“过去两周内每一次启动、每一个服务崩溃、每一次内核恐慌”的完整记录。这种时间纵深,是
/var/log/
下零散文本日志永远无法提供的。
这三点共同构成了
journalctl
的不可替代性:它不是一个命令,而是一套日志操作系统。理解这一点,才能避免把它用成“高级
cat
”,也才能真正释放其威力。
3. 从入门到精通:一条命令的七种变形实战
journalctl
的语法看似简单,实则精妙。它的核心逻辑是:
所有选项都是对日志流的“筛选器”和“修饰器”
。没有“主命令”,只有“组合拳”。下面我用一条真实生产环境中的故障排查命令,拆解其背后七层逻辑:
journalctl -u nginx.service --since "2024-05-20 14:00:00" --until "2024-05-20 14:05:00" -p err -n 100 -o json-pretty | jq '.MESSAGE | select(contains("502"))'
3.1
-u nginx.service
:单元级精准定位
-u
(
--unit
)是
journalctl
最常用也最强大的筛选器。它不是简单地 grep “nginx”,而是通过
UNIT=
元数据字段,只选取由
nginx.service
这个 systemd 单元产生的所有日志。这意味着:
-
它会包含
nginx主进程、工作进程、以及nginx启动时 fork 的所有子进程(如nginx: worker process)的日志; -
它会自动排除
nginx的配置检查日志(nginx -t产生的,属于systemd自身调用,UNIT=是systemd); -
它甚至能区分
nginx.service和nginx.socket(如果启用了 socket 激活),后者负责监听端口,前者负责处理请求。
注意:
-u后面的名称必须是 systemd 单元的 完整名称 。nginx是别名,nginx.service才是真名。systemctl list-units --type=service | grep nginx是确认真名的最快方法。
3.2
--since
与
--until
:时间轴上的手术刀
--since
和
--until
不是简单的字符串匹配,而是
journald
内置的时间解析引擎。它支持:
-
绝对时间:
"2024-05-20 14:00:00"(注意引号,避免 shell 解析空格) -
相对时间:
"1 hour ago"、"yesterday"、"2 days ago" -
模糊时间:
"today"、"this week"
关键在于,
journald
的时间戳是纳秒级精度,且与系统时钟严格同步。当你用
--since "1 hour ago"
时,它不是计算当前时间减一小时,而是从日志数据库中精确查找
__REALTIME_TIMESTAMP=
字段大于该时间点的所有条目。这保证了在高并发、多服务场景下,时间筛选的绝对准确性。
3.3
-p err
:日志级别的精准狙击
-p
(
--priority
)用于过滤日志优先级。
journald
遵循 syslog 标准,共 8 级(0-7),数值越小级别越高:
-
0=emerg(紧急) -
1=alert(警报) -
2=crit(严重) -
3=err(错误)← 这是我们最常查的 -
4=warning(警告) -
5=notice(注意) -
6=info(信息) -
7=debug(调试)
-p err
等价于
-p 3
,它会返回
PRIORITY <= 3
的所有日志(即
emerg
,
alert
,
crit
,
err
)。如果你想只看
err
本身,必须用
-p 3
。这点极易混淆,我曾因此漏掉一个
crit
级别的磁盘满告警。
3.4
-n 100
:上下文的黄金比例
-n
(
--lines
)控制输出行数。
-n 100
并非只显示最后 100 行,而是从满足前述所有条件的日志流中,
截取最后 100 条
。为什么是 100?经验告诉我,少于 50 条,往往看不到完整的错误链(如
connect failed
->
retrying
->
timeout
);多于 200 条,信息过载,关键线索会被淹没。100 是一个经过上百次线上排障验证的“黄金比例”。
3.5
-o json-pretty
:结构化输出的终极形态
-o
(
--output
)定义输出格式。
json-pretty
将每条日志转换为格式化 JSON,这是与外部工具(如
jq
)集成的桥梁。它让原本隐藏在二进制日志文件中的所有元数据(
_HOSTNAME
,
_PID
,
CODE_FILE
,
CODE_LINE
)全部暴露出来。相比默认的
short
格式(只显示时间、服务名、消息),
json-pretty
是调试复杂问题的“显微镜”。
3.6
| jq '.MESSAGE | select(contains("502"))'
:日志内容的正则级过滤
jq
是 JSON 处理的瑞士军刀。这条管道命令的意思是:从每条 JSON 日志中提取
.MESSAGE
字段,并只保留其中包含字符串
"502"
的条目。这比
grep "502"
强大得多,因为:
-
grep会匹配到日志里的任意位置(比如PID=502),而jq精确到MESSAGE字段; -
jq支持正则:select(test("502.*Bad Gateway")); -
jq可以组合多个条件:select(.PRIORITY == 3 and .MESSAGE | contains("timeout"))。
3.7 组合的威力:一次命令,闭环诊断
把这七层逻辑串起来,这条命令就不再是一行指令,而是一个完整的诊断流程:
-
锁定范围
:只看
nginx.service(-u); -
划定战场
:聚焦故障发生的具体五分钟窗口(
--since/--until); -
聚焦要害
:只抓错误级别日志(
-p err); -
获取足够证据
:取 100 条以覆盖完整错误链(
-n 100); -
暴露全部细节
:转为 JSON 获取所有元数据(
-o json-pretty); -
精准定位
:在消息体中搜索
502(jq); - 输出结果 :得到一份带有完整上下文(时间、进程、主机、代码行)的精准错误报告。
这才是
journalctl
的正确打开方式。它不是让你“学会命令”,而是让你“构建诊断思维”。
4. 那些没人告诉你的坑:
journalctl
的暗礁与绕行指南
journalctl
功能强大,但它的设计哲学是“为 systemd 生态服务”,而非“为人类友好设计”。这就导致了一系列只有踩过才知道的暗礁。以下是我用血泪换来的四条铁律:
4.1 坑一:
-b
参数的“启动”陷阱
-b
(
--boot
)是查看某次启动日志的快捷方式。
journalctl -b
查看本次启动,
journalctl -b -1
查看上一次启动。听起来很完美?错。
-b
的底层逻辑是匹配
BOOT_ID=
元数据。而
BOOT_ID
是由
systemd
在启动时生成的 UUID。问题来了:如果你的系统启用了
kexec
(快速内核切换),或者使用了某些容器运行时(如
podman
的
--systemd
模式),
BOOT_ID
可能不会改变!这意味着
journalctl -b -1
可能返回的不是上一次启动的日志,而是同一
BOOT_ID
下的另一批日志。我曾在一台 KVM 虚拟机上,因
kexec
导致
journalctl -b -1
返回了三天前的日志,差点误判故障时间。
绕行指南
:永远用
journalctl --list-boots
先确认。它会列出所有已知启动记录及其时间戳和
BOOT_ID
。找到你真正想查的那一次,然后用
journalctl -b <BOOT_ID>
精确指定,而不是依赖
-1
、
-2
这样的相对索引。
4.2 坑二:
/var/log/journal/
目录权限的“静默失败”
你兴冲冲地
sudo mkdir /var/log/journal/
,然后
sudo systemctl restart systemd-journald
,以为日志从此就持久化了。结果
journalctl --disk-usage
显示还是
0B
。原因?
journald
对
/var/log/journal/
的所有权有严格要求:它必须属于
root:systemd-journal
,且权限为
0755
。如果你只是
mkdir
,目录所有者是
root:root
,
journald
会默默放弃写入,连日志都不记。
绕行指南 :创建目录后,务必执行:
sudo mkdir -p /var/log/journal
sudo chown root:systemd-journal /var/log/journal
sudo chmod 0755 /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
最后一行
systemd-tmpfiles
是关键,它会根据
/usr/lib/tmpfiles.d/systemd.conf
中的规则,确保目录权限和属组完全正确。
4.3 坑三:
_SYSTEMD_UNIT=
与
UNIT=
的“身份迷雾”
journalctl -u sshd.service
能查到
sshd
的日志,但有时你会发现,
sshd
的子进程(如
sshd: user@pts/0
)日志却不在其中。这是因为
sshd
的子进程继承的是父进程的
UNIT=
,但
journald
会给它们打上
_SYSTEMD_UNIT=
标签,指向
sshd@.service
(实例化单元)。
-u
只匹配
UNIT=
,不匹配
_SYSTEMD_UNIT=
。
绕行指南
:要查所有
sshd
相关日志(包括子进程),必须用:
journalctl '_SYSTEMD_UNIT=sshd@.service' '_SYSTEMD_UNIT=sshd.service'
或者更暴力的全局搜索:
journalctl | grep -i sshd
但后者会丢失结构化优势。最佳实践是:在服务配置文件(
/etc/systemd/system/sshd.service
)中,添加
SyslogIdentifier=sshd
,这样所有子进程都会带上
SYSLOG_IDENTIFIER=sshd
,用
-u
就能一网打尽。
4.4 坑四:
--all
与
--no-pager
的“性能雪崩”
journalctl --all
会强制显示所有日志,包括那些被
journald
认为“不重要”的二进制 blob(如内核模块的原始 dump)。
journalctl --no-pager
会禁用分页器,把所有内容一股脑刷到终端。当这两个参数一起用时,尤其是在日志量巨大的生产服务器上,后果是灾难性的:终端卡死、内存暴涨、SSH 连接超时断开。我见过一次,
journalctl --all --no-pager | wc -l
统计出 2700 万行日志,
less
分页器直接崩溃。
绕行指南 :永远遵循“最小权限原则”:
-
查日志,先用
-n 100限制行数; -
查特定服务,必用
-u或-p; -
真需要
--all,务必配合-o json和jq做流式处理,而不是cat到屏幕; -
--no-pager只在管道中使用(如journalctl -u nginx | grep error),绝不单独使用。
这些坑,文档里不会写,教程里不会提,只有在深夜被报警电话叫醒、对着一片空白的终端发呆时,你才会真正记住它们。
5. 超越命令行:
journalctl
的进阶武器库
当
journalctl
成为你肌肉记忆的一部分,下一步就是把它从“命令行工具”升维为“运维基础设施”。这需要三件套:配置、集成、自动化。
5.1 配置:
/etc/systemd/journald.conf
的深度调优
journald.conf
是
journald
的大脑。默认配置(
/etc/systemd/journald.conf
)是为桌面环境优化的,对服务器而言,它太“温柔”了。以下是生产环境必改的五项:
| 配置项 | 默认值 | 推荐值 | 作用与原理 |
|---|---|---|---|
Storage=
|
auto
|
persistent
|
强制日志写入
/var/log/journal/
。
auto
在无
/var/log/journal/
时退化为
volatile
(内存存储),重启即丢。
persistent
是持久化的开关。
|
SystemMaxUse=
|
10%
|
2G
|
限制日志总大小。
10%
在 1TB 磁盘上就是 100G,对日志服务器是灾难。固定
2G
更可控,且
journald
会自动轮转删除最老日志。
|
MaxRetentionSec=
|
1month
|
30d
|
限制日志最长保存时间。
1month
是模糊值,
30d
是精确值,避免因月份天数不同导致轮转不一致。
|
ForwardToSyslog=
|
no
|
yes
|
将
journald
日志同时转发给传统
rsyslogd
。这是双保险:
journalctl
提供结构化查询,
rsyslogd
提供长期归档和远程发送能力。
|
Compress=
|
yes
|
yes
|
启用 LZ4 压缩。
journald
的压缩率极高(通常 90%+),开启后几乎不增加 CPU 开销,却能大幅节省磁盘。
|
修改后,必须执行
sudo systemctl restart systemd-journald
生效。注意:重启
journald
不会丢失正在内存中的日志,它会先刷盘再重启。
5.2 集成:
journalctl
+
rsyslogd
的黄金搭档
journald
擅长实时、结构化、短期查询;
rsyslogd
擅长长期、归档、远程传输。两者不是竞争关系,而是父子关系。
journald
的
ForwardToSyslog=yes
会让它把所有日志,通过
AF_UNIX
socket 发送给
rsyslogd
。
rsyslogd
则可以:
-
将日志按
UNIT=分类,写入/var/log/nginx/、/var/log/mysql/等独立目录; - 将日志加密后发送到远程 SIEM(安全信息与事件管理)平台;
- 对日志进行二次过滤和富化(如添加地理位置、用户角色)。
我的标准配置是:
journald
负责本地 30 天内的所有日志查询;
rsyslogd
负责将
auth.log
、
kern.log
等关键日志实时同步到中央日志服务器。这样,既享受了
journalctl
的闪电速度,又保有了传统日志生态的成熟稳定。
5.3 自动化:用
journalctl
构建自愈脚本
journalctl
的结构化输出,让它成为自动化脚本的绝佳数据源。下面是一个检测
nginx
服务异常并自动恢复的 Bash 脚本核心逻辑:
#!/bin/bash
# 检查过去5分钟内 nginx 的 err 级别日志数量
ERROR_COUNT=$(journalctl -u nginx.service --since "5 minutes ago" -p err -n 1000 --no-pager 2>/dev/null | wc -l)
if [ "$ERROR_COUNT" -gt 10 ]; then
echo "$(date): nginx ERROR count ($ERROR_COUNT) > 10, restarting..." >> /var/log/nginx-autoheal.log
# 记录详细日志用于事后分析
journalctl -u nginx.service --since "5 minutes ago" -p err -n 100 --no-pager >> /var/log/nginx-autoheal.log
# 执行恢复动作
systemctl restart nginx.service
# 发送告警
echo "ALERT: nginx auto-restarted due to high error rate" | mail -s "NGINX AUTOHEAL" admin@example.com
fi
这个脚本的关键在于:它不依赖
systemctl is-active
这种“表面健康检查”,而是直接分析
journald
中的真实错误信号。
nginx
进程可能还在,但
502
错误已经泛滥,
is-active
会返回
active
,而
journalctl
会如实汇报。这就是结构化日志带来的“真相优势”。
提示:此类脚本应通过
systemd timer(而非cron)来调度,因为timer与journald同属systemd生态,能获得更精确的启动时间和日志关联。
6. 国产 Linux 发行版中的
journalctl
实战差异
国内主流 Linux 发行版(如 openEuler、Kylin、UOS、Anolis OS)均已全面拥抱
systemd
,
journalctl
是标配。但它们在日志策略上,有着鲜明的本土化特色,这直接影响你的排查思路。
6.1 openEuler:安全增强日志(SELinux-style)
openEuler 默认启用
auditd
(Linux 审计子系统),并将审计日志(
/var/log/audit/audit.log
)与
journald
深度集成。
journalctl
会自动将
audit
事件作为
MESSAGE
的一部分,并打上
AUDIT_SESSION=
、
AUDIT_LOGINUID=
等特殊字段。这意味着,当你查一个权限拒绝错误时,
journalctl -p err
可能只显示
Permission denied
,但加上
AUDIT_LOGINUID=
过滤,就能立刻定位到是哪个用户的哪个会话触发了该操作。
实战技巧
:在 openEuler 上排查 SELinux 相关问题,不要只看
avc: denied
,更要结合
journalctl _AUDIT_TYPE=1400
(
1400
是 AVC 拒绝事件的审计类型码)。
6.2 UOS/Kylin:图形化日志中心的“影子”
UOS 和 Kylin 提供了名为“系统日志”或“日志查看器”的图形界面。它看起来是个独立应用,但后台完全依赖
journalctl
。它的所有筛选、搜索、导出功能,最终都编译为
journalctl
命令执行。这意味着:
-
图形界面的搜索框,等价于
journalctl | grep "关键词"; -
“按服务筛选”下拉菜单,等价于
journalctl -u <服务名>; -
“导出为 CSV”按钮,等价于
journalctl -o json | jq -r '... > export.csv'。
实战技巧
:当你在图形界面里发现一个奇怪的日志,但无法复制全文时,立刻打开终端,执行
journalctl -n 50 --no-pager | tail -20
,你会发现,图形界面展示的,只是
journalctl
海量日志中的一小片浮冰。真正的冰山,永远在命令行之下。
6.3 Anolis OS:云原生日志的“轻量化”
Anolis OS(龙蜥)面向云原生场景,其
journald.conf
默认配置极度精简:
Storage=volatile
(不持久化)、
SystemMaxUse=50M
(小内存友好)、
ForwardToSyslog=no
(鼓励直接对接 Loki/Prometheus)。它的哲学是:日志是短暂的、可丢弃的,应该由上层可观测性平台统一收集。
实战技巧
:在 Anolis 上,
journalctl
的主要用途是“即时诊断”,而非“历史回溯”。你需要习惯在发现问题的第一时间,就用
journalctl -u <服务> -n 200 -o json
把现场快照下来,然后交给
loki
查询。它的
journalctl
,更像是一个“日志快照枪”,而不是“日志档案馆”。
这些差异,不是 bug,而是不同发行版对同一套
systemd
工具链的差异化解读。理解它们,你才能在国产 Linux 的世界里,游刃有余。
7. 从
journalctl
到
systemd
:日志背后的系统观
journalctl
是一把钥匙,但它打开的门,通向的是整个
systemd
的世界。当你熟练使用
journalctl
后,会自然产生一系列更深层的问题:
-
为什么
nginx.service的日志里,会有systemd[1]的启动信息? -
为什么
journalctl -u docker.service能看到containerd的日志,但journalctl -u containerd.service却看不到? -
systemd的WorkingDirectory=配置,是如何影响服务日志中路径显示的?
这些问题的答案,都藏在
systemd
的单元(Unit)模型里。
journalctl
的
-u
参数,本质上是在查询
systemd
的单元生命周期事件。
systemd[1]
是 PID 1 的
systemd
进程,它负责启动、监控、重启所有单元。当它启动
nginx.service
时,会记录
Starting nginx.service
;当
nginx
进程崩溃时,
systemd
会捕获
SIGCHLD
,记录
nginx.service: Main process exited, code=exited, status=1/FAILURE
;然后根据
Restart=
配置决定是否重启。
docker.service
和
containerd.service
的关系,则揭示了
systemd
的依赖注入机制。
docker.service
的
After=
和
Wants=
配置,声明了它依赖
containerd.service
。因此,
docker
的启动日志流中,必然包含
containerd
的启动完成事件。但
containerd
作为一个独立单元,其自身日志只记录自己的内部状态,不会主动上报
docker
的行为。
至于
WorkingDirectory=
,它决定了服务进程的
cwd
(当前工作目录)。
journalctl
日志中的路径(如
Failed to start daemon: dial unix /var/run/docker.sock: connect: no such file or directory
),其相对路径解析,就是基于这个
WorkingDirectory
。如果你把
WorkingDirectory=/tmp
,那么
./config.json
就会变成
/tmp/config.json
。
所以,
journalctl
的终点,不是命令的结束,而是系统理解的起点。它逼着你去阅读
/usr/lib/systemd/system/
下的服务文件,去理解
Type=
(simple/forking/notify)、
RestartSec=
、
StartLimitIntervalSec=
等配置的含义。当你能看着
journalctl
的输出,反推出服务的配置缺陷时,你就不再是日志的读者,而是系统的作者。
我最后一次深刻体会到这一点,是在修复一个
systemd
的
reboot
命令失效问题时。
systemctl reboot
没反应,
journalctl -u systemd-logind
里全是
Session XXX logged out
,但没有
Rebooting...
。最终发现,是
/etc/systemd/logind.conf
中的
HandleReboot=ignore
被误设。
journalctl
没有直接告诉你这个配置,但它忠实地记录了
logind
收到
reboot
信号后的“无视”行为。顺着这条线索,我才找到了那个被遗忘的配置项。
这就是
journalctl
的终极价值:它不提供答案,它提供线索;它不教你怎么做,它逼你去思考为什么。

1058

被折叠的 条评论
为什么被折叠?



