openEuler下Nginx日志分析:Shell脚本实现轻量级监控与性能优化

openEuler下Nginx日志分析:Shell脚本实现轻量级监控与性能优化
1. 项目概述与核心价值最近在折腾一个跑在 openEuler 虚拟机上的 Web 服务Nginx 的访问日志一天下来能有好几百兆。看着那一行行密密麻麻的日志想快速知道今天谁访问最频繁、哪个接口响应最慢、有没有异常的爬虫在扫站靠肉眼一条条看肯定是不现实的。网上虽然有成套的 ELKElasticsearch, Logstash, Kibana或者 Grafana Loki 方案但对于单个服务、轻量级的需求来说部署和维护成本有点高杀鸡用牛刀了。于是我就琢磨着写一个轻量级的 Nginx 日志分析脚本直接在 openEuler 服务器上跑定时执行把关键指标以最直观的方式输出甚至能发个邮件报告。这活儿听起来简单但真要把日志解析、数据聚合、结果呈现都做好里面有不少门道。今天就把我折腾这个脚本的过程、踩过的坑以及最终成型的方案详细跟大家分享一下。无论你是运维新手还是想给自己负责的服务加个轻量级监控这套思路和脚本都能直接拿去用。这个脚本的核心价值在于“轻量”和“直达痛点”。它不依赖复杂的分布式架构就是纯 Shell 脚本配合 awk, sort, uniq 这些 Linux 老伙计加上一点 Python 或 Go 来增强表现力可选。你只需要有一台安装了 openEuler或其他 Linux 发行版的服务器Nginx 日志按标准格式输出就能立刻获得对服务访问情况的洞察力。特别适合个人项目、初创公司早期、或者作为大型监控系统的补充和快速验证工具。2. 环境准备与日志格式确认2.1 openEuler 虚拟机基础环境我的实验环境是一台在 VMware Workstation 17 上安装的 openEuler 22.03 LTS 虚拟机。选择 openEuler 是因为它作为企业级的 Linux 发行版在安全性、稳定性以及对国产硬件平台的兼容性上表现不错而且其软件源和社区生态也越来越丰富。对于这个项目来说任何主流的 Linux 发行版如 CentOS, Ubuntu, Debian其实都可以操作大同小异。首先确保你的 openEuler 系统已经安装了必要的工具。打开终端用 root 或具有 sudo 权限的用户执行# 更新系统软件包索引 dnf update -y # 安装本项目可能用到的核心工具 # vim: 文本编辑器用于编写和修改脚本。 # curl/wget: 用于从网络下载文件如下载示例日志或安装脚本。 # awk/sed/grep: 文本处理三剑客日志分析的基石。 # sort/uniq: 数据排序和去重用于统计排名。 # mailx: 用于发送邮件报告如果需要邮件功能。 dnf install -y vim wget curl gawk sed grep coreutils mailx如果你的 openEuler 是最小化安装可能默认没有mailx需要手动安装mailx或配置其他邮件发送方式如 Python 的 smtplib。这里我们先确保基础文本处理工具到位。2.2 Nginx 日志格式解析脚本要正确工作前提是能正确解析 Nginx 的日志格式。Nginx 默认的访问日志格式定义在nginx.conf文件中通常是log_format指令。最常见的格式是combinedlog_format combined $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent;对应的一条日志可能长这样192.168.1.100 - - [10/Apr/2023:15:30:22 0800] GET /api/user?id123 HTTP/1.1 200 1534 https://example.com/ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36我们需要清楚地知道每个字段代表什么$remote_addr: 客户端 IP 地址192.168.1.100$remote_user: 远程用户用于 HTTP 认证通常为-[$time_local]: 请求时间戳[10/Apr/2023:15:30:22 0800]$request: 请求方法、URI 和协议GET /api/user?id123 HTTP/1.1$status: HTTP 状态码200$body_bytes_sent: 发送给客户端的正文字节数1534$http_referer: 来源页面https://example.com/$http_user_agent: 用户代理字符串即浏览器或爬虫标识关键一步确认你的 Nginx 实际使用的日志格式。执行nginx -T命令需要 Nginx 有-T参数支持或直接查看你的 Nginx 配置文件通常在/etc/nginx/nginx.conf或/usr/local/nginx/conf/nginx.conf找到access_log指令和它引用的log_format。务必确保你的脚本解析逻辑与实际的日志格式严格对应否则字段会错乱。我的脚本默认按combined格式解析这也是最通用的格式。注意有些配置可能使用了自定义的log_format比如加入了$request_time请求处理时间、$upstream_response_time上游响应时间等非常有价值的字段。如果你的日志里有这些字段强烈建议在脚本中利用起来它们对于分析接口性能至关重要。3. 脚本核心功能设计与模块拆解一个有用的日志分析脚本不应该只是简单统计行数。我把它设计成模块化的每个模块负责一个维度的分析最后可以汇总输出。这样也方便你后续增删功能。核心模块包括流量概览总请求数、独立 IP 数、总流量消耗。状态码分析统计各类 HTTP 状态码2xx, 3xx, 4xx, 5xx的数量和比例快速发现错误。热门请求分析找出访问量最高的 URL或接口有助于发现热点或潜在的攻击路径。客户端分析统计访问最频繁的 IP 地址可能是正常用户也可能是爬虫或攻击源。用户代理分析识别主要的浏览器、爬虫如 Googlebot, Baiduspider或异常客户端。耗时分析如果日志包含时间字段统计平均响应时间找出最慢的请求。报告输出与告警将分析结果格式化成易读的报告输出到屏幕、文件或通过邮件发送。可以设置阈值当错误率或慢请求超过一定比例时触发告警。基于这些模块脚本的基本工作流程是指定要分析的日志文件路径可以是单个文件也可以是按天切割的文件如access.log、access.log-20231015.gz。按行读取日志文件。使用awk按预定义的格式拆分每一行日志提取出各个字段。根据不同的分析维度对提取出的字段进行聚合计算计数、求和、排序等。将各模块的分析结果汇总格式化输出。下面我们进入具体的实现环节。4. 基础 Shell 脚本实现与逐行解析我们先实现一个最基础的、纯 Shell (bash) 版本的脚本。它不依赖外部语言在任何标准的 Linux 环境包括 openEuler中都能直接运行。创建一个名为nginx_log_analyzer.sh的文件#!/bin/bash # 基础版 Nginx 日志分析脚本 # 适用于 combined 日志格式 LOG_FILE${1:-/var/log/nginx/access.log} # 允许通过参数传入日志路径默认使用 /var/log/nginx/access.log if [ ! -f $LOG_FILE ]; then echo 错误日志文件不存在 - $LOG_FILE exit 1 fi echo echo Nginx 访问日志分析报告 echo 分析文件: $LOG_FILE echo 生成时间: $(date %Y-%m-%d %H:%M:%S) echo echo 4.1 流量概览模块实现这个模块我们统计三个基础指标。# 模块1: 流量概览 echo 1. 流量概览 echo ---------------------------------------------- # 总请求数直接计算日志行数排除可能的空行 TOTAL_REQUESTS$(grep -c ^ $LOG_FILE 2/dev/null || wc -l $LOG_FILE) echo 总请求数: $TOTAL_REQUESTS # 独立IP数提取第一列remote_addr去重计数 UNIQUE_IPS$(awk {print $1} $LOG_FILE | sort | uniq | wc -l) echo 独立IP地址数: $UNIQUE_IPS # 总流出流量求和第10列body_bytes_sent。注意combined格式下body_bytes_sent通常是第10列。 # 使用awk进行求和效率远高于在Shell循环中累加。 TOTAL_BYTES$(awk {sum$10} END {print sum} $LOG_FILE) # 将字节转换为更易读的单位 (KB, MB, GB) if [ $TOTAL_BYTES -gt 1073741824 ]; then TRAFFIC_READABLE$(echo scale2; $TOTAL_BYTES/1073741824 | bc) echo 总流出流量: ${TRAFFIC_READABLE} GB elif [ $TOTAL_BYTES -gt 1048576 ]; then TRAFFIC_READABLE$(echo scale2; $TOTAL_BYTES/1048576 | bc) echo 总流出流量: ${TRAFFIC_READABLE} MB elif [ $TOTAL_BYTES -gt 1024 ]; then TRAFFIC_READABLE$(echo scale2; $TOTAL_BYTES/1024 | bc) echo 总流出流量: ${TRAFFIC_READABLE} KB else echo 总流出流量: ${TOTAL_BYTES} Bytes fi echo 实操心得在求和总流量时使用awk的内置数值计算{sum$10}比在 Shell 里用while read循环累加快得多尤其是处理百万行日志时性能差异非常明显。另外bc命令用于浮点数计算如果系统未安装可以用dnf install bc -y安装或者用printf进行格式转换。4.2 状态码分析模块实现HTTP 状态码是衡量服务健康度的晴雨表。# 模块2: HTTP 状态码分析 echo 2. HTTP 状态码分布 echo ---------------------------------------------- # 使用awk提取第9列status然后排序、计数、按数量降序排列 awk {print $9} $LOG_FILE | sort | uniq -c | sort -rn | head -20 | while read count code; do # 计算百分比 if [ $TOTAL_REQUESTS -ne 0 ]; then percentage$(echo scale2; $count*100/$TOTAL_REQUESTS | bc) # 根据状态码范围添加简单描述 case $code in 2*) desc成功 ;; 3*) desc重定向 ;; 4*) desc客户端错误 ;; 5*) desc服务器错误 ;; *) desc其他 ;; esac printf %-6s: %-6d 次 (%-5s%% ) [%s]\n $code $count $percentage $desc fi done # 单独统计4xx和5xx错误的总数便于监控 ERROR_4XX$(awk $9 ~ /^4[0-9]{2}$/ {count} END {print count0} $LOG_FILE) ERROR_5XX$(awk $9 ~ /^5[0-9]{2}$/ {count} END {print count0} $LOG_FILE) echo 4xx 客户端错误总计: $ERROR_4XX echo 5xx 服务器错误总计: $ERROR_5XX if [ $TOTAL_REQUESTS -ne 0 ]; then ERROR_4XX_RATIO$(echo scale4; $ERROR_4XX/$TOTAL_REQUESTS*100 | bc) ERROR_5XX_RATIO$(echo scale4; $ERROR_5XX/$TOTAL_REQUESTS*100 | bc) echo 4xx 错误率: ${ERROR_4XX_RATIO}% echo 5xx 错误率: ${ERROR_5XX_RATIO}% fi echo 注意事项uniq -c命令会在每行前面加上出现次数sort -rn是按数字逆序排序出现次数多的在前。head -20只显示前20种状态码通常足够了。这里用while read循环来格式化输出并计算百分比。注意在计算百分比时要判断分母总请求数是否为0否则bc命令会出错。4.3 热门请求与客户端分析模块实现找出“最忙”的接口和最“活跃”的客户端。# 模块3: 热门请求URL (TOP 20) echo 3. 热门请求 URL (TOP 20) echo ---------------------------------------------- # 提取第7列request中的URI部分如 /api/user。先按$7排序计数再按频率排序。 awk {print $7} $LOG_FILE | sort | uniq -c | sort -rn | head -20 | while read count url; do if [ $TOTAL_REQUESTS -ne 0 ]; then percentage$(echo scale2; $count*100/$TOTAL_REQUESTS | bc) printf %-50s: %-6d 次 (%-5s%% )\n $url $count $percentage fi done echo # 模块4: 活跃客户端IP (TOP 20) echo 4. 活跃客户端 IP (TOP 20) echo ---------------------------------------------- awk {print $1} $LOG_FILE | sort | uniq -c | sort -rn | head -20 | while read count ip; do if [ $TOTAL_REQUESTS -ne 0 ]; then percentage$(echo scale2; $count*100/$TOTAL_REQUESTS | bc) # 可以尝试解析IP归属地需要额外工具或API这里仅显示IP printf %-20s: %-6d 次 (%-5s%% )\n $ip $count $percentage fi done echo 4.4 用户代理分析模块实现用户代理字符串能告诉我们访问者是谁。# 模块5: 用户代理分析 (TOP 15) echo 5. 主要用户代理 (TOP 15) echo ---------------------------------------------- # 提取最后一列User-Agent进行粗略归类 awk -F {print $6} $LOG_FILE | sort | uniq -c | sort -rn | head -15 | while read count agent; do # 简化过长的UA字符串 SHORT_AGENT$(echo $agent | cut -c1-80) printf %-80s: %d 次\n $SHORT_AGENT $count done echo 至此一个基础功能的纯 Shell 分析脚本就完成了。给它加上执行权限chmod x nginx_log_analyzer.sh然后运行./nginx_log_analyzer.sh /path/to/your/access.log试试看吧。对于几十兆的日志文件它能在几秒内给出结果。5. 进阶增强版脚本与性能优化基础版脚本虽然能用但还有不少可以增强的地方。比如处理压缩日志.gz、分析自定义日志字段如响应时间、生成更美观的报告HTML、定时任务与邮件发送等。我们一步步来。5.1 支持压缩日志与多文件输入生产环境的日志通常会按天切割并压缩例如access.log-20231015.gz。我们可以让脚本自动识别并处理。修改脚本开头使其能接受通配符或文件列表#!/bin/bash # 增强版支持处理多个日志文件包括.gz压缩格式 LOG_FILES(${:-/var/log/nginx/access.log}) # 支持传入多个文件如 ./script.sh access.log*.gz TMP_COMBINED_LOG/tmp/nginx_analysis_combined.log.$$ # 使用进程ID创建临时文件避免冲突 # 清空或创建临时文件 $TMP_COMBINED_LOG echo 正在处理日志文件... for file in ${LOG_FILES[]}; do if [ ! -e $file ]; then echo 警告文件不存在跳过 - $file continue fi echo - 处理: $file # 根据文件后缀决定解压方式 case $file in *.gz) zcat $file $TMP_COMBINED_LOG 2/dev/null || gzip -dc $file $TMP_COMBINED_LOG ;; *.bz2) bzcat $file $TMP_COMBINED_LOG 2/dev/null || bzip2 -dc $file $TMP_COMBINED_LOG ;; *.xz) xzcat $file $TMP_COMBINED_LOG 2/dev/null || xz -dc $file $TMP_COMBINED_LOG ;; *) cat $file $TMP_COMBINED_LOG ;; esac done # 检查临时文件是否有内容 if [ ! -s $TMP_COMBINED_LOG ]; then echo 错误未找到有效的日志数据。 rm -f $TMP_COMBINED_LOG exit 1 fi # 后续的分析都基于这个临时合并文件 ANALYSIS_FILE$TMP_COMBINED_LOG在脚本最后记得清理临时文件# 脚本结尾处添加清理代码 rm -f $TMP_COMBINED_LOG echo 分析完成。临时文件已清理。踩坑记录使用zcat,bzcat,xzcat这些命令时要确保系统已经安装了对应的工具gzip,bzip2,xz-utils。在 openEuler 上可以用dnf install gzip bzip2 xz -y来安装。另外临时文件命名加上进程ID ($$) 是个好习惯可以防止多个脚本实例同时运行时文件冲突。5.2 集成响应时间分析如果日志包含如果你的 Nginx 配置了$request_time或$upstream_response_time到日志中这个数据就太宝贵了。假设你的log_format在combined基础上增加了$request_time作为第11列。首先修改log_format并重载 Nginxlog_format detailed $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $request_time;然后在脚本中添加分析模块# 模块6: 请求耗时分析 (需要日志包含$request_time字段假设在第11列) echo 6. 请求耗时分析 (单位: 秒) echo ---------------------------------------------- # 检查日志是否包含足够多的列至少11列 SAMPLE_LINE$(head -1 $ANALYSIS_FILE) COLUMN_COUNT$(echo $SAMPLE_LINE | awk {print NF}) if [ $COLUMN_COUNT -ge 11 ]; then # 计算平均响应时间 AVG_TIME$(awk {sum$11} END {if(NR0) print sum/NR; else print 0} $ANALYSIS_FILE) printf 平均响应时间: %.3f 秒\n $AVG_TIME # 统计耗时区间的请求数量 echo 响应时间分布: awk {rt$11; if(rt0.1) a; else if(rt0.5) b; else if(rt1.0) c; else if(rt3.0) d; else e;} END { totalNR; printf 0.1s : %d (%.1f%%)\n, a, (a/total)*100; printf 0.1-0.5s: %d (%.1f%%)\n, b, (b/total)*100; printf 0.5-1s : %d (%.1f%%)\n, c, (c/total)*100; printf 1-3s : %d (%.1f%%)\n, d, (d/total)*100; printf 3s : %d (%.1f%%)\n, e, (e/total)*100; } $ANALYSIS_FILE # 找出最慢的10个请求 echo echo 最慢的请求 (TOP 10): # 打印耗时、IP、方法、URL、状态码 awk {printf %.3f %s %s %s %s\n, $11, $1, $6, $7, $9} $ANALYSIS_FILE | sort -rn | head -10 | while read time ip method url status; do # 清理method中的引号 method$(echo $method | tr -d ) printf %-8s %-15s %-6s %-40s %s\n ${time}s $ip $method $url $status done else echo 当前日志格式未包含响应时间字段(request_time)。 echo 建议在nginx.conf的log_format中添加 \$request_time 字段以启用此分析。 fi echo 这个模块能让你一眼看出服务的性能瓶颈在哪里哪些接口最慢慢请求来自哪些IP。5.3 生成 HTML 报告与邮件发送纯文本报告在终端看还行但要是想每天定时发邮件给团队HTML 格式就友好多了。我们可以用 Shell 生成一个简单的 HTML 页面或者更优雅一点用 Python 来生成。这里提供一个混合方案用 Shell 收集数据然后用 Python 的jinja2模板或直接字符串拼接生成 HTML。为了简化我们先用 Shell 生成一个简单的 HTML 片段。首先在脚本中将每个模块的输出不仅打印到屏幕也重定向到一个变量或文件。这里我们选择将关键数据存储到变量中最后统一渲染。但更实用的方法是将 Shell 脚本作为数据收集器然后调用一个 Python 脚本来生成报告和发邮件。我们可以在 Shell 脚本末尾添加# 假设我们将关键数据以JSON格式输出到一个临时文件 TMP_DATA_JSON/tmp/nginx_analysis_data.json.$$ cat $TMP_DATA_JSON EOF { total_requests: $TOTAL_REQUESTS, unique_ips: $UNIQUE_IPS, total_traffic_bytes: $TOTAL_BYTES, error_4xx: $ERROR_4XX, error_5xx: $ERROR_5XX, error_4xx_ratio: $ERROR_4XX_RATIO, error_5xx_ratio: $ERROR_5XX_RATIO, top_ips: [ $(awk {print $1} $ANALYSIS_FILE | sort | uniq -c | sort -rn | head -5 | awk {printf {\ip\: \%s\, \count\: %s},\n, $2, $1} | sed $s/,$//) ], top_urls: [ $(awk {print $7} $ANALYSIS_FILE | sort | uniq -c | sort -rn | head -5 | awk {printf {\url\: \%s\, \count\: %s},\n, $2, $1} | sed $s/,$//) ] } EOF # 然后调用 Python 脚本处理这个 JSON 并生成报告 PYTHON_REPORT_SCRIPT/path/to/your/generate_report.py if [ -f $PYTHON_REPORT_SCRIPT ] command -v python3 /dev/null; then python3 $PYTHON_REPORT_SCRIPT --input-json $TMP_DATA_JSON --log-file $LOG_FILE --output-html /tmp/report_$(date %Y%m%d).html # 可以在这里添加发送邮件的命令例如使用 mailx 或 python 的 smtplib # mailx -s Nginx日志日报 $(date %Y-%m-%d) -a /tmp/report_$(date %Y%m%d).html teamexample.com /dev/null else echo Python 报告脚本未找到或 Python3 未安装跳过 HTML 报告生成。 fi # 清理临时数据文件 rm -f $TMP_DATA_JSON而generate_report.py可以是一个简单的 Python 脚本使用json模块加载数据用字符串模板或jinja2生成漂亮的 HTML。这里限于篇幅不展开完整的 Python 代码但思路是清晰的数据由高效的 Shell 脚本预处理展示层由更灵活的 Python 负责。5.4 性能优化技巧当单日日志文件达到 GB 级别时脚本的性能就很重要了。避免多次读取文件基础脚本中每个awk或管道命令都会重新读取一遍日志文件。对于大文件这是巨大的 IO 开销。优化方法是将日志预处理到一个临时文件或者使用更复杂的awk脚本在一次读取中完成所有统计。我们上面使用的合并临时文件法已经是一种优化确保后续分析只读一次这个临时文件。使用awk关联数组在awk中可以使用关联数组即字典在内存中完成计数和聚合这是最快的文本处理方式之一。例如统计状态码和URL可以合并到一个awk脚本里。处理压缩文件时zcat是流式读取不会完全解压到磁盘比先gzip -d再cat更高效。排序的代价sort和uniq -c对内存要求较高。如果数据量极大可以考虑使用LC_ALLC sort来加速设置区域为 C使用字节排序速度更快或者使用awk的数组排序功能部分替代。采样分析对于海量历史日志有时不需要100%的精确度。可以随机采样一部分行进行分析用shuf命令如果支持或awk NR % 100 0每100行取一行来减少数据量。一个高度优化的、单次awk遍历完成多项统计的脚本框架如下示意awk { # 提取字段 ip $1 status $9 url $7 bytes $10 # 假设是第10列 # 1. 统计总请求和流量 total_req total_bytes bytes # 2. 统计独立IP (近似完全精确需要存储所有IP内存可能不够) ip_count[ip] # 3. 统计状态码 status_count[status] # 4. 统计URL url_count[url] # ... 其他统计 } END { # 输出所有结果 print 总请求数:, total_req print 总流量:, total_bytes # 遍历数组输出TOP N # 注意awk中排序数组比较麻烦通常还是输出到外部用sort排序 for (s in status_count) { print s, status_count[s] /tmp/status_stats.txt } # ... 其他输出 } $LOG_FILE6. 部署与自动化定时任务与告警集成脚本写好了总不能每次都手动运行。我们需要把它集成到系统的自动化流程中。6.1 配置 Crontab 定时任务假设我们的完整脚本路径是/opt/scripts/nginx_log_analyzer.sh并且它支持处理压缩日志。编辑当前用户的 crontabcrontab -e添加以下行实现每天凌晨1点分析前一天的日志并发送邮件报告# 每天凌晨1点10分执行日志分析 10 1 * * * /opt/scripts/nginx_log_analyzer.sh /var/log/nginx/access.log-$(date -d yesterday \%Y\%m\%d).gz /var/log/nginx_analysis.log 21 # 如果日志是按天切割但未压缩去掉 .gz # 10 1 * * * /opt/scripts/nginx_log_analyzer.sh /var/log/nginx/access.log-$(date -d yesterday \%Y\%m\%d) /var/log/nginx_analysis.log 21注意crontab 中的%需要转义为\%。$(date -d yesterday \%Y\%m\%d)会生成昨天的日期如20231015。这要求你的 Nginx 日志切割命名规则与之匹配。常见的日志切割工具如logrotate配置的命名可能是access.log-YYYYMMDD。6.2 集成简单告警我们可以在脚本中增加判断逻辑当某些指标超过阈值时不仅输出报告还发送告警通知如邮件、钉钉、企业微信等。在 Shell 脚本的末尾添加如下告警判断# 告警阈值配置 ALERT_5XX_RATIO1 # 5xx错误率超过1%告警 ALERT_AVG_TIME2 # 平均响应时间超过2秒告警 (如果启用了耗时分析) # 判断并告警 ALERT_MESSAGE if [ $(echo $ERROR_5XX_RATIO $ALERT_5XX_RATIO | bc) -eq 1 ]; then ALERT_MESSAGE${ALERT_MESSAGE}⚠️ 5xx错误率过高: ${ERROR_5XX_RATIO}% (阈值: ${ALERT_5XX_RATIO}%)\n fi if [ -n $AVG_TIME ] [ $(echo $AVG_TIME $ALERT_AVG_TIME | bc) -eq 1 ]; then ALERT_MESSAGE${ALERT_MESSAGE}⚠️ 平均响应时间过长: ${AVG_TIME}秒 (阈值: ${ALERT_AVG_TIME}秒)\n fi if [ -n $ALERT_MESSAGE ]; then echo -e \n【告警】\n$ALERT_MESSAGE # 这里可以集成发送告警邮件的命令例如 # echo -e Subject: Nginx服务告警 - $(date)\n\n$ALERT_MESSAGE | sendmail -v adminexample.com # 或者调用发送钉钉/企业微信的脚本 # /opt/scripts/send_dingding_alert.sh Nginx服务异常 $ALERT_MESSAGE fi6.3 日志文件路径与权限问题在 openEuler 或其他 Linux 系统上Nginx 日志默认通常由root或nginx用户创建权限可能是640。如果你的脚本由普通用户比如通过 crontab执行可能会因为权限不足无法读取日志。解决方法有几种最佳实践将执行脚本的用户如nginx或一个专用的监控用户加入nginx或root组并调整日志文件的组权限为可读。sudo usermod -a -G nginx your_username sudo chmod 644 /var/log/nginx/access.log # 注意这可能会降低安全性需权衡 # 或者更精细地设置组权限 sudo chown root:nginx /var/log/nginx/access.log sudo chmod 640 /var/log/nginx/access.log然后执行脚本的用户需要重新登录以使组生效。使用 sudo在 crontab 中通过sudo以 root 权限运行脚本。但这需要配置sudoers文件赋予相应用户无需密码运行该脚本的权限存在安全风险需谨慎。修改日志目录权限不推荐会降低系统安全性。7. 常见问题排查与实战技巧在实际操作中你可能会遇到下面这些问题。7.1 脚本执行报错字段错乱或空白症状统计出的 URL 全是乱码或者 IP 地址跑到状态码那一列去了。原因日志格式与脚本中awk的列索引不匹配。combined格式是固定的但如果你自定义了log_format列的顺序就变了。排查查看一条真实的日志样本head -1 /var/log/nginx/access.log数一下每个字段的位置。注意$request如GET /foo HTTP/1.1是一个整体算一列即使里面包含空格。调整脚本中awk的列号。例如如果$request_time加在了最后它可能是第11列如果加在了中间后面的列号都要顺延。7.2 处理海量日志时脚本卡死或内存不足症状脚本运行很久不出结果或者直接被系统 kill 掉。原因sort和uniq在处理超大文件时需要大量内存和临时磁盘空间。优化使用LC_ALLC在sort前加上LC_ALLC可以大幅提升排序速度因为它使用简单的字节排序而非复杂的本地化规则。LC_ALLC sort ...指定临时目录如果/tmp空间小可以用-T参数指定到大容量的分区sort -T /bigdisk/tmp ...分而治之如果日志是按小时或按天切割的分别分析每个小文件然后再汇总结果。采样分析如前所述使用awk NR % 100 0进行采样。换用更高效的工具对于 TB 级别的日志考虑使用Go或Rust写一个专门的分析程序或者直接上ELK、ClickHouse这类专业方案。7.3 分析结果中 URL 带有查询参数导致统计分散症状/api/user?id1和/api/user?id2被算作两个不同的 URL无法聚合。解决在统计前用awk或sed清洗掉查询字符串。例如只取$7URI中?之前的部分# 在提取URL的awk命令中处理 awk {split($7, a, ?); urla[1]; print url} $LOG_FILE | sort | uniq -c | sort -rnsplit($7, a, ?)将第7列以?为分隔符拆分成数组aa[1]就是问号前的路径部分。7.4 如何分析 POST 请求的请求体大小Nginx 的$body_bytes_sent记录的是发送给客户端的响应体大小不是客户端发来的请求体大小。Nginx 默认的访问日志不记录客户端请求体大小。如果你需要记录可以在log_format中添加$request_length变量它表示客户端请求的总长度包括请求行、头部和请求体。但请注意这可能会略微增加日志体积和性能开销。7.5 在 openEuler 上邮件发送失败如果你使用mailx通过外部 SMTP 服务器发信可能需要配置/etc/mail.rc或~/.mailrc。一个简单的配置示例使用 Gmail 的 SMTP请注意安全风险不建议在生产环境用密码set smtpsmtp.gmail.com:587 set smtp-use-starttlsyes set ssl-verifyignore set nss-config-dir/etc/pki/nssdb/ set smtp-authlogin set smtp-auth-useryour-emailgmail.com set smtp-auth-passwordyour-app-password # 注意不要用真实密码用应用专用密码 set fromYour Name your-emailgmail.com在 openEuler 上可能需要安装mailx的s-nail变体并确保其支持 SMTP。更可靠的方式是使用 Python 的smtplib库来发送邮件灵活性更高。8. 扩展思路从脚本到简易日志分析平台这个脚本是一个很好的起点。随着需求增长你可以考虑以下扩展方向逐步构建一个轻量级的内部日志分析平台数据持久化将脚本每日的分析结果如总请求数、错误率、TOP IP等写入一个简单的数据库如 SQLite或文本文件CSV。这样你就可以绘制趋势图观察指标随时间的变化。可视化用 Python 的matplotlib或plotly库读取持久化的数据生成每日/每周的趋势图表并嵌入到 HTML 报告中。实时监控使用tail -f命令实时读取 Nginx 日志并通过管道传递给一个简单的实时分析脚本可以用awk或Python实时计算 QPS、错误率并在控制台动态刷新或推送至监控面板。集成更多日志源将脚本模块化使其不仅能分析 Nginx 访问日志还能分析错误日志error.log、应用日志、数据库慢查询日志等。为每种日志定义不同的解析规则和分析模板。自动化根因分析RCA当脚本检测到错误率飙升时可以自动关联分析同一时间段内的错误日志、系统监控指标如 CPU、内存甚至调用一些诊断命令尝试给出可能的原因提示。这需要更复杂的逻辑但可以极大提升排障效率。最终这个始于简单 Shell 脚本的项目可以根据你的实际需求演变成一套高度定制化、成本可控的日志监控解决方案。它可能没有商业 APM 工具那么功能全面但它完全贴合你的业务没有冗余功能而且所有数据都在自己掌控之中。