)
Nginx的access_log每天产生几百万行绝大多数运维团队对它的利用方式是出了故障之后上去grep。这相当于有一台24小时运转的心电图仪但只在人已经倒下之后才去看回放。实际上access_log里有三类信号是可以实时监控并主动告警的5xx错误率——后端在崩但用户还没大面积投诉之前你就能知道请求耗时分布——接口在变慢还没到完全超时但已经在退化请求频率异常——某个IP在疯狂请求可能是爬虫也可能是攻击前兆我们在给一个电商客户做运维时就靠access_log的5xx告警在凌晨3点发现了后端服务OOM——比用户投诉早了40分钟比APM告警因为采样率设了10%漏掉了早了15分钟。下面是完整方案从log_format设计到告警规则直接能用。一、前提标准化log_format不标准的日志没法监控很多Nginx还在用默认的combined格式这个格式最大的问题是没有请求耗时字段——你连哪个请求慢都看不出来。1.1 推荐的log_format配置# /etc/nginx/nginx.conf http { # 标准化日志格式包含所有监控所需字段 log_format monitor $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $request_time $upstream_response_time $upstream_addr $upstream_status; access_log /var/log/nginx/access.log monitor; error_log /var/log/nginx/error.log warn; }各字段说明字段含义监控用途$statusHTTP状态码5xx/4xx统计$request_time请求总耗时秒慢请求检测$upstream_response_time后端响应耗时区分Nginx慢还是后端慢$upstream_status后端返回状态码后端故障定位$body_bytes_sent响应体大小带宽异常检测$remote_addr客户端IP异常IP检测1.2 按域名分文件多站点场景server { server_name api.example.com; access_log /var/log/nginx/api_access.log monitor; } server { server_name www.example.com; access_log /var/log/nginx/www_access.log monitor; }分文件的好处每个服务独立统计告警时直接知道哪个服务出问题。二、整体架构日志采集→统计→告警方案选型说明方案优点缺点适用场景Zabbix Agent 脚本轻量、无额外组件统计逻辑在脚本里中小规模20台NginxFilebeat ES 告警实时性好、查询灵活需要维护ES集群大规模/已有ELK两者结合告警走Zabbix成熟、查询走ES双链路维护成本推荐兼顾告警排查本文重点讲Zabbix Agent 统计脚本这条路轻量、落地快、90%场景够用Filebeat采集配置附在最后作为扩展。三、核心实现Zabbix Agent自定义监控项3.1 日志统计脚本这个脚本每分钟跑一次统计过去1分钟内access_log的各项指标#!/bin/bash# /etc/zabbix/scripts/nginx_log_monitor.sh# 用途解析Nginx access_log输出过去1分钟的统计指标# 被Zabbix Agent的UserParameter调用LOG_FILE${1:-/var/log/nginx/access.log}METRIC${2}# 获取1分钟前的时间戳用于过滤日志行ONE_MIN_AGO$(date-d1 minute ago%d/%b/%Y:%H:%M2/dev/null||\date-v-1M%d/%b/%Y:%H:%M)# 兼容Linux和macOS# 提取最近1分钟的日志基于时间戳前缀匹配CURRENT_MIN$(date%d/%b/%Y:%H:%M)RECENT_LOGS$(awk-vt1$ONE_MIN_AGO-vt2$CURRENT_MIN\$0 ~ t1 || $0 ~ t2$LOG_FILE)case$METRICintotal_requests)echo$RECENT_LOGS|wc-l|tr-d ;;status_5xx)echo$RECENT_LOGS|awk{print $9}|grep-c^5[0-9][0-9]$;;status_4xx)echo$RECENT_LOGS|awk{print $9}|grep-c^4[0-9][0-9]$;;status_2xx)echo$RECENT_LOGS|awk{print $9}|grep-c^2[0-9][0-9]$;;slow_requests)# request_time 3秒的请求数量$11是request_time字段位置echo$RECENT_LOGS|awk$11 3.0 {count} END {print count0};;avg_request_time)# 平均请求耗时毫秒echo$RECENT_LOGS|awk$11 ~ /^[0-9]/ {sum$11; count} END { if(count0) printf %.0f, sum/count*1000; else print 0};;p95_request_time)# P95请求耗时毫秒echo$RECENT_LOGS|awk$11 ~ /^[0-9]/ {print $11}|sort-n|\awk{a[NR]$1} END {idxint(NR*0.95); if(idx0) printf %.0f, a[idx]*1000; else print 0};;top_ip_count)# 单IP最高请求数echo$RECENT_LOGS|awk{print $1}|sort|uniq-c|sort-rn|head-1|awk{print $1};;bandwidth_mb)# 出流量MBecho$RECENT_LOGS|awk{sum$10} END {printf %.2f, sum/1024/1024};;error_log_count)# error_log最近1分钟的error/crit/alert行数ERROR_LOG${LOG_FILE/access/error}if[[-f$ERROR_LOG]];thenawk-vt1$ONE_MIN_AGO-vt2$CURRENT_MIN\$0 ~ t1 || $0 ~ t2$ERROR_LOG|grep-c-E\[(error|crit|alert)\]elseecho0fi;;*)echoUsage:$0log_file metricechoMetrics: total_requests|status_5xx|status_4xx|status_2xx|slow_requests|avg_request_time|p95_request_time|top_ip_count|bandwidth_mb|error_log_countexit1;;esac部署# 放到Zabbix脚本目录chmodx /etc/zabbix/scripts/nginx_log_monitor.sh# 确保zabbix用户能读取Nginx日志usermod-aGadm zabbix# Debian/Ubuntu# 或者setfacl-mu:zabbix:r /var/log/nginx/access.log3.2 Zabbix Agent配置# /etc/zabbix/zabbix_agentd.d/nginx_log.conf # Nginx access_log 监控项 UserParameternginx.log[*],/etc/zabbix/scripts/nginx_log_monitor.sh $1 $2 # 使用示例 # nginx.log[/var/log/nginx/access.log,total_requests] → 过去1分钟总请求数 # nginx.log[/var/log/nginx/access.log,status_5xx] → 过去1分钟5xx数量 # nginx.log[/var/log/nginx/access.log,slow_requests] → 过去1分钟慢请求数 # nginx.log[/var/log/nginx/api_access.log,status_5xx] → API服务的5xx多站点重启Agent生效systemctl restart zabbix-agent# 本地验证zabbix_agentd-tnginx.log[/var/log/nginx/access.log,total_requests]# 期望输出nginx.log[/var/log/nginx/access.log,total_requests] [t|1234]3.3 Zabbix Server侧配置监控项在Zabbix Web界面为Nginx主机添加监控项或做成模板批量应用# Template: Template_Nginx_Log_Monitor# 监控项列表items:-name:Nginx - 每分钟总请求数key:nginx.log[/var/log/nginx/access.log,total_requests]type:ZABBIX_AGENTvalue_type:UNSIGNEDdelay:60s-name:Nginx - 每分钟5xx错误数key:nginx.log[/var/log/nginx/access.log,status_5xx]type:ZABBIX_AGENTvalue_type:UNSIGNEDdelay:60s-name:Nginx - 每分钟4xx错误数key:nginx.log[/var/log/nginx/access.log,status_4xx]type:ZABBIX_AGENTvalue_type:UNSIGNEDdelay:60s-name:Nginx - 慢请求数(3s)key:nginx.log[/var/log/nginx/access.log,slow_requests]type:ZABBIX_AGENTvalue_type:UNSIGNEDdelay:60s-name:Nginx - 平均响应时间(ms)key:nginx.log[/var/log/nginx/access.log,avg_request_time]type:ZABBIX_AGENTvalue_type:UNSIGNEDdelay:60s-name:Nginx - P95响应时间(ms)key:nginx.log[/var/log/nginx/access.log,p95_request_time]type:ZABBIX_AGENTvalue_type:UNSIGNEDdelay:60s-name:Nginx - 单IP最高请求数/分钟key:nginx.log[/var/log/nginx/access.log,top_ip_count]type:ZABBIX_AGENTvalue_type:UNSIGNEDdelay:60s-name:Nginx - 每分钟出流量(MB)key:nginx.log[/var/log/nginx/access.log,bandwidth_mb]type:ZABBIX_AGENTvalue_type:FLOATdelay:60s-name:Nginx - error_log错误数/分钟key:nginx.log[/var/log/nginx/access.log,error_log_count]type:ZABBIX_AGENTvalue_type:UNSIGNEDdelay:60s四、5类异常模式告警规则4.1 告警一5xx错误率突增这是最高优先级告警——5xx意味着后端返回了服务端错误通常是服务崩溃、OOM、数据库连接池耗尽。# 触发器15xx绝对数量trigger:name:Nginx 5xx错误突增{HOST.NAME}expression:last(/Template_Nginx_Log_Monitor/nginx.log[/var/log/nginx/access.log,status_5xx])20severity:HIGHdescription:过去1分钟5xx错误数超过20次# 触发器25xx比率避免低流量误报trigger:name:Nginx 5xx错误率5%{HOST.NAME}expression:last(/Template_Nginx_Log_Monitor/nginx.log[/var/log/nginx/access.log,status_5xx]) / last(/Template_Nginx_Log_Monitor/nginx.log[/var/log/nginx/access.log,total_requests]) 0.05 and last(/Template_Nginx_Log_Monitor/nginx.log[/var/log/nginx/access.log,total_requests]) 100severity:HIGHdescription:5xx占比超5%且请求总量100排除低流量时段干扰为什么要绝对值比率双条件只看绝对值凌晨流量低的时候3个5xx就报警可能就是某个爬虫触发的。只看比率高峰期5xx有100个但比率才1%不报警实际上100个用户已经受影响了。建议高峰期用绝对值兜底低谷期用比率触发。4.2 告警二慢请求突增# 触发器慢请求数量trigger:name:Nginx慢请求突增3s请求超10个/分钟{HOST.NAME}expression:last(/Template_Nginx_Log_Monitor/nginx.log[/var/log/nginx/access.log,slow_requests])10severity:WARNINGdescription:过去1分钟超过3秒的请求数10个# 触发器P95响应时间trigger:name:Nginx P95响应时间2s{HOST.NAME}expression:last(/Template_Nginx_Log_Monitor/nginx.log[/var/log/nginx/access.log,p95_request_time])2000severity:WARNINGdescription:P95响应时间超过2000ms# 触发器P95持续恶化连续3个周期trigger:name:Nginx P95持续恶化连续3分钟1.5s{HOST.NAME}expression:min(/Template_Nginx_Log_Monitor/nginx.log[/var/log/nginx/access.log,p95_request_time],3m)1500severity:HIGHdescription:P95响应时间连续3分钟1500ms服务持续退化4.3 告警三异常IP高频访问trigger:name:单IP请求频率异常500次/分钟{HOST.NAME}expression:last(/Template_Nginx_Log_Monitor/nginx.log[/var/log/nginx/access.log,top_ip_count])500severity:WARNINGdescription:某个IP在过去1分钟请求超过500次可能是CC攻击或恶意爬虫trigger:name:单IP请求频率严重异常2000次/分钟{HOST.NAME}expression:last(/Template_Nginx_Log_Monitor/nginx.log[/var/log/nginx/access.log,top_ip_count])2000severity:HIGHdescription:某个IP请求2000次/分钟疑似攻击收到这个告警后的自动化响应脚本可配成Zabbix Action#!/bin/bash# /etc/zabbix/scripts/auto_block_ip.sh# 自动封禁高频IP配合Zabbix Action触发LOG_FILE/var/log/nginx/access.logTHRESHOLD2000BLOCK_DURATION3600# 封禁1小时# 找出超过阈值的IPCURRENT_MIN$(date%d/%b/%Y:%H:%M)BAD_IPS$(awk-vt$CURRENT_MIN$0 ~ t {print $1}$LOG_FILE|\sort|uniq-c|sort-rn|awk-vth$THRESHOLD$1th {print $2})foripin$BAD_IPS;do# 使用iptables临时封禁if!iptables-CINPUT-s$ip-jDROP2/dev/null;theniptables-AINPUT-s$ip-jDROPecho$(date)Blocked$ip(will unblock after${BLOCK_DURATION}s)/var/log/zabbix/blocked_ips.log# 定时解封(sleep$BLOCK_DURATIONiptables-DINPUT-s$ip-jDROP)fidone4.4 告警四4xx集中爆发trigger:name:Nginx 404集中爆发100次/分钟{HOST.NAME}expression:last(/Template_Nginx_Log_Monitor/nginx.log[/var/log/nginx/access.log,status_4xx])100severity:INFORMATIONdescription:过去1分钟404超过100次可能是爬虫扫描路径或配置变更导致路由失效4.5 告警五出流量突增trigger:name:Nginx出流量突增正常值200%{HOST.NAME}expression:last(/Template_Nginx_Log_Monitor/nginx.log[/var/log/nginx/access.log,bandwidth_mb]) avg(/Template_Nginx_Log_Monitor/nginx.log[/var/log/nginx/access.log,bandwidth_mb],1h) * 2severity:HIGHdescription:当前分钟出流量超过最近1小时平均值的2倍可能有大文件下载/盗链/异常抓取五、告警通知模板企业微信Webhook示例告警内容不能只是Nginx有5xx——值班人员需要知道哪个服务、什么时候开始、当前多严重。#!/usr/bin/env python3# /etc/zabbix/alertscripts/wechat_nginx_alert.py# Zabbix告警通知脚本 - 企业微信机器人importsysimportjsonimporturllib.request WEBHOOK_URLhttps://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyYOUR_KEYdefsend_alert(subject,message):# 解析Zabbix传入的参数alert_data{msgtype:markdown,markdown:{content:f## ⚠️ Nginx告警 **{subject}**{message}**处理建议** 1. 检查nginx -t配置是否正常 2. 查看upstream后端服务状态 3. tail -100 /var/log/nginx/error.log 看错误详情 4. 若为攻击检查WAF/限流规则}}requrllib.request.Request(WEBHOOK_URL,datajson.dumps(alert_data).encode(utf-8),headers{Content-Type:application/json})urllib.request.urlopen(req)if__name____main__:send_alert(sys.argv[1],sys.argv[2])六、进阶Filebeat采集配置大规模场景如果Nginx台数超过20台或者需要日志查询而不仅仅是告警推荐加一条Filebeat采集链路# /etc/filebeat/filebeat.ymlfilebeat.inputs:-type:logenabled:truepaths:-/var/log/nginx/access.logfields:log_type:nginx_accessenv:production# 解析log_format中的字段processors:-dissect:tokenizer:%{client_ip} - %{user} [%{timestamp}] %{method} %{url} %{http_version} %{status} %{bytes} %{referer} %{user_agent} %{request_time} %{upstream_time} %{upstream_addr} %{upstream_status}field:messagetarget_prefix:nginx-type:logenabled:truepaths:-/var/log/nginx/error.logfields:log_type:nginx_errorenv:productionmultiline:pattern:^\d{4}/\d{2}/\d{2}negate:truematch:afteroutput.elasticsearch:hosts:[http://es-server:9200]indices:-index:nginx-access-%{yyyy.MM.dd}when.equals:fields.log_type:nginx_access-index:nginx-error-%{yyyy.MM.dd}when.equals:fields.log_type:nginx_error七、实际效果和避坑我们部署这套方案半年几个数据指标部署前部署后5xx发现时间用户投诉后平均45分钟1分钟内告警慢请求发现周例会看APM报表实时告警趋势追踪CC攻击响应运维手动发现封IP自动检测自动封禁故障定位时间需要登录机器grep日志看Zabbix图表直接定位时间段踩坑记录坑1日志轮转导致统计中断Nginx默认用logrotate每天切割日志切割后access.log变成空文件统计脚本读到0。解决# /etc/logrotate.d/nginx 中确保使用copytruncate而不是rename/var/log/nginx/*.log{daily rotate14compress delaycompress copytruncate# 关键截断而非重命名脚本不会读到空文件notifempty}坑2高并发下awk统计脚本本身变慢日志量大每分钟10万行时每分钟全量awk一次access.log会消耗较多CPU。解决改用增量读取——记录上次读取的offset只处理新增部分# 使用logtail2Zabbix官方推荐的增量日志读取工具OFFSET_FILE/tmp/nginx_access.offsetNEW_LINES$(logtail2-f$LOG_FILE-o$OFFSET_FILE)echo$NEW_LINES|awk{print $9}|grep-c^5[0-9][0-9]$坑3时区问题导致统计偏移Nginx日志时间戳用的是服务器本地时区如果date命令和Nginx时区不一致过滤会错位。解决统一用UTC或确保系统时区和Nginx一致。# 确认时区一致date%Z# 系统时区grep-itimezone /etc/nginx/nginx.conf# Nginx通常跟系统走八、和运维体系的打通单纯的Nginx日志告警只是发现问题的第一步。如果告警发出来没人跟进、没有记录、下次同样的问题还是靠人肉排查那这套监控就只完成了一半。我们实际项目里把Nginx告警接到了冠服云EMS平台——告警触发后自动创建事件工单工单里带有告警时间段的日志快照链接Zabbix Graph URL值班人员打开工单就能看到什么时候开始的、当时的5xx数量曲线、哪些IP在访问。处理完之后工单关闭自动归档到知识库下次同类告警触发时工单里会关联历史处理记录。整个链路是Nginx日志→Zabbix告警→EMS工单→处理→闭环归档不需要人记着上次这种情况是怎么处理的。小结Nginx日志监控不需要复杂架构核心就三步标准化log_format必须有$request_time和$upstream_response_time否则慢请求和后端故障区分不了轻量统计精准告警Zabbix Agent 一个shell脚本就能实现5类核心告警模式不需要ELK告警规则要分层绝对值兜底比率触发持续性确认三层过滤避免误报一台Nginx跑起来之后再用Zabbix模板批量克隆到所有Nginx节点——配置一次覆盖所有。站内链接CSDN-44Redis监控https://blog.csdn.net/weixin_70758133/article/details/161310134?spm1011.2415.3001.5331CSDN-46告警升级https://blog.csdn.net/weixin_70758133/article/details/161418720?spm1011.2415.3001.5331