你有任何有用的awk和grep脚本parsingapache日志?

我可以使用日志分析器,但是我经常需要parsing最近的Web日志来看看目前发生了什么。

我有时会做一些事情,比如找出要求某个文件的前10位

cat foo.log | grep request_to_file_foo | awk '{print $1}' | sort -n | uniq -c | sort -rn | head 

你在工具箱里有什么?

    你可以用awk单独完成apache日志文件的任何操作。 Apache日志文件基本上是空白分隔的,你可以假装引号不存在,并按列号访问你感兴趣的任何信息。 唯一的一次是,如果你有组合的日志格式,并对用户代理感兴趣,那么你必须使用引号(“)作为分隔符并运行一个单独的awk命令。下面将显示IP每个请求索引页面的用户按访问次数sorting:

     awk -F'[ "]+' '$7 == "/" { ipcount[$1]++ } END { for (i in ipcount) { printf "%15s - %d\n", i, ipcount[i] } }' logfile.log 

    $ 7是请求的url。 你可以在开始时添加你想要的任何条件。 用你想要的任何信息replace“$ 7 ==”/“”。

    如果您replace(ipcount [$ 1] ++)中的$ 1,则可以按照其他条件对结果进行分组。 使用$ 7将显示访问的页面和频率。 当然,你会想在开始的时候改变状态。 以下内容将显示用户从特定IP访问的页面:

     awk -F'[ "]+' '$1 == "1.2.3.4" { pagecount[$7]++ } END { for (i in pagecount) { printf "%15s - %d\n", i, pagecount[i] } }' logfile.log 

    您还可以通过sorting来pipe道输出,以便按顺序获得结果,既可以作为shell命令的一部分,也可以作为awk脚本本身:

     awk -F'[ "]+' '$7 == "/" { ipcount[$1]++ } END { for (i in ipcount) { printf "%15s - %d\n", i, ipcount[i] | sort } }' logfile.log 

    如果您决定扩展awk脚本以打印出其他信息,后者将很有用。 这一切都是你想知道的。 无论你感兴趣的是什么,这都应该成为一个起点。

    有一件事我从来没有见过其他人做,因为我无法想象的原因是将Apache日志文件格式更改为一个更容易parsing的版本,其中包含的信息对您来说确实很重要。

    例如,我们从来不使用HTTP基本authentication,所以我们不需要logging这些字段。 我感兴趣的是每个请求需要多长时间才能提供服务,所以我们将其添加进来。对于一个项目,我们也想知道(在我们的负载均衡器上)是否有服务器比其他服务器提供的请求慢,所以我们logging名称我们正在代理的服务器。

    以下是一个服务器的apacheconfiguration摘录:

     # We don't want to log bots, they're our friends BrowserMatch Pingdom.com robot # Custom log format, for testing # # date proto ipaddr status time req referer user-agent LogFormat "%{%F %T}t %p %a %>s %D %r %{Referer}i %{User-agent}i" standard CustomLog /var/log/apache2/access.log standard env=!robot 

    你无法真正了解的是,每个字段之间是一个字面制表符(\ t)。 这意味着如果我想在Python中做一些分析,例如可能显示非200状态,我可以这样做:

     for line in file("access.log"): line = line.split("\t") if line[3] != "200": print line 

    或者,如果我想做“谁是盗链图片? 这将是

     if line[6] in ("","-") and "/images" in line[5]: 

    对于访问日志中的IP计数,前面的示例:

     grep -o "[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" logfile | sort -n | uniq -c | sort -n 

    变成这样的东西:

     cut -f 3 log | uniq -c | sort -n 

    更容易阅读和理解,并且在9 GB的日志上计算花费less得多(没有正则expression式),在多长时间内会产生巨大的差异。 如果你想为User-Agents做同样的事情,这真的很简单。 如果您的日志是空格分隔的,则必须手工进行一些正则expression式匹配或stringsearch。 采用这种格式,很简单:

     cut -f 8 log | uniq -c | sort -n 

    与上面完全一样。 事实上,你想要做的任何总结基本上是完全一样的。

    为什么我会花费我的系统的CPU在awk和grep时,剪切将完成我想要的数量级更快?

    忘了awk和grep。 看看asql 。 为什么编写不可读的脚本时,可以使用sql语法来查询日志文件。 例如。

     asql v0.6 - type 'help' for help. asql> load /home/skx/hg/engaging/logs/access.log Loading: /home/skx/hg/engaging/logs/access.log sasql> select COUNT(id) FROM logs 46 asql> alias hits SELECT COUNT(id) FROM logs ALIAS hits SELECT COUNT(id) FROM logs asql> alias ips SELECT DISTINCT(source) FROM logs; ALIAS ips SELECT DISTINCT(source) FROM logs; asql> hits 46 asql> alias ALIAS hits SELECT COUNT(id) FROM logs ALIAS ips SELECT DISTINCT(source) FROM logs; 

    这是一个脚本,可以从最近的N个日志条目中find顶级URL,顶级引用链接和顶级使用者

     #!/bin/bash # Usage # ls-httpd type count # Eg: # ls-httpd url 1000 # will find top URLs in the last 1000 access log entries # ls-httpd ip 1000 # will find top IPs in the last 1000 access log entries # ls-httpd agent 1000 # will find top user agents in the last 1000 access log entries type=$1 length=$2 if [ "$3" == "" ]; then log_file="/var/log/httpd/example.com-access_log" else log_file="$3" fi if [ "$type" = "ip" ]; then tail -n $length $log_file | grep -o "[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" | sort -n | uniq -c | sort -n elif [ "$type" = "agent" ]; then tail -n $length $log_file | awk -F\" '{print $6}'| sort -n | uniq -c | sort -n elif [ "$type" = "url" ]; then tail -n $length $log_file | awk -F\" '{print $2}'| sort -n | uniq -c | sort -n fi 

    资源

    对于访问日志中的IP计数:

     cat log | grep -o "[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" | sort -n | uniq -c | sort -n 

    这有点难看,但是有效。 我也使用下面的netstat(查看活动连接):

     netstat -an | awk '{print $5}' | grep -o "[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" | egrep -v "(`for i in \`ip addr | grep inet |grep eth0 | cut -d/ -f1 | awk '{print $2}'\`;do echo -n "$i|"| sed 's/\./\\\./g;';done`127\.|0\.0\.0)" | sort -n | uniq -c | sort -n 

    他们是我最喜欢的“一条线”:)

    build立一个常见的问题列表将是这个问题的答案的一个很好的索引。 我常见的问题是:

    • 为什么命中率有变化?
    • 为什么总体响应时间会上升?

    我通过监视服务器状态页面(通过mod_status)获得命中率以及近似完成请求的近似响应时间(完全知道我错过了一大堆数据,但是样本足够好),我注意到了这些变化。

    我使用下面的LogFormat指令(%T是非常有用的)

     LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %T" custom 

    我正在寻找因果关系,首先发生了什么…通常关于日志中特定的模式子集,所以我需要知道任何给定模式/正则expression式的以下内容:

    • 每个时间间隔(分钟或小时)对于一个给定的模式(IP地址或CGIstring或参数等)hitcounts
    • 近似响应时间的直方图(使用%T参数)

    我通常使用Perl,因为最终它变得足够复杂,是值得的。


    对于非200状态代码,非perl示例将是每分钟快速命中率:

     tail -9000 access_log | grep -v '" 200 ' | cut -d: -f2,3 | uniq -c 

    是的,我用这个grep作弊,假定一个引用空间200的空间只匹配http状态码….可以使用awk或者perl隔离这个字段,只要记住它可能是不准确的。


    在perl中一个更复杂的例子可能是可视化一个模式的命中率的变化。

    在下面的脚本中有很多需要咀嚼的地方,尤其是如果你不熟悉perl。

    • 读取标准input,所以你可以使用你的日志的一部分,尾巴(特别是与尾巴-f),有或没有greps和其他过滤…
    • 使用正则expression式的黑客作弊时间戳记和使用Date :: Manip
    • 您可以稍微修改它以提取响应时间或其他任意数据

    代码如下:

     #!/usr/bin/perl # script to show changes in hitrates for any regex pattern # results displayed with arbitrary intervals # and ascii indication of frequency # gaps are also displayed properly use Date::Manip; use POSIX qw(strftime); $pattern=shift || "."; $ival=shift || 60; $tick=shift || 10; $minb=undef; while (<>){ next unless /$pattern/; $stamp="$1 $2" if m[(../.../....):(..:..:..)]; $epoch = UnixDate(ParseDate($stamp),"%s"); $bucket= int($epoch/$ival)*$ival; $minb=$bucket if $bucket<$minb || !defined($minb); $maxb=$bucket if $bucket>$maxb; $count{$bucket}++; } # loop thru the min/max range to expose any gaps for($t=$minb;$t<=$maxb;$t+=$ival){ printf "%s %s %4d %s\n", $t, strftime("%m/%d/%Y %H:%M:%S",localtime($t)), $count{$t}+0, substr("x"x100,0,$count{$t}/$tick ); } 

    如果你只是想处理标准指标,结帐

    • 'mergelog'将所有日志放在一起(如果在负载平衡器后面有多个apacs)和
    • webalizer(或awstats或其他常见的分析仪)。

    这里是我的'sed'例子,它读取apache日志的默认格式并将其转换为更方便的自动处理。 整行被定义为正则expression式,variables被保存并写入输出,用'#'作为分隔符。

    input的简化符号为:%s%s%s [%s]“%s”%s%s“%s”“%s”

    示例input行:xx.xx.xx.xx – – [29 / Mar / 2011:12:33:02 +0200]“GET /index.html HTTP / 1.0”200 9443“ – ”“Mozilla / 4.0”

    示例输出行:xx.xx.xx.xx# – # – #29 / Mar / 2011:12:33:02 + 0200#GET /index.html HTTP / 1.0#200#9443# – #Mozilla / 4.0

     cat access.log | \ sed 's/^\(.*\) \(.*\) \(.*\) \[\(.*\)\] \"\(.*\)\" \(.*\) \(.*\) \"\(.*\)\" \"\(.*\)\"$/\1#\2#\3#\4#\5#\6#\7#\8#\9/g' 

    感受正则expression式的力量:-)

    我通过拖拽或抓取文件来使用awk。 每个晚上我都会为每个服务器提供一个networking报告。 根据你的日志文件和你的LogFormat,你需要编辑一些内衬来为你工作。

    这是一个简单的例子:

    如果我想在我的服务器上只logging404/500状态码的日志,我会这样做:

     # $6 is the status code in my log file tail -f ${APACHE_LOG} | awk '$8 ~ /(404|500)/ {print $6}' 

    <snip>

     echo "" #echo "Hits by source IP:" echo "======================================================================" awk '{print $2}' "$1" | grep -ivE "(127.0.0.1|192.168.100.)" | sort | uniq -c | sort -rn | head -25 echo "" echo "" #echo "The 25 most popular pages:" echo "======================================================================" awk '{print $6}' "$1" | grep -ivE '(mod_status|favico|crossdomain|alive.txt)' | grep -ivE '(.gif|.jpg|.png)' | \ sed 's/\/$//g' | sort | \ uniq -c | sort -rn | head -25 echo "" echo "" echo "The 25 most popular pages (no js or css):" echo "======================================================================" awk '{print $6}' "$1" | grep -ivE '(mod_status|favico|crossdomain|alive.txt)' | grep -ivE '(.gif|.jpg|.png|.js|.css)' | \ sed 's/\/$//g' | sort | \ uniq -c | sort -rn | head -25 echo "" #echo "The 25 most common referrer URLs:" echo "======================================================================" awk '{print $11}' "$1" | \ grep -vE "(^"-"$|/www.$host|/$host)" | \ sort | uniq -c | sort -rn | head -25 echo "" #echo "Longest running requests" echo "======================================================================" awk '{print $10,$6}' "$1" | grep -ivE '(.gif|.jpg|.png|.css|.js)' | awk '{secs=0.000001*$1;req=$2;printf("%.2f minutes req time for %s\n", secs / 60,req )}' | sort -rn | head -50 exit 0 

    </ snip>

    谁热链接你的图片:

     awk -F\" '($2 ~ /\.(jpg|gif)/ && $4 !~ /^http:\/\/www\.mydomain\.com/){print $4}' access_log | sort | uniq -c | sort 

    虽然不是sed或awk,但是有两件事对于处理apache和icecast日志文件非常有用。

    AWStats有一个名为logresolvemerge.pl的非常有用的脚本,它将合并多个压缩或未压缩的日志文件,stripdupe和按时间戳sorting。 它也可以做DNS查找并configuration为运行multithreading。 当使用awstats时,这是非常有用的,因为awstats不能添加比当前数据库更早的时间戳的日志行,所以必须按顺序添加所有日志行,但是这很容易,因为您只需将所有内容都放在logresolvemerge.pl中,并且它们都可以很好地popup。

    sed和awk在处理date方面相当糟糕,因为它们通常将它们视为string。 awk有一些时间和date的function,但是它们不怎么样。 例如,如果文件中没有出现这些确切的时间戳(即使它们之间存在值),提取两个时间戳之间的一系列行也很困难 – Chris的例子恰好是这个问题。 为了解决这个问题,我编写了一个PHP脚本 ,用于报告日志文件时间戳范围,也可以使用任意date或时间格式(不需要与日志文件的时间戳格式相匹配)按时间戳范围提取块。

    为了保持这个话题,这里有几个有用的awkisms:获取来自apache或icecast日志的总字节数:

     cat access.log | awk '{ sum += $10 } END { print sum }' 

    获取从icecast日志连接的总秒数:

     cat access.log | awk '{ sum += $13 } END { print sum }' 

    我倾向于大部分时间做的事情是根据时间阅读日志的部分,所以我写了下面的脚本使用sed来拉出我感兴趣的时期,它适用于我所来的每个日志文件也可以处理归档日志。

     #!/斌/庆典
     #此脚本应该返回2个值之间的一组行,主要目的是在2次之间search日志文件
     #Script用法:logship.sh“开始”“停止”文件
    
     #如果文件在date范围中包含任何“/”,则以下两行添加转义字符,以便可以对这些字符执行search
     start = $(echo“$ 1”| sed's / \ // \\\ // g')
     stop = $(echo“$ 2”| sed's / \ // \\\ // g')
    
     zipped = $(echo“$ 3”| grep -c“gz $”)#如果文件被压缩或不压缩
    
    如果[“$ zip”==“1”]; 那么#如果文件被压缩,然后在sed之前通过zcat
             zcat $ 3 |  sed -n“/ $ start /,/ $ stop / p”;
    其他
             sed -n“/ $ start /,/ $ stop / p”$ 3;  #如果没有压缩就运行sed
    科幻
    

    恢复这个旧的线程,放弃大日志文件的asql后,寻找一个解决schemeagaing,也是在serverfault,我发现这里是一个开源工具,它能够做实时监控或进程日志和获取统计信息(top N),非常灵活和强大,官方的地方在这里