排查Linux服务器问题的时候,我十有八九会先敲一条命令:journalctl。它是systemd体系下的日志查询工具,几乎所有跑新发行版的机器都会带它。和传统“一个/var/log/messages写到老”的方式不同,journald会把内核、服务、用户程序产生的日志统一收进一套带索引的二进制日志库里,再用journalctl按服务、时间、级别、关键字过滤。这篇文章不打算列手册里的所有参数,而是把一个运维老兵在日常排障、容量治理、日志持久化里真正用得到的部分,掰开揉碎讲一遍。无论你是刚接触Linux的运维新人,还是已经用过一阵 journalctl -f 的老手,应该都能找到点有用的东西。
1. 为什么排查问题我总是先打开journalctl
1.1 一个真实的“半夜故障”场景
前阵子半夜接到告警,线上Nginx大面积502。我第一反应不是去看业务日志,而是先登录服务器跑了两条命令:
bash复制systemctl status nginx
journalctl -u nginx -n 50 --since "10 min ago"
第一条命令看服务是不是活着,第二条命令直接看最近10分钟Nginx服务的完整输出。结果没翻几页就发现worker进程在反复崩溃,紧跟着一条 connection limit exceeded 的报错,问题瞬间定位到了连接数配置上。整个过程不到五分钟。
这就是我日常最典型的journalctl用法:刚出事的时候,别急着翻各种文件,先用时间窗口把相关服务最近几分钟的日志拖出来看一眼。journald默认会收集服务进程的stdout和stderr,所以很多平时“从不写日志”的服务,在journal里也能看到完整输出。这个特性,老syslog体系下想都不敢想。
1.2 journald和旧日志体系到底差在哪
Linux传统日志体系是“文件+追加”模式:内核往/var/log/kern.log写,用户服务往/var/log/messages或/var/log/syslog写,应用各自再往自己的文件写。查询基本靠grep,想按时间过滤要结合sed/awk,想看某服务今天的日志更是要在一堆文本里翻来翻去。
journald改变了这个格局。它由systemd启动,核心功能就一个:统一收日志。内核消息、系统服务输出、syslog转发、进程通过sd_journal接口主动提交的日志,全部汇入一张“网”。journalctl就是这个网的查询入口。
| 对比项 | 传统syslog/文件日志 | journald + journalctl |
|---|---|---|
| 数据格式 | 纯文本,靠约定解析 | 二进制加结构化字段,带索引 |
| 查询方式 | grep/awk硬筛 | 按服务、时间、级别、PID等字段过滤 |
| 收集范围 | 需要单独配置syslog规则 | 服务stdout/stderr默认自动收集 |
| 磁盘占用 | 固定文件,满了要自己轮转 | 默认限额,超过后自动清理(需配置) |
| 时间定位 | 需要额外写脚本 | --since/--until直接定位,天然支持启动序号 |
说白了,journald不是要取代所有文件日志,而是把“系统层面到底发生了什么”这件事统一管起来。应用自己的业务日志当然还是打文件、接日志平台,但系统级事件查journal就对了。
1.3 第一次使用前要搞清楚的基础概念
用journalctl之前,脑子里要有三个基本概念:
第一条,journald是后台守护进程,journalctl只是它的查询客户端。journald负责收集、存储、轮转,journalctl负责把数据用人类可读的方式展示出来。
第二条,日志存在/run/log/journal(内存临时目录)或/var/log/journal(磁盘持久化目录)。如果系统里没有/var/log/journal目录,默认日志只放内存,一重启就全没了。这是个极度常见的坑,后面我会专门讲怎么改。
第三条,普通用户只能查看自己用户会话相关的日志,想看系统级服务日志,要么是root,要么把自己加进systemd-journal组。公司内网机器我一般建议运维账号直接加组,不然每次sudo非常烦。
注意:
journalctl命令本身不产生日志,它只是读取systemd-journald写好的二进制日志库。很多人误以为它是“实时抓取命令”,其实它是“实时查询命令”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. journalctl高频参数实战,按使用频率排序
2.1 查服务日志:-u 就是最核心的用法
使用频率最高的一定是 -u,后面跟服务名,查看指定systemd服务的日志:
bash复制# 查看Nginx服务的全部日志
journalctl -u nginx.service
# 服务名不带.service也能识别
journalctl -u nginx
# 同时查看多个服务的日志
journalctl -u nginx -u mysql.service
这里有个经验:服务名最好写完整,至少要准确。systemd允许你只写nginx而自动补全nginx.service,但如果你有nginx-abc、nginx-def这种多实例,少写后缀可能匹配出一堆东西。多服务叠加查看的场景,常见于排查“A服务调B服务,两个都出问题了”,比如Nginx连不上PHP-FPM,同时开两个unit能直接把请求链路两边日志对起来看。
-u 还有一个很实用的搭配,就是配合 -x:
bash复制journalctl -u nginx -x -n 50
-x 会输出systemd对日志条目的详细解释,比如服务退出码的含义、报错字段的出处。对新手来说,相当于每行日志多了一行注释,但我实际用下来觉得生产环境还是优先看原始输出,-x 适合确认某个错误码具体指什么。
2.2 按时间窗口过滤:--since / --until
排障最大的需求是“找出某个时间段内发生了什么”,journalctl的时间过滤做得非常顺手:
bash复制# 最近15分钟
journalctl --since "15 min ago"
# 指定起止时间,精确到秒
journalctl --since "2025-01-01 10:00:00" --until "2025-01-01 10:30:00"
# 只看昨天到今天早上
journalctl --since yesterday --until "today 09:00"
--since 和 --until 支持自然语言,“yesterday”“today”“10 min ago”都认识。时间窗口配合 -u 是排障黄金组合:
bash复制journalctl -u mysql --since "2025-01-01 02:00:00" --until "2025-01-01 02:05:00"
如果只看到一行“服务已停止”,后边跟着“Failed with result 'oom-kill'”,那基本就是被系统杀进程了,这些细节我后面会用真实案例展开。
2.3 按级别过滤:-p 只看ERROR及以上
日志级别是排障时最有效的筛子。journald给每条日志打了优先级,从严重到轻微依次是:
| 数字 | 级别 | 说明 |
|---|---|---|
| 0 | emerg | 系统不可用 |
| 1 | alert | 必须立即处理 |
| 2 | crit | 严重错误 |
| 3 | err | 错误 |
| 4 | warning | 警告 |
| 5 | notice | 正常但重要 |
| 6 | info | 常规信息 |
| 7 | debug | 调试信息 |
日常用得最多的是 -p err,它会显示err及更高级别(即0到3),把一堆info/debug噪音直接过滤掉:
bash复制# 只看本次启动以来所有错误
journalctl -b -p err
# 配合服务名和行数限制
journalctl -u nginx -p err -n 30
如果你想把warning也带上,可以用范围语法:
bash复制journalctl -p warning..err
这个语法表示从warning到err,即保留4、3两个级别。实际排障我的习惯是:先 -p err 看一眼有没有明确错误,如果界面太干净,再降级到 -p warning,通常能发现一些“潜在风险”的线索。
2.4 实时跟踪:-f 等于tail -f的日志版
-f 参数就是实时滚动输出,作用和 tail -f 一样,但它是针对journal库的。配合 -u 是最强服务监控组合:
bash复制# 实时看Nginx日志
journalctl -u nginx -f
# 实时看最近10分钟的系统日志
journalctl -f --since "10 min ago"
我经常在重启服务的时候开两个终端,一个跑 journalctl -u mysql -f,另一个执行 systemctl restart mysql。这样服务从“停止—启动—初始化—报错”的每一步,都能实时输出到屏幕上。
这里有个细节要注意:-f 默认会把之前积压的日志先全部打印出来再进入follow模式。如果服务跑了很久,或者journal里积压了大量日志,第一次输出会非常长。所以我习惯先加 -n 控制初始输出量:
bash复制journalctl -u nginx -f -n 50
含义是“先显示最近50行,然后持续追踪新日志”,这样启动命令后屏幕立刻进入可读状态,不会刷屏刷到怀疑人生。
2.5 关键字与结构化输出:--grep 和 -o json
日志量大的时候,直接看全量输出很容易眼花。journalctl支持用关键字过滤内容:
bash复制# 在nginx服务日志里搜索关键字
journalctl -u nginx --grep="timeout|refused"
# 忽略大小写
journalctl -u nginx --grep="error" --case-sensitive=0
--grep 匹配的对象主要是日志消息文本,背后是Perl正则。用它比 journalctl -u xxx | grep xxx 高效,因为journal按索引过滤而不是逐行读,日志多的时候性能差距非常明显。
还有一类高级用法是结构化输出:
bash复制journalctl -u nginx -o json-pretty
journalctl -u nginx -o verbose -n 1
-o json-pretty 会把每条日志展开成JSON,带 _PID、_UID、_SYSTEMD_UNIT、MESSAGE 等字段,适合写脚本解析。-o verbose 则展示日志条目包含的全部字段,能看到某条日志到底带了哪些元数据,这是排查问题时的“字段字典”。
3. 日志占用磁盘的控制与持久化
3.1 日志存哪里,为什么会突然占满磁盘
先说存储路径。journald的日志可以放两个地方:
/run/log/journal:内存文件系统,机器重启日志直接消失。/var/log/journal:磁盘持久化,重启后日志还在。
默认配置 Storage=auto 的逻辑是:如果系统存在 /var/log/journal 目录,就持久化;不存在就放内存。很多Linux发行版装完系统并不会自动创建这个目录,所以你以为“日志都有了”,其实重启一下就全没了。
顺着磁盘问题延伸:journald默认不是无限增长,它有配额机制,默认上限约为所在文件系统容量的10%。但10%对大容量磁盘来说依然很可观,比如一个2T数据盘,10%就是200G,这在生产环境完全可能把磁盘撑爆。
查看当前日志占用的磁盘空间:
bash复制journalctl --disk-usage
输出类似:
code复制Archived and active journals take up 3.2G in the file system.
看到这个3.2G就能直观判断是否需要清理。还有一个细节:在内存模式下,--disk-usage 显示的是 /run 里的占用,而不是磁盘持久化的占用,别搞混。
3.2 手动清理日志的正确姿势
journald提供了专门的清理命令,总共有三个方向:
bash复制# 按总大小清理,只保留最近500M的日志
journalctl --vacuum-size=500M
# 按时间清理,只保留最近30天的日志
journalctl --vacuum-time=30d
# 按文件数量清理,只保留最近5个日志文件
journalctl --vacuum-files=5
我每次清理前都习惯先执行一次 journalctl --rotate,让journald把当前正在写的日志文件轮转成“归档文件”,然后再做vacuum。原因很简单:正在写的文件是不能随便删的,先rotate再清理,可以避免文件句柄被误伤。
注意:
--vacuum-size里的“尺寸”指的是所有journal日志文件的总大小,不是所在磁盘的总剩余空间。单位支持K、M、G、T,时间支持s、m、h、days、weeks等。
生产环境我一般不会等到磁盘满才去清理,而是写个定时任务,每周执行一次:
bash复制journalctl --vacuum-size=1G
日志超过1G就自动压缩到1G以内,成本极低,效果极好。
3.3 通过journald.conf设置上限与保留时长
手动清理终究是被动防御,更好的做法是直接在 /etc/systemd/journald.conf 里把上限写死:
ini复制[Journal]
Storage=auto
Compress=yes
Seal=yes
SystemMaxUse=1G
SystemMaxFileSize=100M
MaxRetentionSec=30d
解释一下这几个参数:
SystemMaxUse=1G:journald最多使用1G磁盘空间。SystemMaxFileSize=100M:单个journal文件达到100M就轮转新文件。MaxRetentionSec=30d:日志最多保留30天。Compress=yes:默认开启,压缩历史日志,能省不少空间。Seal=yes:启用FSS(转发安全密封)机制,防止日志被篡改,性能有轻微损耗,敏感环境建议开。
修改完配置记得重启服务,否则不生效:
bash复制systemctl restart systemd-journald
重启journald不会影响已经存在的日志文件,它只是让新配置在下次写入时生效。这个操作非常轻量,生产环境也可以放心做。
3.4 把日志改成持久化存储
如果发现日志重启后全没了,说明当前是内存模式。改持久化最简单的两种方式:
第一种,直接创建目录,然后重启journald:
bash复制mkdir -p /var/log/journal
systemctl restart systemd-journald
因为默认 Storage=auto,一旦发现该目录存在,journald就会自动把日志持久化到磁盘。
第二种,在配置文件里改成 Storage=persistent:
ini复制[Journal]
Storage=persistent
persistent 会无视目录是否存在,强制持久化,并且journald会自动创建 /var/log/journal。相比之下我更推荐直接写配置,可追溯、可继承,换新机器时把配置文件一股脑搬过去就行。
切换持久化后,如果内存里已经攒了大量日志,可以执行:
bash复制journalctl --flush
这个命令会把内存中尚未落盘的日志立即写入磁盘。做完这一步,重启机器的日志才算真正“活下来”。
4. 进阶用法:内核日志、启动问题与生产排障
4.1 看内核日志和系统启动日志:-k、-b、--list-boots
journald不只收服务日志,内核消息也在它管辖范围内。用 -k 只看内核日志,相当于结构化版的dmesg:
bash复制journalctl -k -b
journalctl -k --since "30 min ago" -f
内核日志排障价值太高了。我之前遇到一台机器网卡反复掉线,业务日志干干净净,但 journalctl -k -b 里清清楚楚写着网卡固件报错,顺着内核日志查才找到问题根源。
再来看启动阶段的日志。-b 参数表示按启动序号过滤,不带参数就是本次启动:
bash复制# 列出历史启动记录
journalctl --list-boots
# 查看上一次启动的日志
journalctl -b -1
# 查看本次启动以来的所有错误
journalctl -b -p err
--list-boots 输出类似:
code复制-1 8a2d3... 2025-01-01 02:11:00+08:00 2025-01-01 02:14:30+08:00
0 8b4f1... 2025-01-01 03:20:00+08:00 2025-01-01 04:02:11+08:00
最左边是序号,-b -1 就是查看上一次启动。排查“为什么服务器重启后起不来了”,翻上次启动日志是第一步。我之前帮人查过一台开机后network.service反复失败的机器,就是因为上次启动时网络初始化顺序出了问题,journalctl -b -1 -u network.service 一眼看穿。
4.2 多字段过滤:从_UID到_PID的精细化查询
journal日志最有价值的地方在于每条日志都带着一堆结构化字段。你可以直接按字段名访问:
bash复制# 查看某个进程PID的所有日志
journalctl _PID=1357
# 查看某个用户的所有日志
journalctl _UID=1000
# 查看特定服务在特定启动周期内的日志
journalctl _SYSTEMD_UNIT=nginx.service -b 0
# 多个字段是与关系,同时满足
journalctl _PID=1357 _SYSTEMD_UNIT=nginx.service
字段过滤比grep关键字快得多,因为journald内部就是按字段建索引的。想查某个进程从出生到死亡都干了些啥,直接 journalctl _PID=xxx 比任何文件查询都直观。
如果要在多个字段之间做“或”运算,用 + 连接:
bash复制journalctl _PID=1357 + _PID=2468
这条命令会把两个PID的日志混在一起,按时间排序输出,适合同时追踪主进程和子进程的通信链路。
查看一条日志到底带了哪些字段,用:
bash复制journalctl -u mysql -o verbose -n 1
会输出类似 _PID=1234、_EXE=/usr/sbin/mysqld、_CMDLINE=...、_SYSTEMD_CGROUP=/system.slice/mysql.service 等完整字段。我把这套方法总结为:排障先看 -o verbose 摸清字段,再按字段精确过滤。
4.3 一次MySQL服务反复重启的完整排查流程
这里分享一个实际案例。某台业务数据库MySQL每隔十几分钟就自动重启,业务端瞬间大量报错。拿到服务器后,我按下面这套流程排查:
第一步,看错误级日志,确定退出原因:
bash复制journalctl -b -p err -n 100
输出里反复出现:
code复制systemd[1]: mysql.service: Main process exited, code=killed, status=9/KILL
systemd[1]: mysql.service: Failed with result 'oom-kill'.
看到 result='oom-kill',基本确认是被OOM Killer干掉的。
第二步,核对内核日志,找出真凶:
bash复制journalctl -k --since "10 min ago"
内核日志里果然有:
code复制kernel: Out of memory: Killed process 2468 (mysqld) total-vm:21000000kB, anon-rss:4100000kB
第三步,结合服务日志确定触发时间和频率:
bash复制journalctl -u mysql --since "2025-01-01 00:00:00" --until "2025-01-01 01:00:00" -p warning..err
把所有重启时间和内存爆掉的时间点对齐,发现完全吻合,说明这台机器物理内存不足,MySQL在内存压力下成了首选牺牲品。
最终处理方案是:调整MySQL的buffer pool大小,给关键进程设 OOMScoreAdjust=-500 降低被杀的优先级,再给机器加内存。整套流程从拿到服务器到定位根因大约20分钟,journald在这里最关键的价值就是“把内核、systemd、服务三层日志统一到一条时间线上”,不然我只能分别翻三个文件,效率差太多了。
5. 常见问题速查与运维避坑清单
5.1 高频问题排查表
把平时在群里被问到的journal问题整理成一张表,直接收藏即可:
| 现象 | 可能原因 | 排查/解决办法 |
|---|---|---|
| 重启后日志全部消失 | /var/log/journal不存在,日志只存内存 | 执行 mkdir -p /var/log/journal && systemctl restart systemd-journald |
| 磁盘被/var/log/journal占满 | Storage模式为persistent且无清理策略 | journalctl --vacuum-size=500M,然后配置SystemMaxUse |
| 普通用户执行journalctl没输出 | 无系统日志读取权限 | 将用户加入systemd-journal组:usermod -aG systemd-journal 用户名 |
| 日志时间看着不对 | 系统时区错误或时间未同步 | 检查timedatectl,部署chrony做时间同步 |
| journal文件损坏,查询报错 | 异常断电导致文件损坏 | journalctl --verify 检查,确认损坏后停止journald再删除对应文件 |
| 改了journald.conf不生效 | 没有重启systemd-journald | 修改后必须 systemctl restart systemd-journald |
5.2 几个我踩过的坑和对应的解决办法
第一个坑是刚接触journal时,图省事直接 rm -rf /var/log/journal/* 来清日志。结果journald进程握着文件句柄没释放,delete后磁盘空间根本不会立刻恢复,还可能导致正在写的日志文件丢失。正确做法永远是先用 journalctl --rotate 再执行vacuum系列命令,让journald自己处理文件生命周期。
第二个坑是配置了 Storage=persistent 却忘了设置 SystemMaxUse。持久化日志会持续增长,在某些大磁盘机器上默认的10%仍然能占掉几十甚至几百G,等发现时磁盘已经红了。所以凡是开持久化的机器,我强烈建议同时把上限、单文件大小、保留时长一次性都配置好。
第三个坑是误以为journal日志是应用业务的唯一日志来源。journal收集的是系统和服务的stdout/stderr,而像Nginx的access.log、MySQL的binlog这类业务日志照样要写文件。如果只盯着journal,业务请求情况会一片空白。我一般把“系统排障看journal、业务分析看应用日志”作为分工原则。
5.3 最后分享一个习惯
我自己有个用了很久的固定套路:任何一台机器出了说不清的问题,第一件事不是乱翻日志文件,而是先执行下面这条命令:
bash复制journalctl -b -p err -n 100 --no-pager
-b 限定本次启动,-p err 过滤掉噪音,-n 100 只看最近100条,--no-pager 直接输出到终端。这套组合拳能让我在半分钟内判断问题是不是系统级,再决定下一步去查哪个服务、哪个时间窗口。用得久了,你会慢慢发现journalctl最值钱的不是某一两个参数,而是它把整个系统的运行轨迹串成了一条可以随意回溯的时间线。
下次再遇到服务起不来、磁盘突然变满、机器重启后异常,别急着瞎猜,先打开journalctl,让系统自己告诉你答案。
