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}代替某個日志文件),這樣做利於他人修改。
光說不練,不如回家喝稀飯!下面是我執行腳本的截圖,請大家參考:
如果有更好觀點的小伙伴歡迎留言討論,感謝您的閱覽,授人以魚不如授人以漁!