BeWithYou

胡搞的技术博客

  1. 首页
  2. 运维/工具
  3. awk扫描分析nginx访问日志

awk扫描分析nginx访问日志


年底感觉没啥活干,尤其是在年底跳槽到新公司,更是没什么重要的项目可以做。老大布置任务,要做一个平台统计每个站点后台的nginx日志,针对url维度分析访问量,加载时间等。

当时设想是这样的,扫描任务部署在线上nginx机器上,定时执行。用shell脚本分析日志,然后找一台开发虚拟机部署管理后台,从shell脚本curl给php接口上报统计结果。
设想的挺好,但是等shell脚本写完以后放到nginx机器上一跑就吓尿了。一个500M的日志文件,用awk分析完需要1分多钟。期间用top看了下,直接CPU100%,占满了一个CPU的核。
后来同事用php写了一个相同逻辑的脚本,结果出人意料——shell要跑1分多钟的任务,php只需要10几秒。我至今不知道是什么原因,印象中用C写成的awk肯定速度要比php快啊?

还是先记录一下awk的扫描脚本主要思路。

## 省略前面获取日志文件路径,格式化时间等步骤

data=`cat $log_file | awk -v hour=$hour -v day=$day -v urls=$urls -v start_time=$start_time -v end_time=$end_time -v interval=$interval '
    BEGIN {
        split(urls,url_arr,"|");
        arr_name=(day" "start_time); ##2016-01-18 16:00:00;
        tmp_name=arr_name;
        gsub(/:| /," ",tmp_name);
        next_gap=strftime("%H:%M:%S",mktime(tmp_name)+interval); ##16:10:00
    }

    {
        if(match($7,urls)){
            ##超过一个时间段
            row_time=substr($4,index($4,":")+1,8);
            if(row_time>=next_gap){
                if(row>end_time){
                    exit; ##直接跳到END
                }
                arr_name=(day" "next_gap);
                tmp_name=arr_name;
                gsub(/:| /," ",tmp_name);
                next_gap=strftime("%H:%M:%S",mktime(tmp_name)+interval); ##16:20:00
            }
            for(i in url_arr){
                if(match($7,url_arr[i])){
                    sta[arr_name,url_arr[i],"visit"] ++;
                    sta[arr_name,url_arr[i],"total_time"] += $11; ##先计算总时间
                    if(match($9,/^"4[0-9][0-9]"$|^"5[0-9][0-9]"$/)){
                        sta[arr_name,url_arr[i],"badcode"] ++;
                    }
                }
            }
        }
    }

    END {
        for(i in sta){ ##i=day+url+visit day+url+time
            split(i,key,SUBSEP); ##key[1]=day key[2]=url key[1]=visit
            for(k in key){
                if(key[3] == "total_time"){
                    printf("%s|%s %s %f\n",key[1],key[2],key[3],sta[i]);
                }else{
                    printf("%s|%s %s %d\n",key[1],key[2],key[3],sta[i]);
                }
            }
        }
    }
'`

## 省略后面curl步骤

首先是-v选项把shell里的参数赋值给awk变量。多个变量需要多次添加-v选项。然后BEGIN里做初始化。每次第一行的主循环里根据实际条件做统计。需要注意的是awk虽然支持关联数组,但是却不支持多维关联数组

变相使其支持多维数组的常用的方法是将,放在key中间。这样遍历的时候再使用split(i,key,SUBSEP);方法,将逗号两遍的东西分隔开来。

比如PHP里的$arr["row"]["col"]=1,在awk里的存储方式实际上是arr["row,col"]=1,使用for(i in arr)找到的i实际上是诸如"row,col"这种逗号隔开的字符串的key。这样如果我们想要表示第row行,第col列,值是1,则应该把"row,col"这个key用split(key,str,SUBSEP);处理掉,之后输出第str[1]行,第str[2]列,值是arr[key]
说的很混乱。不过以后也不一定用的着。

还有,如果BEGIN和主循环里执行了exit;awk并不会立即退出,而是跳到END里去执行。

回到顶部