跳到主要内容

系统日志与journalctl

1. 概念与原理

日志是生产排障的命脉。前端工程师碰日志的场景:

  • 看 Nginx access/error 日志
  • 看自己 Node 应用的日志
  • 看 systemd 服务为什么起不来
  • 看 dmesg 排查内核问题(OOM、网络丢包)
  • 处理日志暴涨吃满磁盘

Linux 现代日志系统有两套并存:

系统路径工具特点
传统 syslog / rsyslog/var/log/*.logtailgrep纯文本,跨发行版稳定
systemd journal/var/log/journal/(二进制)journalctl结构化、带元数据、可过滤

新系统(CentOS 7+、Ubuntu 16+)journald 是主力。但 nginx、自己写的应用还是用文本日志。两套要都会。

2. journalctl 核心命令

2.1 看什么

journalctl # 所有日志(从早到晚,分页)
journalctl -e # 跳到末尾
journalctl -r # 倒序(最新在上)
journalctl -n 100 # 最后 100 行
journalctl -f # 实时跟随(类似 tail -f)
journalctl --no-pager # 不分页,脚本里用

2.2 按服务过滤

journalctl -u nginx # 看 nginx 服务
journalctl -u nginx -f # 实时
journalctl -u nginx --since today
journalctl -u nginx --since "1 hour ago"
journalctl -u nginx --since "2026-06-18 10:00" --until "2026-06-18 11:00"
journalctl -u nginx -p err # 只看 error 级别及以上

时间表达极灵活:yesterdaytoday2 hours ago-2h(systemd 252+)。

2.3 按优先级过滤

syslog 优先级(数字越小越严重):

级别数字含义
emerg0系统不可用
alert1必须立即处理
crit2严重
err3错误
warning4警告
notice5正常但值得注意
info6一般信息
debug7调试
journalctl -p err # err 及以上(即 0-3)
journalctl -p warning..err # 区间

2.4 按内核 / 启动过滤

journalctl -k # 只看内核日志(dmesg)
journalctl -b # 当前启动周期
journalctl -b -1 # 上一次启动
journalctl --list-boots # 所有启动历史
journalctl -b -1 -u nginx # 上次启动的 nginx 日志

排查机器重启原因:journalctl -b -1 -p err --no-pager

2.5 按进程 / 用户过滤

journalctl _PID=12345
journalctl _UID=1000
journalctl /usr/bin/node # 按可执行路径
journalctl --user-unit myapp # 用户级服务

2.6 JSON 输出(脚本友好)

journalctl -u nginx -o json
journalctl -u nginx -o json-pretty
journalctl -u nginx -o json --since "1 hour ago" \
| jq 'select(.PRIORITY == "3") | .MESSAGE'

2.7 磁盘占用与清理

journalctl --disk-usage
# Archived and active journals take up 1.5G in the file system.

# 按大小限制保留
sudo journalctl --vacuum-size=500M

# 按时间限制保留
sudo journalctl --vacuum-time=7d

# 配置永久限制(/etc/systemd/journald.conf)
SystemMaxUse=500M
MaxRetentionSec=1month

改完 systemctl restart systemd-journald

3. 传统文本日志

3.1 关键日志位置

路径内容
/var/log/syslog/var/log/messages系统综合日志(Ubuntu vs CentOS)
/var/log/auth.log/var/log/secure认证日志(SSH 登录、sudo)
/var/log/kern.log内核日志(同 dmesg)
/var/log/dmesg启动日志
/var/log/nginx/access.logNginx 访问
/var/log/nginx/error.logNginx 错误
/var/log/cron.logcron 任务(部分发行版)
/var/log/apt//var/log/yum.log包管理

3.2 文本日志高效查询

tail -f /var/log/nginx/error.log # 实时
tail -n 100 /var/log/nginx/error.log # 最后 100 行
tail -f file1 file2 # 同时跟多个

# 多文件实时(带文件名标识)
tail -f /var/log/nginx/*.log
# 或 multitail(更友好)
multitail /var/log/nginx/error.log /var/log/app/error.log

# grep 实时
tail -f file.log | grep ERROR
tail -f file.log | grep -E "ERROR|WARN"

# 看某时间段
sed -n '/2026-06-18 10:00/,/2026-06-18 11:00/p' file.log

# 看压缩历史日志
zcat file.log.gz | grep ERROR
zgrep ERROR file.log.*.gz
zless file.log.gz

3.3 grep 高级技巧

grep -i error file.log # 忽略大小写
grep -v INFO file.log # 排除 INFO
grep -c ERROR file.log # 计数
grep -A 3 ERROR file.log # 匹配行后 3 行
grep -B 3 ERROR file.log # 前 3 行
grep -C 3 ERROR file.log # 前后各 3 行
grep -E "ERROR|FATAL" file.log # 扩展正则
grep -P "\d{3}\.\d{3}" file.log # Perl 正则(含 \d)
grep -l ERROR /var/log/*.log # 只输出文件名
grep -r ERROR /var/log/ # 递归
grep -h ERROR /var/log/*.log # 多文件不带文件名前缀

3.4 awk 提取字段

Nginx access 日志默认格式:

1.2.3.4 - - [18/Jun/2026:10:00:01 +0800] "GET /api/x HTTP/1.1" 200 1234 "https://ref" "Mozilla/5.0..."
# 提取 IP(第 1 列)
awk '{print $1}' access.log

# 提取状态码
awk '{print $9}' access.log

# 按状态码统计
awk '{print $9}' access.log | sort | uniq -c | sort -rn

# 5xx 的请求
awk '$9 >= 500 {print}' access.log

# 慢请求(假设最后是响应时间)
awk '$NF > 1 {print}' access.log

# 按 IP 统计访问量 Top 10
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head

# 计算 95 分位响应时间
awk '{print $NF}' access.log | sort -n | awk 'BEGIN{c=0; sum=0} {a[c]=$1; c++; sum+=$1} END{print "P95:", a[int(c*0.95)]}'

sort | uniq -c | sort -rn 这个组合是日志分析的"Top N"标准范式,记住它。

4. logrotate — 日志切割

应用日志不切割迟早写满磁盘。logrotate 是标准方案,包管理器一般预装。

4.1 配置位置

  • 全局:/etc/logrotate.conf
  • 每服务:/etc/logrotate.d/<service>

4.2 Nginx 默认配置示例

# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily # 按天切
missingok # 文件不存在不报错
rotate 14 # 保留 14 份
compress # gzip 压缩历史
delaycompress # 第一次切的不压缩(避免正在写)
notifempty # 空文件不切
create 0640 nginx adm # 新文件的权限和所有者
sharedscripts # 多文件只执行一次脚本
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
endscript
}

关键点:rotate 后必须通知应用重新打开日志文件,否则应用继续写老的 fd,新文件永远是空的。Nginx 用 SIGUSR1,Node 应用通常用 copytruncate(见下)。

4.3 Node 应用 logrotate

Node 应用一般不支持 SIGUSR1 重开日志(除非用 pino 等库手工实现),用 copytruncate

/var/log/myapp/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
copytruncate # 复制后清空原文件,应用 fd 不变
}

copytruncate 缺点:复制和截断之间的写入会丢。生产环境对日志完整性要求高的,用 winston/pino 的 file rotation 插件。

4.4 手动测试

logrotate -d /etc/logrotate.d/nginx # 干跑(debug)
sudo logrotate -f /etc/logrotate.d/nginx # 强制执行

logrotate 默认由 cron / systemd timer 每天跑一次,看:

cat /etc/cron.daily/logrotate
# 或
systemctl list-timers | grep logrotate

5. dmesg — 内核日志

排查 OOM、磁盘错误、网络丢包、USB/硬件问题。

dmesg # 全部
dmesg -T # 人类可读时间
dmesg -w # 实时跟随
dmesg -l err,crit # 按级别
dmesg | grep -i "out of memory"
dmesg | grep -i oom
dmesg | grep -i "segfault"
dmesg | grep -i "tcp"

新版系统 dmesg 等同 journalctl -k

5.1 OOM Killer 排查

dmesg -T | grep -i "killed process"
# [Wed Jun 18 10:00:00 2026] Out of memory: Killed process 12345 (node)
# total-vm:8000000kB, anon-rss:7000000kB

看到这个就明确:Node 进程因为内存不够被内核杀。后续:

  1. 调小 --max-old-space-size
  2. 加机器内存
  3. 容器加 memory limit + 应用感知(process.memoryUsage()
  4. 排查内存泄漏(模块 11)

6. 应用层日志:自己写日志的规范

6.1 结构化日志

Node 应用别用 console.log,用 pinowinston

// pino 示例
const pino = require('pino')
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
// 生产用 JSON,本地可加 pretty 转换
})

logger.info({ userId: 123, action: 'login' }, '用户登录')
// {"level":30,"time":1718681400000,"pid":12345,"userId":123,"action":"login","msg":"用户登录"}

JSON 日志的好处:

  • 字段化,jq/awk 查询精确
  • ELK / Loki 直接索引字段
  • 不会被多行堆栈搞乱

6.2 日志级别约定

级别什么时候用
error业务/系统真出错了(需要告警)
warn异常但不致命(如降级、重试)
info关键业务节点(请求、登录、订单)
debug开发调试,生产关闭
trace极细粒度,临时开

生产 LOG_LEVEL=info,告警基于 error 级别和数量。

6.3 关键字段

每条日志应该包含:

  • timestamp — ISO 8601 UTC
  • level — error/warn/info
  • service — 服务名
  • traceId — 链路追踪 ID(贯穿所有微服务)
  • userId / requestId — 业务定位
  • message — 人类可读
  • error.stack — 错误堆栈(如果有)

6.4 不写什么

  • 不写敏感信息:密码、token、信用卡、身份证、私钥
  • 不写大对象 dump(请求 body 整体 stringify)
  • 不在循环里写 info 级日志(量爆炸)
  • 不用 console.log 在生产(同步阻塞 + 没结构)

7. 故障排查实战

7.1 服务起不来

# 1. 看状态
systemctl status myapp

# 2. 看日志
journalctl -u myapp -n 100 --no-pager
# 关注最后几条 error,常见:端口被占、配置语法错、依赖未启动

# 3. 看具体启动失败原因
journalctl -xe # x 解释、e 跳到末尾

7.2 网站 5xx 突增

# 1. Nginx 错误日志
tail -100 /var/log/nginx/error.log
# 关注 upstream timed out / connect() failed / no live upstreams

# 2. 后端应用日志
journalctl -u backend -p err --since "10 min ago"

# 3. 看是哪些请求出错(access 日志 5xx)
awk '$9 >= 500 {print $7}' access.log | sort | uniq -c | sort -rn

# 4. 看时间分布
awk '$9 >= 500 {print $4}' access.log | cut -c14-18 | sort | uniq -c

7.3 日志暴涨磁盘满

# 1. 找最大的日志
du -sh /var/log/* | sort -h | tail -20

# 2. 看是谁在写
lsof | grep "\.log" | awk '{print $1, $9}' | sort -u

# 3. 立即释放空间(保留 fd,应用继续写)
sudo truncate -s 0 /var/log/big.log
# 不要 rm!rm 后空间不释放(fd 被持有)

# 4. 临时停日志输出(如果是 nginx)
# 在 nginx.conf 加 access_log off; 然后 reload

# 5. 修 logrotate
sudo logrotate -f /etc/logrotate.d/nginx

truncate -s 0 是日志爆磁盘的标准救火操作。> 重定向也行(: > big.log)。

7.4 SSH 登录排查

# 看谁登录过
journalctl -u ssh --since today
# 或老系统
tail /var/log/auth.log

# 看暴力破解
grep "Failed password" /var/log/auth.log | awk '{print $11}' | sort | uniq -c | sort -rn
# 输出按失败次数排序的 IP

# 看是不是被入侵
last # 最近登录
lastlog # 每个用户最后登录
who # 当前在线

8. 集中式日志(前端必知)

单机日志足够小项目。多机 / 容器场景必须集中收集,否则排障要 ssh 到 N 台机器。主流方案:

方案特点
ELK(Elasticsearch + Logstash + Kibana)老牌,功能全,重
EFK(用 Fluentd 代 Logstash)K8s 主流
Loki + Grafana轻量,只索引标签不索引内容,便宜
Datadog / 阿里 SLS / 腾讯 CLS商业 SaaS,省心

集成方案详见模块 08。前端在自己应用侧只需做到:

  1. 输出 JSON 结构化日志到 stdout
  2. 容器 / K8s 平台自动收集 stdout
  3. 不要写日志到容器内文件(容器销毁日志丢失)

9. 常见反模式

  • rm big.log 救火:fd 被进程持有,磁盘空间不释放。用 truncate -s 0: > file
  • 日志不切割:必爆磁盘。logrotate 是基础设施
  • console.log 当生产日志:无级别、无结构、无时间戳
  • 打印密码/token 到日志:审计灾难,被入侵直接泄漏
  • tail -f 看大日志开头:用 lessheadgrep,tail -f 是看实时增量
  • 日志写到容器内 /var/log/app/:容器销毁日志丢失,写到 stdout 让平台收
  • info 级别日志写每个 SQL 查询:QPS 一上来日志量爆炸
  • journalctl 不设大小限制:默认行为各发行版不一,可能吃几 G 磁盘
  • 多个应用都写 app.log 不区分级别:grep 出来全混在一起

10. 速查表

我想……命令
看 nginx 实时错误tail -f /var/log/nginx/error.log
看 systemd 服务最近日志journalctl -u <svc> -n 100
看为什么服务起不来systemctl status <svc> + journalctl -xeu <svc>
看 OOM 杀了谁dmesg -T | grep -i killed
看磁盘谁占了du -sh /var/log/* | sort -h
释放被进程持有的日志空间truncate -s 0 <file>
5xx 请求 top URLawk '$9>=500{print $7}' access.log | sort | uniq -c | sort -rn
暴力破解 IPgrep "Failed password" /var/log/auth.log | awk '{print $11}' | sort | uniq -c
看上次启动日志journalctl -b -1
日志按级别过滤journalctl -u <svc> -p err

11. 延伸阅读