nginx是常见的web、缓存、代理等服务,以功能强大、服务稳定、占用资源少等优点著称。然而实际使用中,大家对nginx有一种误解:认为它很简单,以至于很少会有人系统的去学习它。

    本篇博文主要讲解nginx日志分析,日志的格式可以经过自定义以适用自己的需求。nginx日志主要分为访问日志(access)、错误日志等(error),当nginx作为反向代理,前面使用cdn时,默认的nginx日志格式无法获取到用户端IP,需要修改。下面是我修改的nginx日志格式,请参考:

    log_format main 'WebAcessLogInformation dateTime="[$time_local]" '
                    'xForwardedFor="$http_x_forwarded_for" sourceAddress=$remote_addr '
                    'dstAddress=$server_addr dstHostName="$server_name" dstPort="$server_port" '
                    'userAgent="$http_user_agent" bytesOutInfo=$bytes_sent sourceUserName="$remote_user" '
                    'responseCode=$status httpRerfer="$http_referer" logSessionNum="-" '
                    'responseTimems=$request_time responseTimes=- requestRInfo="$request" ';

    说明:“=”前面的字符串是个人定义的常量名,可以为任意名称,如:“WebAcessLogInformation”可以写成“nginx-log”等字样,之所以这样写,是为了更方便用awk、cut等工具对日志截取。

    访问日志分析,常见的需求有:1.统计pv(当天访问nginx的次数,即:access日志的行数);2.最活跃的前n个IP;3.访问最频繁的前n个url;4.各网页状态码数目;5.最活跃的前n个IP的每个IP访问的最频繁的前n个URL;6.访问最频繁的前n个url的每个url最活跃的前n个IP(有点拗口,哈哈);7.以上各参数统计结果占比......可能对有些同学来说,乍一看这些需求无从下手的样子,实际上很简单——就是一些linux命令的组合——就是awk、sort、uniq、cut、head等命令的组合。

     运维这件事儿,很简单~ 难得是没有需求,目标不明确,就会感觉无从下手。很多时候,脚本可以帮助我们处理大部分事情,脚本写的好与坏,关系到使用者能否准确定位到问题,所以请对脚本加上足够的备注及说明。

     下面是我贡献的一份脚本,在使用之前,请将nginx日志改成上面格式,脚本使用方式:sh nginx_access_analyze.sh { simple | detail | help }。说明:“simple”选项是简单分析(简单输出)的意思;“detail”选项是详细分析(详细输出)的意思,“help”选项是帮助文档,包括脚本使用说明、日志格式。

#!/bin/bash
# func: analyze nginx access log.
# version: v1.2
# auth: 任小为 
# tel: 18658160015
# date: 2018.07.11

public(){
    echo ""
    read -p "请输入要分析的访问日志: " log_file
    echo ""
    
    if [ ! -f $log_file ];then
        echo -e "未找到: ${log_file} \n"
        exit 1
    fi
    
    if [ ! -s $log_file ];then
        echo -e "${log_file}是空文件 \n"
        exit 1
    fi
    
    top_num=10

    input_file=`echo $log_file | awk -F '/' '{print $(NF)}'`
    analyze_dir=/tmp/nginx_log_analyze
    top_ip_file=$analyze_dir/ngx_log_top_ip_${input_file}.txt
    top_src_url_file=$analyze_dir/ngx_log_top_src_url_${input_file}.txt
    top_dest_url_file=$analyze_dir/ngx_log_top_dest_url_${input_file}.txt
    top_code_file=$analyze_dir/ngx_log_top_code_${input_file}.txt
    
    mkdir -p $analyze_dir
    
    start_time=`head -1 $log_file | awk '{print $2}'|cut -d "[" -f2`
    end_time=`tail -1 $log_file | awk '{print $2}'|cut -d "[" -f2`
    total_nums=`wc -l $log_file | awk '{print $1}'`
    size=`du -sh $log_file | awk '{print $1}'`
    
    #获取起始与截止时间
    echo -e "访问起始时间: $start_time ; 截止时间: $end_time \n"
    
    #获取总行数与大小
    echo -e "共访问 $total_nums 次 ; 日志大小: $size \n"

    #获取最活跃IP
    awk -F "sourceAddress=" '{print $2}' $log_file | awk '{print $1}' | sort | uniq -c | sort -rn | head -${top_num} > $top_ip_file

    #获取访问来源最多的url
    awk -F "httpRerfer=" '{print $2}' $log_file | awk -F '"' '{print $2}' | sort | uniq -c | sort -rn | head -${top_num} > $top_src_url_file

    #获取请求最多的url
    awk -F 'requestRInfo=' '{print $2}' $log_file | awk '{print $2}' | sort | uniq -c | sort -rn | head -${top_num} > $top_dest_url_file

    #获取返回最多的状态码
    awk -F " responseCode=" '{print $2}' $log_file | cut -d " " -f1 | sort | uniq -c | sort -rn | head -${top_num} > $top_code_file
}

simple(){
    echo -e "\033[44;36m+-+-+-+-+-+- 下面是粗略分析 +-+-+-+-+-+-\033[0m \n"
    
    #获取最活跃IP
    printf "\033[44;36m最活跃的前${top_num}个访问IP: \033[0m \n"
    cat $top_ip_file
    echo ""
    
    #获取访问来源最多的url
    printf "\033[44;36m访问来源最多的前${top_num}个url: \033[0m \n"
    cat $top_src_url_file
    echo ""
    
    #获取请求最多的url
    printf "\033[44;36m请求最多的前${top_num}个url: \033[0m \n"
    cat $top_dest_url_file
    echo ""
    
    #获取返回最多的状态码
    printf "\033[44;36m返回最多的前${top_num}个状态码: \033[0m \n"
    cat $top_code_file
    echo ""
}

detail(){
    echo -e "\033[44;36m+-+-+-+-+-+- 下面是详细分析 +-+-+-+-+-+-\033[0m \n"
    
    printf "\033[44;36m最活跃的前${top_num}个访问IP详情: \033[0m \n"
    ip_nums=`wc -l $top_ip_file | awk '{print $1}'`
    for ((i=1; i<=$ip_nums; i++))
    do
        ip_num=`head -$i $top_ip_file | tail -1 | awk '{print $1}'`
        ip_addr=`head -$i $top_ip_file | tail -1 | awk '{print $2}'`
        ip_percent=`awk 'BEGIN { printf "%.1f%",('$ip_num'/'$total_nums')*100 }'`
        #分别计算访问最多的url所占百分比
        echo ""
        echo -e "共有来自 ${ip_addr} 的 ${ip_num} 条访问,占比:\033[31;49;1m ${ip_percent} \033[31;49;0m \n"
        #分别计算每个访问最多的IP的最多来源url
        echo -e "\033[32;49;1m+=+=+=+= ${ip_addr} \033[31;49;0m访问来源最多的前${top_num}个url: "
        grep ${ip_addr} $log_file | awk -F "httpRerfer=" '{print $2}' | awk -F '"' '{print $2}' | sort | uniq -c | sort -rn | head -${top_num}
        echo ""
        #分别计算每个请求最多的IP的最多来源url
        echo -e "\033[32;49;1m#+#+#+#+\033[31;49;0m${ip_addr}请求最多的前${top_num}个url: "
        grep ${ip_addr} $log_file | awk -F 'requestRInfo=' '{print $2}' | awk '{print $2}' | sort | uniq -c | sort -rn | head -${top_num}
    done
    
    echo ""
    printf "\033[44;36m访问来源最多的前${top_num}个url详情: \033[0m \n"
    url_src_nums=`wc -l $top_src_url_file | awk '{print $1}'`
    for ((k=1; k<=$url_src_nums; k++))
    do
        url_src_num=`head -$k $top_src_url_file | tail -1 | awk '{print $1}'`
        url_src_addr=`head -$k $top_src_url_file | tail -1 | awk '{print $2}'`
        url_src_percent=`awk 'BEGIN { printf "%.1f%",('$url_src_num'/'$total_nums')*100 }'`
        #分别计算访问来源最多的url所占百分比
        echo ""
        echo -e "共有 ${url_src_num} 条 ${url_src_addr} 的访问,占比:\033[31;49;1m ${url_src_percent} \033[31;49;0m \n"
        #分别计算每个访问来源最多的url的最多ip
        echo -e "\033[32;49;1m#-#-#-#-\033[31;49;0m${url_src_addr}访问最多的前${top_num}个ip: "
        grep "${url_src_addr}" $log_file | awk -F 'sourceAddress=' '{print $2}' | awk '{print $1}' |sort | uniq -c | sort -rn | head -${top_num}
    done
    
    echo ""
    printf "\033[44;36m请求最多的前${top_num}个url详情: \033[0m \n"
    url_dest_nums=`wc -l $top_dest_url_file | awk '{print $1}'`
    for ((j=1; j<=$url_dest_nums; j++))
    do
        url_dest_num=`head -$j $top_dest_url_file | tail -1 | awk '{print $1}'`
        url_dest_addr=`head -$j $top_dest_url_file | tail -1 | awk '{print $2}'`
        url_dest_percent=`awk 'BEGIN { printf "%.1f%",('$url_dest_num'/'$total_nums')*100 }'`
        #分别计算访问请求最多的url所占百分比
        echo ""
        echo -e "共有 ${url_dest_num} 条 ${url_dest_addr} 请求的访问,占比:\033[31;49;1m ${url_dest_percent} \033[31;49;0m \n"
        #分别计算每个访问请求最多的url的最多ip
        echo -e "\033[32;49;1m#.#.#.#.\033[31;49;0m${url_dest_addr}访问最多的前${top_num}个ip: "
        grep "${url_dest_addr}" $log_file | awk -F 'sourceAddress=' '{print $2}' | awk '{print $1}' |sort | uniq -c | sort -rn | head -${top_num}
    done
    
    echo ""
    printf "\033[44;36m返回最多的前${top_num}个状态码详情: \033[0m \n"
    code_nums=`wc -l $top_code_file | awk '{print $1}'`
    for ((h=1; h<=$code_nums; h++))
    do
        code_num=`head -$h $top_code_file | tail -1 | awk '{print $1}'`
        code_name=`head -$h $top_code_file | tail -1 | awk '{print $2}'`
        code_percent=`awk 'BEGIN { printf "%.1f%",('$code_num'/'$total_nums')*100 }'`
        #分别计算请求最多的状态码百分比
        echo ""
        echo -e "状态码为 ${code_name} 的共有 ${code_num} 条,占比:\033[31;49;1m ${code_percent} \033[31;49;0m \n"
        #分别计算每个最多状态码的最多ip
        echo -e "\033[32;49;1m*.*.*.*.\033[31;49;0m状态码为${code_name}访问最多的前${top_num}个ip: "
        grep "responseCode=${code_name}" $log_file | awk -F "sourceAddress=" '{print $2}' | awk '{print $1}' | sort | uniq -c | sort -rn | head -${top_num}
    done
    echo ""
}

log_format(){
cat << EOF
log_format main 'WebAcessLogInformation dateTime="[\$time_local]" '
                'xForwardedFor="\$http_x_forwarded_for" sourceAddress=\$remote_addr '
                'dstAddress=\$server_addr dstHostName="\$server_name" dstPort="\$server_port" '
                'userAgent="\$http_user_agent" bytesOutInfo=\$bytes_sent sourceUserName="\$remote_user" '
                'responseCode=\$status httpRerfer="\$http_referer" logSessionNum="-" '
                'responseTimems=\$request_time responseTimes=- requestRInfo="\$request" ';
EOF
echo ""
}

case $1 in
    simple)
        public
        simple
        ;;

    detail)
        public
        detail
        ;;

    help)
        echo ""
        echo -e "\033[44;36m1. 脚本使用方法: sh $0 { simple | detail | help }\033[0m"
        echo -e "\033[44;36m   simple选项代表错略分析; detail代表详细分析.\033[0m \n"
        echo -e "\033[44;36m2. 请确保日志必须为如下格式: \033[0m \n"
        log_format
        ;;

    *)
        echo ""
        echo -e $"Usage: $0 { simple | detail | help }\n"
esac
exit 0

    几点建议:

          1.请将脚本命名成一个有意义的名字,如:nginx_access_analyze.sh ;

          2.写脚本时,合理利用缩进、空行、注释、备注等,便于后期维护及他人阅读;

          3.脚本的输出结果,合理利用“echo " "”或“print " "”空行,输出到终端的颜色(如正确的用绿色或不用颜色,错误用红色标记等)利于脚本输出结果的排版,便于阅读;

          4.脚本尽量用“模块”话“拼凑”,这里的“模块”即bash函数,每个函数尽量简单,最好只做一件事,如此下来,通篇脚本也会尽量简单,也会只做“一件事”,不建议写的太花哨;

          5.脚本的变量尽量放在文件开头,尤其是容易改动的变量要放在前排;脚本主体或者函数尽量不出现常量(如:引用某个文件的路径),尽量用变量替代常量(如:用${file_log}代替某个日志文件),这样做利于他人修改。


      光说不练,不如回家喝稀饭!下面是我执行脚本的截图,请大家参考:

nginx日志分析_第1张图片

nginx日志分析_第2张图片

nginx日志分析_第3张图片

nginx日志分析_第4张图片

如果有更好观点的小伙伴欢迎留言讨论,感谢您的阅览,授人以鱼不如授人以渔!