Skip to content

日志分析脚本

本节实现一个通用的日志分析脚本,能够从日志文件中提取关键信息、统计错误频率、分析访问趋势。

功能设计

  • 统计各级别日志(ERROR/WARN/INFO)数量
  • 提取高频错误信息
  • 分析 Nginx 访问日志(IP、状态码、URL)
  • 生成分析报告

Nginx 访问日志分析

bash
#!/usr/bin/env bash
#
# Nginx 访问日志分析脚本
# 用法:./analyze_nginx.sh <日志文件> [--top N]
#

set -euo pipefail

LOG_FILE="${1:-/var/log/nginx/access.log}"
TOP_N="${2:-10}"

[[ ! -f "$LOG_FILE" ]] && { echo "日志文件不存在:$LOG_FILE" >&2; exit 1; }

echo "========================================"
echo "  Nginx 访问日志分析报告"
echo "  文件:$LOG_FILE"
echo "  时间:$(date '+%Y-%m-%d %H:%M:%S')"
echo "========================================"

# 总访问量
total=$(wc -l < "$LOG_FILE")
echo ""
echo "📊 总访问量:$total 条"

# 按状态码统计
echo ""
echo "📈 HTTP 状态码统计:"
awk '{print $9}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -20 | \
    awk '{printf "  %s: %d 次\n", $2, $1}'

# TOP IP
echo ""
echo "🌐 访问 TOP ${TOP_N} IP:"
awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -rn | head "$TOP_N" | \
    awk '{printf "  %-20s %d 次\n", $2, $1}'

# TOP URL
echo ""
echo "🔗 访问 TOP ${TOP_N} URL:"
awk '{print $7}' "$LOG_FILE" | sort | uniq -c | sort -rn | head "$TOP_N" | \
    awk '{printf "  %d 次  %s\n", $1, $2}'

# 错误请求(4xx/5xx)
echo ""
echo "❌ 错误请求统计(4xx/5xx):"
awk '$9 ~ /^[45]/' "$LOG_FILE" | awk '{print $9, $7}' | \
    sort | uniq -c | sort -rn | head "$TOP_N" | \
    awk '{printf "  %d 次  [%s] %s\n", $1, $2, $3}'

# 按小时统计访问量
echo ""
echo "⏰ 按小时访问量分布:"
awk '{print $4}' "$LOG_FILE" | cut -d: -f2 | sort | uniq -c | \
    awk '{printf "  %02d:00  %d 次\n", $2, $1}' | sort

# 流量统计(字节)
echo ""
total_bytes=$(awk '{sum += $10} END {print sum}' "$LOG_FILE" 2>/dev/null || echo 0)
total_mb=$(( total_bytes / 1024 / 1024 ))
echo "📦 总流量:${total_mb} MB(${total_bytes} 字节)"

通用错误日志分析

bash
#!/usr/bin/env bash
#
# 通用应用日志分析脚本
# 用法:./analyze_log.sh <日志文件> [--date 2025-01-01]
#

set -euo pipefail

LOG_FILE="${1:?请提供日志文件路径}"
FILTER_DATE="${2:-}"

# 如果指定日期,只分析该日期的日志
if [[ -n "$FILTER_DATE" ]]; then
    log_content=$(grep "$FILTER_DATE" "$LOG_FILE")
else
    log_content=$(cat "$LOG_FILE")
fi

echo "========================================"
echo "  日志分析报告"
echo "  文件:$LOG_FILE"
echo "  日期过滤:${FILTER_DATE:-全部}"
echo "========================================"

# 各级别统计
echo ""
echo "📊 日志级别统计:"
for level in ERROR WARN INFO DEBUG; do
    count=$(echo "$log_content" | grep -c "\[$level\]" 2>/dev/null || echo 0)
    printf "  %-8s %d 条\n" "$level" "$count"
done

# 错误信息 TOP 10
echo ""
echo "❌ 高频错误 TOP 10:"
echo "$log_content" | grep "\[ERROR\]" | \
    sed 's/.*\[ERROR\] //' | \
    sort | uniq -c | sort -rn | head 10 | \
    awk '{count=$1; $1=""; printf "  %d 次  %s\n", count, $0}'

# 最新 20 条错误
echo ""
echo "🕐 最近 20 条错误:"
echo "$log_content" | grep "\[ERROR\]" | tail -20 | \
    while IFS= read -r line; do
        echo "  $line"
    done

日志定期清理脚本

bash
#!/usr/bin/env bash
#
# 日志清理脚本
# 用法:./cleanup_logs.sh [--days 30] [--dir /var/log/myapp]
#

set -euo pipefail

KEEP_DAYS=30
LOG_DIR="/var/log/myapp"
DRY_RUN=false

while [[ $# -gt 0 ]]; do
    case "$1" in
        --days)    KEEP_DAYS="$2"; shift 2 ;;
        --dir)     LOG_DIR="$2"; shift 2 ;;
        --dry-run) DRY_RUN=true; shift ;;
        *) echo "未知参数:$1"; exit 1 ;;
    esac
done

echo "日志清理 | 目录:$LOG_DIR | 保留天数:$KEEP_DAYS"
$DRY_RUN && echo "(演练模式,不实际删除)"

total_size_before=$(du -sh "$LOG_DIR" 2>/dev/null | cut -f1)

# 查找并删除过期日志
find "$LOG_DIR" -name "*.log" -mtime "+$KEEP_DAYS" | while read -r file; do
    size=$(du -h "$file" | cut -f1)
    echo "  删除:$file$size)"
    $DRY_RUN || rm -f "$file"
done

# 压缩 7 天前的日志
find "$LOG_DIR" -name "*.log" -mtime +7 -not -name "*.gz" | while read -r file; do
    echo "  压缩:$file"
    $DRY_RUN || gzip "$file"
done

total_size_after=$(du -sh "$LOG_DIR" 2>/dev/null | cut -f1)
echo "清理完成 | 清理前:$total_size_before | 清理后:$total_size_after"

使用方法

bash
# 分析 nginx 日志
chmod +x analyze_nginx.sh
./analyze_nginx.sh /var/log/nginx/access.log --top 20

# 分析应用日志(只看今天)
./analyze_log.sh /var/log/myapp/app.log "$(date +%Y-%m-%d)"

# 定期清理(加入 cron)
# 0 2 * * * /opt/scripts/cleanup_logs.sh --days 30 --dir /var/log/myapp