journalctl 是查询 systemd Journal 的标准客户端。对工业边缘设备而言,它不只是"看日志的命令":服务失败、内核错误、上一次启动现场、结构化业务字段、磁盘占用和日志保留策略,都可以围绕 Journal 建立一套可排查、可审计、可持续运维的机制。
但 journalctl 也有前提和边界。它适用于使用 systemd-journald 的 Linux 系统;如果 Journal 运行在 volatile 模式,日志只保存在 /run/log/journal,重启后就会丢失;如果权限不足,普通用户只能看到很小一部分日志。把这些边界搞清楚,比记住一堆参数更重要。
本文从实战角度梳理常用查询、过滤、输出格式、启动现场、结构化日志、持久化、容量清理、权限和常见坑,并给出适合工业边缘设备的日志治理模板。
一、先确认 Journal 的工作状态
排查前先确认三件事:systemd 是否在管理日志、Journal 当前能否持久化、当前用户是否有读取权限。
bash
# 查看 journald 服务状态
systemctl status systemd-journald
# 查看当前配置来源
systemd-analyze cat-config systemd/journald.conf
# 查看磁盘占用
journalctl --disk-usage
# 查看启动记录
journalctl --list-boots
普通用户默认只能看到自己产生的日志。如果需要查询系统日志,通常有以下方式:
- 使用
sudo journalctl; - 将运维账号加入
systemd-journal、adm或发行版约定的日志读取组; - 通过日志平台或受控脚本暴露只读查询能力。
示例:
bash
sudo usermod -aG systemd-journal ops
加入组后需要重新登录才会生效。不要为了看日志直接放宽 /var/log/journal 的全局读写权限。
二、基础查询:时间、尾部与实时跟踪
1. 查看全部日志
bash
journalctl
默认进入分页器。脚本中建议显式关闭分页:
bash
journalctl --no-pager
2. 时间过滤
bash
# 今天的日志
journalctl --since today
# 最近 1 小时
journalctl --since "1 hour ago"
# 指定时间区间
journalctl --since "2025-01-01 00:00:00" \
--until "2025-01-31 23:59:59"
# 使用 UTC 输出,避免跨时区误读
journalctl --since today --utc
--since 和 --until 也可分别写成 -S 和 -U。时间解析依赖本机时区配置。跨站点排障时,应统一记录时间戳来源,并在告警中明确 UTC 或本地时间。
3. 尾部与实时跟踪
bash
# 最近 50 条
journalctl -n 50 --no-pager
# 逆序显示
journalctl -r --no-pager
# 类似 tail -f
journalctl -f
# 只跟踪某个服务
journalctl -u edge-gateway.service -f
生产环境中不要长期裸跑 journalctl -f。更稳妥的方式是按服务、优先级、字段过滤后跟踪,并把输出交给日志采集器。
三、按服务、内核和优先级过滤
1. 按服务单元过滤
bash
journalctl -u myapp
journalctl -u myapp.service
journalctl -u myapp.service -f
# 多个 -u 是并列匹配,即任一单元匹配即可
journalctl -u myapp.service -u nginx.service
建议显式写 .service 后缀,尤其是服务名与其他 unit 类型可能冲突时。
2. 按内核日志过滤
bash
journalctl -k
journalctl --dmesg
# 当前启动的内核错误
journalctl -k -p err -b
-k 查看的是内核消息。它不能替代 dmesg 的所有排查场景,例如某些早期启动消息、容器隔离环境或非 systemd 系统的内核日志,需要结合现场判断。
3. 按优先级过滤
优先级从高到低为:
text
emerg > alert > crit > err > warning > notice > info > debug
常见用法:
bash
# err 及更严重级别:emerg / alert / crit / err
journalctl -p err
# 只看 warning 到 err 这个范围
journalctl -p err..warning
# 只看 err 级别
journalctl -p err..err
# 最近 1 小时的错误
journalctl -p err --since "1 hour ago"
这里容易误读:单独使用 -p err 不是"只看 err",而是包含 err 以及更严重的 crit、alert、emerg。如果要精确匹配一个级别,需要写 err..err。
4. 按进程、用户和可执行文件过滤
bash
# 按 PID 字段
journalctl _PID=12345
# 按 UID 字段
journalctl _UID=1000
# 按系统标识
journalctl -t myapp
journalctl SYSLOG_IDENTIFIER=myapp
# 按可执行文件路径匹配 _EXE
journalctl /usr/local/bin/myapp
# 按进程名匹配 _COMM
journalctl _COMM=myapp
注意:
/usr/local/bin/myapp匹配的是 Journal 记录中的_EXE字段,只适合真实可执行文件路径;- Python、Shell、Node 等脚本的
_EXE通常是对应解释器路径,不是脚本文件路径; _COMM是进程名,可能同名冲突;- 更推荐服务用
-u,业务日志用SYSLOG_IDENTIFIER或自定义结构化字段。
四、文本匹配与组合过滤
1. 使用 --grep
如果 systemd 编译启用了 PCRE2 支持,可以使用:
bash
journalctl -u edge-gateway.service \
--since "2 hours ago" \
--grep 'timeout|connection refused'
# 忽略大小写
journalctl -u edge-gateway.service \
--since "2 hours ago" \
--case-sensitive=false \
--grep 'device.*offline'
--grep 的能力依赖构建选项。如果提示不支持,需要退回普通文本管道:
bash
journalctl -u edge-gateway.service \
--since "2 hours ago" \
--no-pager |
grep -Ei 'timeout|connection refused'
2. 字段匹配的与或关系
同一个命令行中的多个字段匹配通常表示同时满足:
bash
journalctl SYSLOG_IDENTIFIER=edge-gateway SITE_ID=SH-01
多个 -u 之间是并列匹配:
bash
journalctl -u edge-gateway.service -u modbus-collector.service
复杂组合可以使用 + 表示两组条件之间的"或"关系,具体语法应以当前 systemd 版本的 systemd.journal-fields(7) 和 journalctl(1) 手册为准。排障时先用窄范围验证条件,再扩大时间窗口,比一次性写复杂表达式更可靠。
五、输出格式:给人看与给程序处理
bash
# 默认格式
journalctl -u myapp.service
# 短格式
journalctl -u myapp.service -o short
# ISO 8601 时间
journalctl -u myapp.service -o short-iso
# 高精度 ISO 时间,适合性能和时序分析
journalctl -u myapp.service -o short-iso-precise
# 只输出 MESSAGE,适合管道处理
journalctl -u myapp.service -o cat
# JSON 行,适合日志采集和脚本处理
journalctl -u myapp.service -o json
# 缩进 JSON,适合人工检查字段
journalctl -u myapp.service -o json-pretty
# 查看一条日志的全部字段
journalctl -u myapp.service -o verbose
如果只需要部分字段,可以使用 --output-fields:
bash
journalctl -u edge-gateway.service \
--since "1 hour ago" \
--output-fields=MESSAGE,PRIORITY,SYSLOG_IDENTIFIER \
-o json-pretty
JSON 输出中的 __REALTIME_TIMESTAMP 是微秒时间戳,不是毫秒。下流水线或入库前要明确单位,避免时间相差 1000 倍。
六、启动现场与服务排障
1. 查看启动
bash
# 本次启动
journalctl -b
# 上一次启动
journalctl -b -1
# 启动记录列表
journalctl --list-boots
-b -1 的前提是上一次启动日志已经被持久化。如果 Journal 是 volatile 模式,重启后没有历史启动可查。
2. 排查服务失败
bash
# 服务状态与最近日志
systemctl status edge-gateway.service --no-pager
journalctl -u edge-gateway.service -n 100 --no-pager
# 服务错误与更严重级别
journalctl -u edge-gateway.service -p err --since today
# 服务启动前后的系统错误
journalctl -b -p err --no-pager
# 实时跟踪
journalctl -u edge-gateway.service -f
如果服务反复重启,还应查看:
bash
systemctl list-units --state=failed --no-pager
systemctl list-timers --all --no-pager
journalctl -u edge-gateway.service --since "24 hours ago" --no-pager
3. 一次典型排障流程
bash
# 1. 确认当前启动和失败单元
journalctl --list-boots
systemctl --failed --no-pager
# 2. 查看失败服务本次启动日志
journalctl -b -u edge-gateway.service -n 200 --no-pager
# 3. 收窄到错误与更严重级别
journalctl -b -u edge-gateway.service -p err --no-pager
# 4. 查看同一时间窗口的内核和系统错误
journalctl -b -p err --since "10 minutes before the failure" \
--until "10 minutes after the failure" --no-pager
上面的 --since 示例应替换为真实时间字符串;journalctl 不会自动知道"failure"发生在什么时候。
七、结构化日志:不要把 JSON 字符串当成结构化字段
下面命令发送的是一个 JSON 字符串作为 MESSAGE:
bash
logger -t myapp -p user.info \
'{"event":"start","port":8080}'
查询时会得到:
bash
journalctl -t myapp -o json-pretty
这个日志仍然可以直接查询到 JSON 文本,但 Journal 并不会自动把 event、port 变成独立字段。要让字段可索引、可过滤,应使用 Journal 字段或日志库的结构化能力。
1. 使用 logger --journald
bash
printf '%s\n' \
'MESSAGE=device online' \
'SYSLOG_IDENTIFIER=edge-gateway' \
'SITE_ID=SH-01' \
'DEVICE_ID=dev-001' \
'EVENT=device_online' |
logger --journald
注意:
MESSAGE是日志正文;SYSLOG_IDENTIFIER用于标识来源;- 自定义字段建议使用大写和下划线;
- 以下划线开头的字段是 Journal 信任字段,不应由普通应用伪造;
- 字段值中包含换行等特殊字符时,需要按 Journal 原生格式编码。
2. Python 发送结构化日志
python
from systemd import journal
def log_device_event(
event: str,
device_id: str,
site_id: str,
*,
priority: int = journal.LOG_INFO,
) -> None:
journal.send(
f"device event: {event}",
PRIORITY=priority,
SYSLOG_IDENTIFIER="edge-gateway",
EVENT=event,
DEVICE_ID=device_id,
SITE_ID=site_id,
)
log_device_event(
event="device_online",
device_id="dev-001",
site_id="SH-01",
)
查询结构化字段:
bash
journalctl -t edge-gateway \
EVENT=device_online \
SITE_ID=SH-01 \
-o json-pretty
Python 示例依赖 python3-systemd 或对应的 systemd Python 包。
3. 在 systemd 服务中固定标识
ini
[Service]
Type=simple
ExecStart=/usr/local/bin/edge-gateway
SyslogIdentifier=edge-gateway
StandardOutput=journal
StandardError=journal
LogExtraFields=SITE_ID=SH-01
这样应用即使没有输出完整上下文,运维侧也能通过 unit、identifier 和额外字段缩小范围。
八、日志持久化与容量治理
1. 为什么默认可能重启丢失
如果只存在 /run/log/journal,日志保存在内存文件系统中,重启后会丢失。要让日志跨重启保留,需要启用 /var/log/journal。
推荐使用 drop-in 配置,而不是直接修改主配置文件:
bash
sudo mkdir -p /etc/systemd/journald.conf.d
sudoedit /etc/systemd/journald.conf.d/90-edge-retention.conf
示例配置:
ini
[Journal]
Storage=persistent
SystemMaxUse=2G
SystemKeepFree=1G
SystemMaxFileSize=100M
MaxRetentionSec=30day
RateLimitIntervalSec=30s
RateLimitBurst=1000
ForwardToSyslog=no
说明:
Storage=persistent:要求日志落盘;SystemMaxUse:Journal 最大可占用空间;SystemKeepFree:至少给文件系统保留多少空闲空间;SystemMaxFileSize:单个 journal 文件上限;MaxRetentionSec:最长保留时间;RateLimitIntervalSec与RateLimitBurst:控制异常刷日志速率;ForwardToSyslog=no只应在没有 rsyslog/syslog 链路时使用。
应用配置:
bash
sudo systemctl restart systemd-journald
重启 journald 可能造成短暂日志写入中断,建议在维护窗口执行。部分发行版还需要确认 /var/log/journal 已创建且权限正确:
bash
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
2. 查看与清理
bash
# 查看磁盘占用
journalctl --disk-usage
# 将活动文件旋转为归档文件
sudo journalctl --rotate
# 按大小清理
sudo journalctl --vacuum-size=1G
# 按时间清理
sudo journalctl --vacuum-time=30d
# 按文件数清理
sudo journalctl --vacuum-files=10
# 将缓冲区日志刷盘
sudo journalctl --sync
关键细节:--vacuum-* 主要清理已归档 journal 文件,不一定会删除当前正在写入的 active 文件。因此清理后仍高于目标值并不一定是命令失败,可能需要等待滚动,或重新评估上限与磁盘容量。
3. 工业边缘建议
边缘设备磁盘空间差异很大,建议同时设置:
- 最大占用;
- 最小保留空闲空间;
- 最长保留时间;
- 单文件大小;
- 日志速率限制;
- 磁盘使用率告警;
- 关键错误外发或集中采集。
如果设备只有 4GB 存储,SystemMaxUse=2G 可能仍然过大;如果设备没有持久盘,应明确接受 volatile 日志,并通过网络转发关键日志。
九、日志转发与平台集成
journalctl 负责查询和管理本地 Journal。工业现场通常还需要把关键日志送到集中平台。
常见方式:
| 方式 | 适用场景 | 注意点 |
|---|---|---|
ForwardToSyslog=yes |
已有 rsyslog/syslog 体系 | 需要确保接收端和队列可靠 |
systemd-journal-remote |
使用 systemd 原生远程 Journal | 需要证书、权限和存储规划 |
| Fluent Bit / Vector | 边缘侧采集并转发到 Kafka、Loki、ES 等 | 需要处理断网缓存与背压 |
| 定时打包归档 | 离线排障和合规留存 | 需要加密、校验和清理策略 |
边缘设备网络不稳定时,转发层要明确:
- 本地缓存多久;
- 磁盘满时丢弃什么;
- 重启后是否续传;
- 重复日志如何去重;
- 敏感字段如何脱敏;
- 时间同步是否可靠。
不要只做"日志能发出去",还要保证断网期间本地仍有可查现场。
十、工程实践
1. 查询先窄后宽
推荐顺序:
text
确定 boot / 时间窗口
→ 按 unit 或 SYSLOG_IDENTIFIER 过滤
→ 按 priority 过滤
→ 按结构化字段过滤
→ 必要时再 --grep
直接 journalctl | grep 会先扫大量日志,效率低,也容易把上下文过滤掉。
2. 业务日志结构化
建议固定这些字段:
| 字段 | 示例 | 说明 |
|---|---|---|
SYSLOG_IDENTIFIER |
edge-gateway |
稳定来源标识 |
SITE_ID |
SH-01 |
站点 |
DEVICE_ID |
dev-001 |
设备 |
EVENT |
device_offline |
业务事件名 |
TRACE_ID |
7f2c... |
跨系统追踪 |
PRIORITY |
3 |
严重度 |
字段值要控制基数,不要把高频变化的大字符串、完整报文或用户敏感信息直接作为字段。
3. 保留现场与控制成本并存
建议分级保留:
- 错误和告警:保留更久或外发;
- 业务审计:按合规要求保留;
- 一般运行日志:短周期保留;
- 调试日志:默认关闭,按需开启。
4. 做时间同步
日志时间不可信,排障价值会大幅下降。工业边缘设备应检查:
bash
timedatectl
systemctl status systemd-timesyncd
chronyc tracking 2>/dev/null || true
跨站点查询时建议统一使用 UTC 存储,展示层再按本地时区转换。
十一、常见坑与应对
| 坑 | 现象 | 应对 |
|---|---|---|
| Journal 是 volatile 模式 | 重启后历史日志消失 | 启用 /var/log/journal 并设置 Storage=persistent |
| 权限不足 | 只能看到少量日志 | 使用 sudo 或加入日志读取组 |
-p err 被误读 |
以为只看 err | 记住它包含 err 及更严重级别 |
| 按脚本路径过滤无效 | _EXE 是解释器路径 |
改用 unit、identifier 或结构化字段 |
logger 发 JSON 字符串 |
JSON 不是独立 Journal 字段 | 使用 --journald 或日志库结构化接口 |
| 日志无限增长 | 磁盘满导致服务异常 | 设置 MaxUse、KeepFree、Retention 并告警 |
| vacuum 后占用没降 | active 文件不能立即删除 | 先 rotate,并理解归档文件机制 |
| 刷日志触发限流 | 现场日志缺失 | 应用侧限速,journald 配置 RateLimit |
| 直接 `journalctl | grep` | 扫描量过大且丢上下文 |
| 日志包含敏感信息 | 密钥或个人信息泄露 | 应用侧脱敏,采集侧二次过滤 |
十二、TL;DR
journalctl是 systemd Journal 的标准查询工具;- 单独
-p err包含err及更严重级别,精确匹配用err..err; - 常用过滤维度:boot、时间、unit、kernel、priority、identifier、字段;
- 输出给人看用
short-iso/short-iso-precise,给程序处理用json; - JSON 字符串不是结构化 Journal 字段,使用
--journald或应用日志库; - 重启后要保留日志,必须启用持久化;
vacuum主要清归档文件,需配合 rotate 和容量策略;- 边缘设备应同时治理磁盘、权限、时间同步、速率限制和远程转发。
下一步建议
- 确认设备 Journal 存储模式与磁盘容量;
- 为核心服务统一
SyslogIdentifier和结构化字段; - 建立按服务、优先级和时间的排障命令模板;
- 配置持久化、容量上限和告警;
- 接入集中日志平台并验证断网续传能力。