结论: 看服务器日志的核心方法只有三条:一是用 journalctl -u 服务名 --since "1 hour ago" 查 systemd 托管服务的日志;二是用 tail -f 实时追踪、grep/awk 离线统计文本文件日志;三是按时间 + 关键字 + 状态码三个维度缩小范围。Nginx 访问日志在 /var/log/nginx/access.log,错误日志在 error.log,系统日志在 /var/log/messages 或 journalctl,认证日志在 /var/log/auth.log 或 secure。日志必须配 logrotate 轮转,否则会写满磁盘导致服务崩溃。
为什么要学会看日志
监控告诉你"出问题了",日志告诉你"为什么出问题"。这两者的关系是:Prometheus 上 CPU 曲线飙到 90% 是现象,而 /var/log/nginx/error.log 里那一行 "connect() failed (111: Connection refused) while connecting to upstream" 才是原因。
对运维新手来说,日志是最被低估的资源。很多人排查故障时习惯性地重启服务——重启确实能临时恢复,但问题会在三天后的某个深夜重演。而读懂日志的人能一步定位到"后端 8080 端口没监听",五分钟彻底解决。这就是为什么服务器日志分析(Log Analysis)是运维的基本功。
另一个现实价值是取证。服务器被入侵后,/var/log/auth.log 里的登录记录、/var/log/nginx/access.log 里的可疑请求参数是还原攻击路径的唯一依据。前提是这些日志当时还在——很多攻击者的第一件事就是删日志,这也是为什么要做远程日志集中。
/var/log 目录结构与常见日志文件
结论先行:先把目录结构记清楚,排查时才不会满世界找文件。下表是 systemd 时代 Linux 的标准布局。
Ubuntu/Debian 与 RHEL 系的路径差异已标出:
| 日志文件 | Debian/Ubuntu | RHEL/Rocky | 记录内容 |
|---|---|---|---|
| 系统综合日志 | /var/log/syslog | /var/log/messages | 内核与系统服务的常规输出 |
| 认证与安全 | /var/log/auth.log | /var/log/secure | SSH 登录、sudo 提权、PAM 认证 |
| 内核日志 | /var/log/kern.log | 同上,由 rsyslog 分流 | 内核环缓冲区(含 OOM、硬件错误) |
| 启动过程 | /var/log/boot.log | /var/log/boot.log | 开机阶段的服务启动输出 |
| Nginx | /var/log/nginx/access.log、error.log | 同左 | Web 访问与错误 |
| MySQL | /var/log/mysql/error.log | /var/log/mysqld.log | 数据库启停与错误 |
| 包管理 | /var/log/apt/history.log | /var/log/dnf.log | 软件安装卸载历史 |
| 审计 | /var/log/audit/audit.log | 同左 | auditd 系统调用审计 |
| 上次登录 | /var/log/wtmp、btmp | 同左 | 二进制,用 last/lastb 读取 |
现代发行版(Ubuntu 20.04+、RHEL 8+、Debian 11+)默认用 systemd-journald 接管日志,日志以二进制形式存在 /var/log/journal/ 或临时目录 /run/log/journal/。这意味着光看 /var/log 下的文本文件是不够的,很多服务日志只进 journal,必须掌握 journalctl。
先看一眼整体占用与新鲜度:
# 查看 journal 占用空间
journalctl --disk-usage
# 查看最近是否有 "No space left" 或 OOM 关键字
journalctl -p err -b --no-pager | head -30
# 确认 /var/log 下各目录大小
du -sh /var/log/* | sort -rh | head -10journalctl 系统日志查询
结论先行:凡是 systemd 托管的服务(systemctl start 启动的),日志一律用 journalctl -u 服务名 查,配合 --since 时间过滤和 -p 级别过滤,80% 的问题能在这里解决。
最常用的一组命令:
# 查看 nginx 最近 1 小时的日志(按时间倒序,-e 跳到末尾)
journalctl -u nginx --since "1 hour ago" -e --no-pager
# 查看本次启动以来的所有日志
journalctl -b
# 上一次启动的日志(用于排查重启原因,极重要)
journalctl -b -1
# 列出系统启动记录(看哪些是非预期重启)
journalctl --list-boots
# 只看错误及以上级别(emerg/alert/crit/err)
journalctl -p err --since today --no-pager
# 实时跟踪某个服务,等价于 tail -f
journalctl -u mysql -f
# 按时间窗口精确过滤
journalctl --since "2026-03-15 09:00:00" --until "2026-03-15 10:00:00" -u nginx
# 反查某服务在崩溃前的最后 50 行
journalctl -u php-fpm -n 50 --no-pager日志级别(priority)共八级,从低到高依次为 emerg(0)、alert(1)、crit(2)、err(3)、warning(4)、notice(5)、info(6)、debug(7)。-p err 表示"err 及更严重的级别",实际范围是 0~3。日常排查建议从 journalctl -p warning -b 起步,噪音小又不会漏掉关键线索。
让 journal 日志持久化的配置很关键,默认很多发行版只保留在 /run(重启即丢):
sudo mkdir -p /var/log/journal
sudo tee -a /etc/systemd/journald.conf > /dev/null <<'EOF'
[Journal]
Storage=persistent # 持久化到磁盘
SystemMaxUse=2G # 最大占用 2GB
MaxRetentionSec=30day # 最长保留 30 天
EOF
sudo systemctl restart systemd-journald
journalctl --disk-usagetail / grep / awk 实时追踪与离线统计
结论先行:文本文件日志(如 Nginx access.log)的处理套路是——实时问题用 tail -f,定位问题用 grep 带上下文,量化问题用 awk 统计。三者配合能覆盖几乎所有分析需求。
# 实时跟踪最新 200 行
tail -200f /var/log/nginx/access.log
# 实时跟踪并高亮关键字链路(多条件 OR)
tail -f /var/log/nginx/access.log | grep --line-buffered -E ' 5[0-9]{2} | 4[0-9]{2} |POST /api/login'
# 带上下文查看(找到目标后看前后 5 行)
grep -n -C5 'Connection refused' /var/log/nginx/error.log
# 忽略大小写 + 反向排除爬虫
grep -viE 'bot|spider|crawler' /var/log/nginx/access.log
# 统计今天 9 点到 10 点之间的请求量
awk -F'[][]' '$2 ~ /15\/Mar\/2026:09/ {c++} END {print c+0}' /var/log/nginx/access.logNginx 访问日志默认采用 combined 格式,字段说明如下:
| 序号 | 样例值 | 字段含义 | 排查用途 |
|---|---|---|---|
| 1 | 203.0.113.45 | remote_addr 客户端 IP | 定位攻击源、统计地区分布 |
| 2 | - | identd 远程用户,通常为 - | 无实用价值 |
| 3 | - | remote_user HTTP 认证用户 | 判断是否有认证流量 |
| 4 | [15/Mar/2026:09:12:33 +0800] | time_local 本地时间与时区 | 按时间段切片统计 |
| 5 | "GET /api/user?id=1 HTTP/1.1" | 请求方法、URI、协议 | 找出慢接口与被刷接口 |
| 6 | 502 | status 响应状态码 | 统计错误率的核心字段 |
| 7 | 153 | body_bytes_sent 响应体字节数 | 找出大文件下载、盗链 |
| 8 | "https://example.com/page" | http_referer 来源页 | 判断盗链、推广来源 |
| 9 | "Mozilla/5.0 ... Chrome/122" | http_user_agent 客户端标识 | 识别爬虫与扫描器 |
| 10 | "10.0.0.7" | http_x_forwarded_for(需自定义) | 反向代理后的真实 IP |
状态码分布统计是最高频的分析动作,记住下面这组:
LOG=/var/log/nginx/access.log
# 状态码分布总览
awk '{print $9}' $LOG | sort | uniq -c | sort -rn
# TOP 10 访问来源 IP
awk '{print $1}' $LOG | sort | uniq -c | sort -rn | head -10
# TOP 10 被访问 URL(去掉 query string)
awk -F'"' '{split($2, a, " "); print a[2]}' $LOG | cut -d'?' -f1 \
| sort | uniq -c | sort -rn | head -10
# 找出响应时间超过 1 秒的请求(需 log_format 中加 $request_time)
awk -F'"' '{print $1, $2, $3}' $LOG | awk '$NF > 1.0' | head
# 统计 5xx 错误占全部请求的比例
awk '{t++; if ($9 ~ /^5/) e++} END {printf "总请求 %d,5xx %d,错误率 %.2f%%\n", t, e+0, e/t*100}' $LOG
# 按小时统计请求量,找流量高峰
awk '{print substr($4, 14, 2)}' $LOG | sort | uniq -c | sort -rn | head -24自定义 Nginx log_format 加上耗时字段能大幅提升排障效率:
# 放入 http 块
log_format main_ext '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time ut=$upstream_response_time '
'cs=$upstream_cache_status uhost=$upstream_addr';
access_log /var/log/nginx/access.log main_ext buffer=32k flush=5s;其中 $request_time 是总耗时,$upstream_response_time 是后端耗时——两者差值大说明瓶颈在 Nginx 侧(如慢客户端写阻塞),两者都大则瓶颈在后端应用。
日志轮转:logrotate 配置
结论先行:不做日志轮转,access.log 会在几周到几个月内吃掉整个分区,这是"磁盘突然满了"最常见的原因之一。logrotate 由 crond/systemd timer 每天触发,通过配置文件决定何时切割、保留几份、是否压缩。
Nginx 标准轮转配置:
sudo tee /etc/logrotate.d/nginx > /dev/null <<'EOF'
/var/log/nginx/*.log {
daily # 每天轮转一次
rotate 14 # 保留 14 份历史
missingok # 日志文件不存在时不报错
notifempty # 空文件不轮转
compress # 压缩历史日志
delaycompress # 延后一轮再压缩,便于昨日日志仍可读
sharedscripts # 所有日志统一执行一次脚本
postrotate # 切割后通知 Nginx 重开日志文件
[ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
endscript
}
EOF
# 强制跑一遍验证(-d 调试模式,-f 强制执行)
sudo logrotate -d /etc/logrotate.d/nginx
sudo logrotate -f /etc/logrotate.d/nginx
ls -lh /var/log/nginx/要点解释:切割后 Nginx 仍持有旧文件的文件描述符,写入的数据会落到已被重命名的文件中,所以必须用 kill -USR1 通知 nginx 重新打开日志;用 copytruncate 可以不用发信号,但存在切割瞬间的日志丢失窗口,生产环境更推荐信号方式。
journald 的清理与 logrotate 无关,用 journalctl 自带命令:
journalctl --vacuum-size=1G # 保留最近 1GB
journalctl --vacuum-time=14d # 保留最近 14 天常见误区 / 排错提示
- 用
cat打开几个 GB 的日志文件:会把终端卡死并吃掉大量内存缓冲。正确做法是less、tail -n 100或grep先过滤,less中按G跳末尾、?关键字向上搜索。 grep完发现 access.log 里全是内网 IP:因为前面挂了反向代理或 CDN,$remote_addr是网关地址。必须在 Nginx 里自定义log_format加入$http_x_forwarded_for才能记录真实来源。- 重启后找不到上一次崩溃的日志:journal 处于临时模式(Storage=volatile)或已被轮转清理。应配置
Storage=persistent并用journalctl -b -1查看上一次启动。 - 时间戳对不上:Nginx 用
$time_local(本地时区),journal 也用本地时区,但应用日志可能输出 UTC。统一配置timedatectl set-timezone Asia/Shanghai并在应用里显式指定时区,否则多源排障会错位数小时。 - 只看 access.log 不看 error.log:很多请求失败在 Nginx 层就没进后端,error.log 才有
upstream timed out、Permission denied这类根因,access.log 里只有一串 499/502。 - 日志文件被删了但空间没释放:进程仍持有已删除文件的句柄。用
lsof | grep deleted找出进程,重启该进程即可释放,不必重启整机。
常见问题(FAQ)
/var/log 目录太大导致磁盘满了怎么办?
先分清是哪类日志再动手:journal 用 journalctl --vacuum-size=500M 清理,Nginx/Apache 用 logrotate 强制轮转并删除旧份,Docker 用 docker system prune。判断依据是不同类型日志由不同组件写入,清理方式也不同——journal 是二进制、必须用它自己的回收命令;文本日志走 logrotate,手工删反而会让进程继续持有已删除文件的句柄,空间不会释放。清理前务必用 du -sh /var/log/* | sort -rh 确认大头在哪,切勿直接 rm -f 正在写入的日志。应急释放空间可先 truncate -s 0 清空内容而保留文件句柄,再补配 logrotate 与 journald 的 SystemMaxUse 上限,避免下次再满。
journalctl 查不到某个服务的日志是什么原因?
该服务很可能不是 systemd 托管的(比如直接用命令行或 supervisor/pm2 启动的),其输出不会进 journal。另外若日志级别被过滤或 journal 设置了 MaxLevelStore,也可能查不到。解决方法是给服务写 systemd unit,或在进程管理器里配置文件日志路径。
怎么实时监控某个关键字的出现?
推荐 tail -f /path/to/log | grep --line-buffered '关键字',注意一定要加 --line-buffered,否则管道缓冲会导致输出延迟甚至看不到。原因是 grep 在输出非终端时默认按块缓冲,攒够 4KB 才写出,加了行缓冲才会逐行刷新。做计数统计则可以用 grep -c 配合 watch -n 5 每 5 秒刷新一次;systemd 托管的服务直接 journalctl -u 服务名 -f | grep 关键字 即可。若要做长期告警而不是人工盯屏,应改用 journalctl 配合 Prometheus 的 mtail 或 Loki 的告警规则,把关键字出现频率变成可告警的指标。
日志文件被误删了还能恢复吗?
如果进程还活着、文件句柄未释放,可以从 /proc/ 恢复:lsof | grep deleted 找到 PID 和 fd,然后 cp /proc/PID/fd/N /tmp/recover.log。如果进程也已重启,就只能从备份或远程日志服务器恢复,这也是为什么要做集中式日志。
多台服务器的日志怎么统一管理?
小规模可用 rsync 定时汇总到一台日志机并按主机名分目录;规模稍大建议 ELK(Elasticsearch + Logstash + Kibana)或轻量替代 Loki + Grafana。Loki 只索引标签不索引正文,资源占用约为 Elasticsearch 的十分之一,2026 年已成为中小团队的主流选择。
Nginx 日志中出现大量 499 是什么问题?
499 是 Nginx 自定义状态码,表示客户端在服务端返回前主动断开连接。常见于后端处理太慢(用户等不及关闭页面)、慢接口被前端超时配置打断、或 CDN 回源超时。应结合 $request_time 找出慢请求,优化后端响应时间或调大超时配置。
企业QQ咨询




