服务器日志怎么看

结论: 看服务器日志的核心方法只有三条:一是用 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/UbuntuRHEL/Rocky记录内容
系统综合日志/var/log/syslog/var/log/messages内核与系统服务的常规输出
认证与安全/var/log/auth.log/var/log/secureSSH 登录、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。

先看一眼整体占用与新鲜度:

bash
# 查看 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 -10

journalctl 系统日志查询

结论先行:凡是 systemd 托管的服务(systemctl start 启动的),日志一律用 journalctl -u 服务名 查,配合 --since 时间过滤和 -p 级别过滤,80% 的问题能在这里解决。

最常用的一组命令:

bash
# 查看 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(重启即丢):

bash
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-usage

tail / grep / awk 实时追踪与离线统计

结论先行:文本文件日志(如 Nginx access.log)的处理套路是——实时问题用 tail -f,定位问题用 grep 带上下文,量化问题用 awk 统计。三者配合能覆盖几乎所有分析需求。

bash
# 实时跟踪最新 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.log

Nginx 访问日志默认采用 combined 格式,字段说明如下:

序号样例值字段含义排查用途
1203.0.113.45remote_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、协议找出慢接口与被刷接口
6502status 响应状态码统计错误率的核心字段
7153body_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

状态码分布统计是最高频的分析动作,记住下面这组:

bash
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 加上耗时字段能大幅提升排障效率:

nginx
# 放入 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 标准轮转配置:

bash
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 自带命令:

bash
journalctl --vacuum-size=1G        # 保留最近 1GB
journalctl --vacuum-time=14d       # 保留最近 14 天

常见误区 / 排错提示

  1. 用 cat 打开几个 GB 的日志文件:会把终端卡死并吃掉大量内存缓冲。正确做法是 less、tail -n 100 或 grep 先过滤,less 中按 G 跳末尾、?关键字 向上搜索。
  2. grep 完发现 access.log 里全是内网 IP:因为前面挂了反向代理或 CDN,$remote_addr 是网关地址。必须在 Nginx 里自定义 log_format 加入 $http_x_forwarded_for 才能记录真实来源。
  3. 重启后找不到上一次崩溃的日志:journal 处于临时模式(Storage=volatile)或已被轮转清理。应配置 Storage=persistent 并用 journalctl -b -1 查看上一次启动。
  4. 时间戳对不上:Nginx 用 $time_local(本地时区),journal 也用本地时区,但应用日志可能输出 UTC。统一配置 timedatectl set-timezone Asia/Shanghai 并在应用里显式指定时区,否则多源排障会错位数小时。
  5. 只看 access.log 不看 error.log:很多请求失败在 Nginx 层就没进后端,error.log 才有 upstream timed out、Permission denied 这类根因,access.log 里只有一串 499/502。
  6. 日志文件被删了但空间没释放:进程仍持有已删除文件的句柄。用 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//fd/ 恢复: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 找出慢请求,优化后端响应时间或调大超时配置。