服务器日志分析入门

结论: 服务器日志分析的核心方法是"定位日志源 → 用命令行做聚合统计 → 把结论固化成监控与告警"三板斧。入门阶段只需掌握 /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.logauditd 审计日志文件访问、命令执行审计
/var/log/nginx/access.logNginx 访问日志请求量、状态码、来源 IP、耗时
/var/log/nginx/error.logNginx 错误日志upstream 失败、配置错误、权限
/var/log/mysql/error.logMySQL 错误日志启动失败、崩溃、连接错误
/var/log/mysql/slow.logMySQL 慢查询日志超过阈值的 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 日志全部丢失。这是排查宕机时最痛的坑。上线第一步就该开启持久化:

bash
# 开启 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-prettyJSON 格式输出(便于程序处理)
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)。

实战:排查服务为什么起不来

bash
# 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 中):

nginx
log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

一条真实日志长这样:

text
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_userHTTP 认证用户名,无则 --
4$time_local本地时间,格式 日/月/年:时:分:秒 时区[12/Mar/2026:14:23:11 +0800]
5$request请求行:方法 + URI + 协议"GET /api/orders HTTP/1.1"
6$statusHTTP 状态码200
7$body_bytes_sent响应体字节数(不含头)1532
8$http_referer来源页-
9$http_user_agent客户端标识Mozilla/5.0 ...

生产环境强烈建议扩展日志格式,加上请求耗时与真实客户端 IP,否则慢请求分析和攻击溯源都做不了:

nginx
# /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

bash
# 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 统计实战:状态码分布

bash
# 状态码分布($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 -20

3.4 awk 统计实战:慢请求

慢请求分析需要日志中已包含 $request_time。假设日志格式为扩展后的 main,$request_time 位于 rt= 之后:

bash
# 提取耗时超过 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 其他常用统计

bash
# 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 慢查询与错误日志

bash
# 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 StackElasticsearch + Logstash + Beats + Kibana全文索引,检索极快高(内存 4GB 起,生产建议 8GB+)中大型,需要复杂检索与聚合
EFKElasticsearch + Fluentd/Fluent Bit + Kibana同上中高Kubernetes 环境常用
Loki + GrafanaLoki + Promtail + Grafana只索引标签,原文压缩存储低(约为 ELK 的 1/5~1/10)中小规模,已用 Grafana 的团队
GraylogGraylog + MongoDB + Elasticsearch中等中需要开箱即用的告警与权限

选择建议:已有 Prometheus + Grafana 监控栈的中小团队首选 Loki,资源友好且与现有看板整合;需要复杂全文检索、日志量很大或有合规审计需求的选 ELK。

Loki 的最小可用配置(Promtail 采集端示例):

yaml
# /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: nginx

LogQL(Loki 的查询语言)入门示例:

logql
# 统计 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 控制单文件大小与份数,避免撑爆磁盘——磁盘满导致的故障在运维事故中占比很高。
bash
# /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 转发),攻击者入侵后通常会清空本地日志,异地留存是溯源的最后希望。
bash
# /etc/rsyslog.d/90-remote.conf
*.* @@10.0.0.100:514        # @@ = TCP,@ = UDP
# 若需可靠传输,配置队列防止日志服务器不可达时丢日志
$ActionQueueFileName fwdRule
$ActionQueueMaxDiskSpace 1g
$ActionQueueSaveOnShutdown on
$ActionQueueType LinkedList
$ActionResumeRetryCount -1