日志分析:从日志文件提取统计 | Log Analysis: Extracting Statistics from Log Files

章节概述

核心理念:日志是系统的”黑匣子”,掌握日志分析能力意味着你能从海量数据中快速定位问题、发现规律。Bash 提供了 awk、grep、sort、uniq 等工具链,可以像 C 语言处理结构化数据一样高效地分析日志。


第1节:awk 统计日志

1.1 awk 基础统计

awk 是日志分析的核心工具,它的语法与 C 语言有相似之处——都支持条件判断、循环和格式化输出。

# 统计日志总行数
awk 'END {print NR}' access.log
 
# 提取特定字段(以空格分隔)
awk '{print $1, $7}' access.log
 
# 按条件过滤(HTTP 500 错误)
awk '$9 >= 500' access.log
 
# 求和计算(统计响应时间总和)
awk '{sum += $NF} END {print "Total:", sum}' access.log
 
# 计算平均值
awk '{sum += $NF; count++} END {print "Average:", sum/count}' access.log

1.2 awk 按状态码分类统计

# 统计每个 HTTP 状态码的出现次数
awk '{print $9}' access.log | sort | uniq -c | sort -rn
 
# 更详细的统计(状态码 + 路径)
awk '{print $9, $7}' access.log | sort | uniq -c | sort -rn | head -20
 
# awk 内部分组统计(无需外部 sort/uniq)
awk '{count[$9]++} END {for (code in count) print code, count[code]}' access.log
 
# 按小时统计请求量
awk '{split($4,a,":"); hour=a[2]; count[hour]++} END {for (h in count) print h":00", count[h]}' access.log | sort
awk 变量说明
NR当前行号(累计)
NF当前行字段数
$0整行内容
$1 ~ $N第 N 个字段
$9HTTP 状态码(常见位置)
$7请求路径(常见位置)
$NF最后一个字段(常为响应时间)

第2节:grep 过滤

2.1 基础过滤

# 查找包含 ERROR 的行
grep "ERROR" app.log
 
# 忽略大小写
grep -i "error" app.log
 
# 反向匹配(排除 DEBUG)
grep -v "DEBUG" app.log
 
# 显示匹配行号
grep -n "timeout" app.log
 
# 使用正则表达式
grep -E "^[0-9]{4}-[0-9]{2}-[0-9]{2}" app.log
 
# 显示匹配行的前后各 3 行
grep -B3 -A3 "critical error" app.log

2.2 高级过滤技巧

# 递归搜索目录下所有日志
grep -rn "ERROR" /var/log/myapp/
 
# 只搜索 .log 文件
grep -rn "ERROR" --include="*.log" /var/log/
 
# 多模式匹配
grep -E "ERROR|WARN|CRITICAL" app.log
 
# 二进制文件中搜索(如压缩日志)
zgrep "ERROR" app.log.gz
 
# 统计匹配行数
grep -c "404" access.log

第3节:sort + uniq 计数

3.1 排序与去重

# 基础去重计数
sort access.log | uniq -c
 
# 按出现次数降序排列
sort access.log | uniq -c | sort -rn
 
# 只显示重复行
sort access.log | uniq -d
 
# 只显示唯一行(不重复的)
sort access.log | uniq -u
 
# 忽略前 N 个字段进行排序(如忽略时间戳)
sort -k2 access.log | uniq -c

3.2 实用统计组合

# 统计最常访问的 IP
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
 
# 统计最常访问的 URL
awk '{print $7}' access.log | sort | uniq -c | sort -rn | head -10
 
# 统计每个 IP 的唯一请求数
awk '{print $1}' access.log | sort -u | wc -l
 
# 按日期统计访问量
awk '{print $4}' access.log | cut -d: -f1 | sort | uniq -c

第4节:时间范围提取

4.1 按时间戳过滤

# 提取特定日期的日志(Apache/Nginx 格式)
awk '/22\/Aug\/2026/' access.log
 
# 提取时间范围(10:00 到 11:00)
awk -F'[: ]' '$5 >= "10" && $5 <= "11"' access.log
 
# 提取最近 1 小时的日志(假设日志有时间戳)
awk -v time="$(date -d '1 hour ago' '+%H:%M')" '$4 >= "["time' access.log
 
# 使用 grep 按日期过滤
grep "2026-08-22" app.log
 
# 提取特定日期范围(多天)
grep -E "2026-08-(20|21|22)" app.log

4.2 时间格式转换与计算

# 将日志时间戳转换为 epoch
awk '{print $4}' access.log | tr -d '[]' | xargs -I{} date -d "{}" +%s
 
# 计算两个时间戳之间的差值
start_epoch=$(date -d "2026-08-22 10:00:00" +%s)
end_epoch=$(date -d "2026-08-22 11:00:00" +%s)
echo "Duration: $((end_epoch - start_epoch)) seconds"

第5节:错误率统计

5.1 完整错误率分析

#!/bin/bash
# 日志错误率统计脚本
 
LOG_FILE="${1:-access.log}"
 
# 总请求数
total=$(wc -l < "$LOG_FILE")
 
# 4xx 错误数
client_errors=$(awk '$9 >= 400 && $9 < 500' "$LOG_FILE" | wc -l)
 
# 5xx 错误数
server_errors=$(awk '$9 >= 500 && $9 < 600' "$LOG_FILE" | wc -l)
 
# 成功请求数(2xx + 3xx)
success=$(awk '$9 >= 200 && $9 < 400' "$LOG_FILE" | wc -l)
 
# 计算错误率
client_rate=$(echo "scale=2; $client_errors * 100 / $total" | bc)
server_rate=$(echo "scale=2; $server_errors * 100 / $total" | bc)
 
echo "=== 日志统计报告 ==="
echo "总请求数:    $total"
echo "成功请求:    $success"
echo "4xx 错误:    $client_errors ($client_rate%)"
echo "5xx 错误:    $server_errors ($server_rate%)"
echo "错误率:      $(echo "scale=2; ($client_errors + $server_errors) * 100 / $total" | bc)%"

5.2 按时间段统计错误率

# 按小时统计错误率
awk '{
    split($4, a, ":");
    hour = a[2];
    total[hour]++;
    if ($9 >= 400) errors[hour]++;
}
END {
    for (h in total) {
        rate = errors[h] * 100 / total[h];
        printf "%s:00 - Total: %d, Errors: %d, Rate: %.2f%%\n",
               h, total[h], errors[h]+0, rate;
    }
}' access.log | sort

第6节:综合实战案例

6.1 Apache 访问日志完整分析

#!/bin/bash
# Apache 日志分析一键脚本
 
LOG="${1:-/var/log/apache2/access.log}"
 
echo "========= Apache 日志分析 ========="
echo ""
echo "--- Top 10 访问 IP ---"
awk '{print $1}' "$LOG" | sort | uniq -c | sort -rn | head -10
 
echo ""
echo "--- Top 10 请求路径 ---"
awk '{print $7}' "$LOG" | sort | uniq -c | sort -rn | head -10
 
echo ""
echo "--- HTTP 状态码分布 ---"
awk '{print $9}' "$LOG" | sort | uniq -c | sort -rn
 
echo ""
echo "--- 每小时请求量 ---"
awk '{split($4,a,":"); print a[2]":00"}' "$LOG" | sort | uniq -c | sort -k2
 
echo ""
echo "--- 平均响应时间 ---"
awk '{sum += $NF; count++} END {printf "%.3f ms\n", sum/count}' "$LOG"

6.2 使用 awk 生成 CSV 报告

# 从日志生成 CSV
awk '{
    split($4, a, "[/:]");
    date = a[1] "-" a[2] "-" a[3];
    printf "%s,%s,%s,%s,%s\n", $1, date, $7, $9, $NF
}' access.log > report.csv
 
# 添加 CSV 头
sed -i '1i IP,Date,Path,StatusCode,ResponseTime' report.csv