结论: 服务器日志分析的核心方法是"定位日志源 → 用命令行做聚合统计 → 把结论固化成监控与告警"三板斧。入门阶段只需掌握 /var/log 目录结构、journalctl 的过滤语法、Nginx 访问日志字段含义,以及 awk/sort/uniq 的组合用法,就能独立排查 80% 的故障并输出 TOP IP、状态码分布、慢请求等关键指标。
引言:日志是服务器唯一的"黑匣子"
服务器不会说话,但它一直在记录。CPU 什么时候飙上去的、谁在什么时候登录、请求为什么 502、磁盘什么时候满的——所有答案都在日志里。问题在于,一台中等流量的服务器每天产生几百 MB 到几十 GB 日志,没有方法的日志分析等于没有日志。
新手面对日志通常有两种典型困境:一是知道出事了但不知道看哪个文件,/var/log 下几十个目录无从下手;二是打开了文件但只会 tail -f 刷屏,看不出任何结论。这两种困境的本质都是缺少结构化方法。
本文的定位是入门到能用:先把 /var/log 的目录结构讲清楚(哪些文件看什么),再讲 systemd 时代最重要的 journalctl 用法,然后是本文重点——用 awk/sort/uniq 对 Nginx 访问日志做统计,给出 TOP IP、状态码分布、慢请求三类可直接复制的实战命令。最后是日志规模变大后的集中化方案(ELK/Loki)与日志留存的合规要求。文中命令以 Rocky Linux 9 / Ubuntu 24.04 LTS 为例,Nginx 以 1.26 版本为例,截至 2026 年均为主流稳定版本。
一、/var/log 目录结构:先知道去哪看
日志分析的第一步不是学命令,是知道哪个文件记录什么。下表按排查频率排序。
| 路径 | 记录内容 | 典型用途 |
|---|---|---|
/var/log/messages | 系统综合日志(RHEL 系) | 内核、服务、硬件的通用信息 |
/var/log/syslog | 系统综合日志(Debian/Ubuntu 系) | 同上,Debian 系对应文件 |
/var/log/secure | 认证与安全日志(RHEL 系) | SSH 登录成功/失败、sudo、用户切换 |
/var/log/auth.log | 认证日志(Debian/Ubuntu 系) | 同上,Debian 系对应文件 |
/var/log/kern.log | 内核日志 | OOM、panic、硬件驱动问题 |
/var/log/dmesg | 启动时的内核环缓冲快照 | 开机硬件检测、磁盘识别 |
/var/log/boot.log | 启动过程日志 | 开机卡在哪一步 |
/var/log/cron | 定时任务执行记录 | cron 是否执行、执行失败原因 |
/var/log/audit/audit.log | auditd 审计日志 | 文件访问、命令执行审计 |
/var/log/nginx/access.log | Nginx 访问日志 | 请求量、状态码、来源 IP、耗时 |
/var/log/nginx/error.log | Nginx 错误日志 | upstream 失败、配置错误、权限 |
/var/log/mysql/error.log | MySQL 错误日志 | 启动失败、崩溃、连接错误 |
/var/log/mysql/slow.log | MySQL 慢查询日志 | 超过阈值的 SQL |
/var/log/journal/ | systemd-journald 二进制日志 | 所有 systemd 托管服务的日志 |
/var/log/lastlog | 用户最后登录时间 | lastlog 命令读取 |
/var/log/wtmp / btmp | 登录成功/失败记录 | last / lastb 命令读取 |
最重要的一条提醒:多数发行版默认不持久化 journald 日志(/var/log/journal 目录不存在,日志只写在内存 /run/log/journal),这意味着重启后 systemd 日志全部丢失。这是排查宕机时最痛的坑。上线第一步就该开启持久化:
# 开启 journald 持久化
mkdir -p /var/log/journal
systemd-tmpfiles --create --prefix /var/log/journal
systemctl restart systemd-journald
ls -la /var/log/journal/ # 出现机器 ID 目录即成功
# 限制 journal 占用(避免撑爆磁盘)
cat > /etc/systemd/journald.conf.d/99-limit.conf <<'EOF'
[Journal]
SystemMaxUse=4G
SystemKeepFree=2G
MaxRetentionSec=180day
Compress=yes
EOF
systemctl restart systemd-journald
journalctl --disk-usage # 查看当前占用二、journalctl:systemd 时代的日志查询主力
journalctl 是查询 systemd 托管服务日志的统一入口。它的价值在于结构化过滤:按服务、按时间、按优先级、按启动次数,比 grep 文本文件高效得多。
常用命令速查表
| 命令 | 作用 |
|---|---|
journalctl -u nginx | 查看指定服务(unit)的日志 |
journalctl -u nginx -f | 实时跟踪(等同 tail -f) |
journalctl -u nginx --since "2026-03-12 14:00" --until "2026-03-12 15:00" | 按时间范围过滤 |
journalctl --since "1 hour ago" | 最近 1 小时 |
journalctl --since today | 今天 |
journalctl -b | 本次启动以来的日志 |
journalctl -b -1 | 上一次启动的日志(排查宕机关键) |
journalctl --list-boots | 列出历次启动(可定位重启时间点) |
journalctl -k | 仅内核日志(等同 dmesg) |
journalctl -p err -b | 仅错误及以上级别 |
journalctl -u nginx -o json-pretty | JSON 格式输出(便于程序处理) |
journalctl -u nginx --no-pager -n 200 | 不分页,输出最后 200 行 |
journalctl -u nginx -o cat | 去掉时间戳等元数据,只输出消息 |
journalctl --disk-usage | 日志占用空间 |
journalctl --vacuum-time=30d | 清理 30 天前的日志 |
journalctl -u nginx _PID=1234 | 按进程 PID 过滤 |
journalctl -u sshd _SYSTEMD_UNIT=sshd.service PRIORITY=6 | 按字段过滤 |
日志优先级(-p 参数)从低到高为:emerg(0)、alert(1)、crit(2)、err(3)、warning(4)、notice(5)、info(6)、debug(7)。
实战:排查服务为什么起不来
# 1. 看服务状态与最后几行日志
systemctl status nginx --no-pager -l
# 2. 看该服务本次启动的全部日志,按优先级过滤错误
journalctl -u nginx -b -p warning --no-pager
# 3. 查看服务启动失败的具体原因(含退出码)
journalctl -u nginx -b -o cat | grep -iE "fail|error|denied|address already in use" | head -20
# 4. 排查反复重启的服务:列出每次启动的时间,计算重启间隔
systemctl show nginx -p NRestarts
journalctl -u nginx --since "24 hours ago" | grep -c "Started"
# 5. 导出日志供分析(发给同事或留存证据)
journalctl -u nginx --since "2026-03-12 14:00" --until "2026-03-12 15:00" > /tmp/nginx_1400.log三、Nginx 访问日志:字段含义与统计分析
3.1 字段结构表
Nginx 默认的 combined 日志格式如下(配置在 nginx.conf 的 log_format 中):
log_format combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';一条真实日志长这样:
203.0.113.45 - - [12/Mar/2026:14:23:11 +0800] "GET /api/orders?page=1 HTTP/1.1" 200 1532 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/122.0"| 字段位置 | 变量 | 含义 | 示例 |
|---|---|---|---|
| 1 | $remote_addr | 客户端 IP(有代理时为代理 IP) | 203.0.113.45 |
| 2 | - | identd 标识,基本恒为 - | - |
| 3 | $remote_user | HTTP 认证用户名,无则 - | - |
| 4 | $time_local | 本地时间,格式 日/月/年:时:分:秒 时区 | [12/Mar/2026:14:23:11 +0800] |
| 5 | $request | 请求行:方法 + URI + 协议 | "GET /api/orders HTTP/1.1" |
| 6 | $status | HTTP 状态码 | 200 |
| 7 | $body_bytes_sent | 响应体字节数(不含头) | 1532 |
| 8 | $http_referer | 来源页 | - |
| 9 | $http_user_agent | 客户端标识 | Mozilla/5.0 ... |
生产环境强烈建议扩展日志格式,加上请求耗时与真实客户端 IP,否则慢请求分析和攻击溯源都做不了:
# /etc/nginx/nginx.conf http 段
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time urt=$upstream_response_time '
'ua=$upstream_addr';
access_log /var/log/nginx/access.log main;关键新增字段:$request_time(请求总耗时,秒)、$upstream_response_time(后端处理耗时,秒)、$http_x_forwarded_for(CDN/代理传递的真实 IP)。
3.2 awk 统计实战:TOP IP
# TOP 20 访问来源 IP(第 1 列)
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
# 带百分比的完整版
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20 \
| awk '{printf "%-18s %8d\n", $2, $1}'
# 统计某个时间段内的 TOP IP(用 grep 先过滤时间)
grep "12/Mar/2026:14:" /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
# 若配置了 X-Forwarded-For,取真实 IP(按你的日志格式调整列号)
awk -F'"' '{print $(NF-3)}' /var/log/nginx/access.log | awk -F, '{print $1}' \
| sort | uniq -c | sort -rn | head -20三个命令的组合逻辑要理解清楚:awk '{print $1}' 提取字段 → sort 排序(让相同项相邻)→ uniq -c 计数 → sort -rn 按计数倒序 → head 取前 N。这套"提取-排序-计数-倒序"是日志统计的通用范式。
3.3 awk 统计实战:状态码分布
# 状态码分布($9 是状态码列,使用默认 combined 格式时)
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# 只看错误码(4xx/5xx)并按状态码+URL 聚合
awk '$9 ~ /^5[0-9][0-9]$/ {print $9, $7}' /var/log/nginx/access.log \
| sort | uniq -c | sort -rn | head -20
# 5xx 占比(判断是否触发告警阈值)
awk '{total++; if ($9 ~ /^5/) err++} END {printf "总请求 %d,5xx %d,占比 %.2f%%\n", total, err, err/total*100}' \
/var/log/nginx/access.log
# 按分钟统计 5xx 数量(定位故障起始时间点)
awk '$9 ~ /^5/ {split($4, a, ":"); print a[2]":"a[3]}' /var/log/nginx/access.log \
| sort | uniq -c | sort -rn | head -20
# 404 TOP URL(常能发现扫描器与失效链接)
awk '$9==404 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -203.4 awk 统计实战:慢请求
慢请求分析需要日志中已包含 $request_time。假设日志格式为扩展后的 main,$request_time 位于 rt= 之后:
# 提取耗时超过 1 秒的请求,按耗时倒序
awk -F'rt=' '{split($2, a, " "); if (a[1]+0 > 1) print a[1], $0}' /var/log/nginx/access.log \
| sort -rn | head -20
# 慢请求 TOP URL(按 URL 聚合平均耗时)
awk -F'rt=' '{split($2, a, " "); rt=a[1]+0; split($1, b, " "); url=b[7]; \
sum[url]+=rt; cnt[url]++} \
END {for (u in sum) printf "%.3f %6d %s\n", sum[u]/cnt[u], cnt[u], u}' \
/var/log/nginx/access.log | sort -rn | head -20
# 统计 P95/P99 耗时(近似:排序后取分位)
awk -F'rt=' '{split($2, a, " "); print a[1]+0}' /var/log/nginx/access.log \
| sort -n > /tmp/rt.txt
awk '{a[NR]=$1} END {printf "样本 %d, P50=%.3fs, P95=%.3fs, P99=%.3fs\n", \
NR, a[int(NR*0.50)], a[int(NR*0.95)], a[int(NR*0.99)]}' /tmp/rt.txt
# 后端慢(upstream_response_time 远大于 request_time 差值说明卡在 Nginx 自身)
awk -F'urt=' '{split($2, a, " "); if (a[1]+0 > 2) print $0}' /var/log/nginx/access.log | head -10如果日志里没有 rt=,说明没配置扩展格式。此时可用 awk 从 $time_local 粗略估算(不精确),或直接改用 goaccess 这类工具。
3.5 其他常用统计
# QPS:按秒统计请求数(取峰值)
awk '{print $4}' /var/log/nginx/access.log | tr -d '[' | cut -d: -f2-4 \
| sort | uniq -c | sort -rn | head -10
# UV(独立 IP 数)
awk '{print $1}' /var/log/nginx/access.log | sort -u | wc -l
# 流量总量(字节)
awk '{s+=$10} END {printf "总出口流量 %.2f GB\n", s/1024/1024/1024}' /var/log/nginx/access.log
# 扫描器识别:高频 404 且 UA 含扫描特征
awk '$9==404 {print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
grep -iE "nmap|masscan|nikto|sqlmap|wpscan|dirbuster|curl/|python-requests" \
/var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
# 实时观察(每秒请求数)
tail -f /var/log/nginx/access.log | awk '{c++; print c}' # 或用 pv四、MySQL 慢查询与错误日志
# 1. 确认慢日志是否开启
mysql -uroot -p -e "SHOW VARIABLES LIKE 'slow_query_log%'; SHOW VARIABLES LIKE 'long_query_time';"
# 2. 临时开启(重启失效,生产建议写进 my.cnf)
mysql -uroot -p -e "SET GLOBAL slow_query_log=ON; SET GLOBAL long_query_time=1;"
# 3. 用 mysqldumpslow 聚合分析慢日志
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log # 按总耗时 Top10
mysqldumpslow -s c -t 10 /var/log/mysql/slow.log # 按出现次数 Top10
mysqldumpslow -s at -t 10 /var/log/mysql/slow.log # 按平均耗时 Top10
# 4. 用 pt-query-digest 做更专业分析(Percona Toolkit)
pt-query-digest /var/log/mysql/slow.log > /tmp/slow_report.txt
less /tmp/slow_report.txt
# 5. MySQL 错误日志中找崩溃与连接问题
grep -iE "abort|crash|shutdown|too many connections|out of memory" /var/log/mysql/error.log | tail -20五、日志规模变大之后:ELK 与 Loki
当服务器数量超过 5~10 台,或者单机日志每天超过 1GB,grep + awk 的方式就会遇到瓶颈:无法跨机检索、无法长期留存、无法做可视化。此时需要集中式日志系统。
| 方案 | 组成 | 存储特点 | 资源开销 | 适用规模 |
|---|---|---|---|---|
| ELK / Elastic Stack | Elasticsearch + Logstash + Beats + Kibana | 全文索引,检索极快 | 高(内存 4GB 起,生产建议 8GB+) | 中大型,需要复杂检索与聚合 |
| EFK | Elasticsearch + Fluentd/Fluent Bit + Kibana | 同上 | 中高 | Kubernetes 环境常用 |
| Loki + Grafana | Loki + Promtail + Grafana | 只索引标签,原文压缩存储 | 低(约为 ELK 的 1/5~1/10) | 中小规模,已用 Grafana 的团队 |
| Graylog | Graylog + MongoDB + Elasticsearch | 中等 | 中 | 需要开箱即用的告警与权限 |
选择建议:已有 Prometheus + Grafana 监控栈的中小团队首选 Loki,资源友好且与现有看板整合;需要复杂全文检索、日志量很大或有合规审计需求的选 ELK。
Loki 的最小可用配置(Promtail 采集端示例):
# /etc/promtail/config.yml
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /var/lib/promtail/positions.yaml
clients:
- url: http://10.0.0.100:3100/loki/api/v1/push
scrape_configs:
- job_name: system
static_configs:
- targets: [localhost]
labels:
__path__: /var/log/{messages,syslog,secure,auth.log}
host: ${HOSTNAME}
job: system
- job_name: nginx
static_configs:
- targets: [localhost]
labels:
__path__: /var/log/nginx/access.log
host: ${HOSTNAME}
job: nginxLogQL(Loki 的查询语言)入门示例:
# 统计 Nginx 5xx 数量
sum(count_over_time({job="nginx"} |~ " 5[0-9]{2} " [5m]))
# 查看某主机的错误日志
{host="web01", job="system"} |= "error" != "ignore_this"
# 统计 TOP IP(配合 LogQL 的解析)
topk(10, sum by (ip) (count_over_time({job="nginx"} | regexp `^(?P\S+)` [1h]))) 六、日志留存与合规:别让日志变成风险
日志既是资产也是风险。留存策略要平衡"够用"与"合规"。
- 留存时长:等保 2.0 常见要求为不少于 6 个月(180 天);金融、政务等行业可能有更长要求。建议系统日志与安全日志留存 180 天,访问日志留存 90 天(可视业务需要调整)。
- 日志轮转:用
logrotate控制单文件大小与份数,避免撑爆磁盘——磁盘满导致的故障在运维事故中占比很高。
# /etc/logrotate.d/nginx 示例
/var/log/nginx/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 0640 nginx adm
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
endscript
}
# 手动触发与调试
logrotate -d /etc/logrotate.d/nginx # 调试模式,不实际执行
logrotate -f /etc/logrotate.d/nginx # 强制执行- 敏感信息脱敏:访问日志可能包含手机号、身份证、token 等。应在 Nginx 层或采集端做脱敏,或至少限制日志文件的访问权限(
chmod 640,仅运维组可读)。 - 防篡改:关键服务器建议把日志实时转发到独立的日志服务器(
rsyslog转发),攻击者入侵后通常会清空本地日志,异地留存是溯源的最后希望。
# /etc/rsyslog.d/90-remote.conf
*.* @@10.0.0.100:514 # @@ = TCP,@ = UDP
# 若需可靠传输,配置队列防止日志服务器不可达时丢日志
$ActionQueueFileName fwdRule
$ActionQueueMaxDiskSpace 1g
$ActionQueueSaveOnShutdown on
$ActionQueueType LinkedList
$ActionResumeRetryCount -1
企业QQ咨询




