用 cron + awk 每小时精准截取并分析 nginx 过去一小时访问日志,需预生成起止时间、处理日期格式兼容性、flock 防并发、正则提取状态码、应对日志轮转与跨天边界。

用 cron + awk 每小时触发一次日志分析
Linux 本身不提供“自动生成报表”功能,必须组合调度(cron)和文本处理(awk、grep、sed)来实现。关键不是“定时”,而是“精准截取过去一小时的日志段”——很多脚本直接用 date -d '1 hour ago' 获取时间戳,但日志格式不统一时会漏行或错位。
实操建议:
- 先确认日志时间格式:比如
/var/log/syslog是Mar 15 14:22:03,而/var/log/nginx/access.log是15/Mar/2024:14:22:03,匹配逻辑完全不同 - 避免用
tail -n 1000粗暴截取,它无法保证时间连续性;应使用awk做时间范围过滤 - 在
cron中用0 * * * *表示整点执行,但注意:如果分析脚本耗时超过 60 秒,下一轮可能并发冲突,建议加锁或用flock
用 awk 精确提取过去 60 分钟的 Nginx 访问日志
Nginx 默认日志时间是 [15/Mar/2024:14:22:03 +0800] 格式,awk 无法直接解析带斜杠和冒号的字符串,需先转换为秒级时间戳再比较。硬编码时间范围容易出错,推荐用 date 预生成起止时间变量传入。
示例(保存为 /usr/local/bin/report-hourly.sh):
#!/bin/bash
START=$(date -d '1 hour ago' '+%d/%b/%Y:%H:%M:%S' | sed 's/^0\([0-9]\)/\1/')
END=$(date '+%d/%b/%Y:%H:%M:%S' | sed 's/^0\([0-9]\)/\1/')
LOGFILE="/var/log/nginx/access.log"
REPORT="/var/log/nginx/report_$(date -d '1 hour ago' +\%Y\%m\%d_\%H).txt"
<p>awk -v start="$START" -v end="$END" '
$4 ~ /^[/ {</p><h1>提取 $4 中的 [15/Mar/2024:14:22:03 形式,去掉开头[和结尾]</h1><pre class="brush:php;toolbar:false;">t = substr($4, 2, length($4)-2)
if (t >= start && t <p>}' "$LOGFILE" | \
awk '{print $1, $7, $9}' | sort | uniq -c | sort -nr > "$REPORT"</p>注意:sed 's/^0\([0-9]\)/\1/' 是为了兼容 date 输出的单数字日期(如 05 → 5),否则 awk 字符串比较会失败。
crontab 中防止脚本重复执行的保险写法
日志分析脚本若运行超时,cron 下一周期仍会启动新进程,导致资源争抢或报表覆盖。不能只依赖“每小时执行一次”的设定,得主动控制并发。
推荐在 crontab 条目里用 flock 加锁:
0 * * * * flock -n /tmp/report-hourly.lock -c '/usr/local/bin/report-hourly.sh'
说明:
-
flock -n表示非阻塞,如果锁已被占用,命令直接退出,不会排队等待 - 锁文件路径要绝对且唯一,
/tmp/下临时文件重启后消失,不影响下次执行 - 不要用
ps | grep判断进程是否存在——竞态条件严重,两个实例可能同时判定“无进程”然后双双启动
报表内容易被忽略的字段陷阱
很多人默认用 $9 取 HTTP 状态码,但在 Nginx 日志中,如果启用了 log_format 自定义,字段顺序可能变化;更稳妥的是用 awk 按正则提取状态码,例如 match($0, /" [0-9]{3} /)。
另外,按小时统计 PV/UV 时:
-
uniq -c统计 IP($1)只能算 PV,UV 需配合sort -u去重后再计数 - 如果日志含 gzip 压缩(
.log.gz),不能直接cat,要用zcat或gunzip -c - 跨天边界(如 23:59 → 00:00)时,
date -d '1 hour ago'在 00:00 执行会得到昨天 23:00,逻辑正确,但需确保日志文件没被 logrotate 搬走或压缩
最麻烦的其实是日志轮转时机——如果 logrotate 在 00:03 触发,而你的脚本在 01:00 运行,就可能漏掉 00:00–00:03 的原始日志。这时候得查 /etc/logrotate.d/ 配置,或者改用 journalctl --since "2024-03-15 14:00:00" --until "2024-03-15 15:00:00" 替代文件读取。











