您是否有任何有用的awk和grep脚本来解析Apache日志?[关闭]


69

我可以使用日志分析器,但通常我需要解析最近的Web日志以了解当前发生的情况。

有时我会做一些事情来弄清楚请求某个文件的前10个ip

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

您的工具箱中有什么?


1
实际上,我有一个手工编写的美丽的正则表达式,用于将我所有的Apache自定义日志解析为单个字段,以提交到数据库中。我正在踢自己,我已经没有了。那是一个班轮。给您每个日志元素一个变量-然后我将其插入MySQL。如果找到它,我会在这里发布。
凯尔·霍奇森

Answers:


54

仅使用awk,您就可以使用apache日志文件执行几乎所有操作。Apache日志文件基本上是用空格分隔的,您可以假装引号不存在,并可以按列号访问任何您感兴趣的信息。只有当您具有合并的日志格式并且对用户代理感兴趣时,这种情况才出现问题,这时您必须使用引号(“)作为分隔符并运行单独的awk命令。以下内容将向您显示IP每个请求索引页的用户均按匹配数排序:

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

$ 7是请求的网址。您可以在开始时添加任何条件。用所需的任何信息替换'$ 7 ==“ /”。

如果将$ 1替换为(ipcount [$ 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

您还可以通过排序通过管道传递输出以按顺序获取结果,这既可以作为shell命令的一部分,也可以在awk脚本本身中进行:

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

如果您决定扩展awk脚本以打印出其他信息,则后者将很有用。这完全取决于您要查找的内容。这些应该作为您感兴趣的任何内容的起点。


是的,看到疯狂的长cat / grep / awk管道总是很奇怪。一旦您进入awk,通常就足够了。原始帖子的前三个子句可以简单地写为“ awk'/ request_to_file_foo / {print $ 1}'foo.log”。awk可以将文件作为输入,并且可以使用正则表达式知道要关注的行。
扎克·汤普森

优雅而简单。好。
Olivier Dulac 2015年

当心,“ authuser”(第3个)字段中似乎允许使用空格,这会破坏所有内容,我个人认为应禁止这样做;-)
Mandark

23

出于我无法想象的原因,我从未见过其他人做过的一件事,就是将Apache日志文件格式更改为更容易解析的版本,其中包含实际对您重要的信息。

例如,我们从不使用HTTP基本身份验证,因此我们不需要记录这些字段。我感兴趣的每个请求需要多长时间来服务,所以我们要补充的是,对于一个项目,我们也想知道(我们的负载均衡器),如果任何服务器比别人慢服务的要求,所以我们的日志的名称我们要代理的服务器的数量。

这是一台服务器的apache配置的摘录:

# 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的日志上,花费的时间差异很大。当这变得很整洁时,就是要对User-agent做同样的事情。如果日志是用空格分隔的,则必须手动进行一些正则表达式匹配或字符串搜索。使用这种格式,很简单:

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

与上述完全相同。实际上,您要执行的任何摘要基本上都是相同的。

为什么在切割时我会在awk和grep上花我系统的CPU来达到我想要的数量级更快的速度?


2
您为新格式编写的示例实际上仍然过于复杂-IP计数已成为cut -f 3 log | uniq -c | sort -n用户代理cut -f 8 log | uniq -c | sort -n
Creshal 2012年

没错,这很简单。我已经更新了示例以反映这一点。
丹·乌迪

“猫文件| grep字符串”是没有用的,为什么不“ grep字符串文件”呢?
c4f4t0r

2
我没有任何借口,并相应地更新了示例。
丹·乌迪

15

忘记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;

有趣,但是如果您认为日志特别大,可能会遇到问题。还可以很好地处理自定义日志格式吗?
Vagnerr

我目前正在尝试,加载时间太慢了(至少在0.9版中)。加载200Mb日志需要花费超过五分钟的时间
。–等于

我必须说,加载时间(花费了大约15分钟)之后,该程序的语法很棒,您可以对它进行排序,计数和分组。非常好。
等于

Apache HTTPD提供了一种可以有效地将日志发送到数据库的方法。是的,写操作可能会花费很长时间,但是线程代理可能只是做中间的正确事情。无论如何,这将使查询类似语法的SQL的日志快得多。也不涉及加载-数据库服务器永久处于“ ON”状态。
Nearora 2012年

6

这是一个脚本,用于从最近的N个日志条目中查找顶部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

资源


4

访问日志中的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

他们是我最喜欢的“一个班轮” :)


3

建立常见问题列表将是此问题答案的重要索引。我的常见问题是:

  • 为什么命中率发生变化?
  • 为什么整体响应时间会增加?”。

我通过监视服务器状态页面(通过mod_status)的命中率以及活动的和最近完成的请求的近似响应时间来注意到这样的更改(完全知道我错过了大量数据,但是样本足够了)。

我使用以下LogFormat指令(%T确实有用)

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

我正在寻找因果关系以及首先发生的事情...通常是关于日志中模式的特定子集,因此对于任何给定的模式/正则表达式,我需要了解以下内容:

  • 给定模式(IP地址或CGI字符串或参数等)的每个间隔(分钟或小时)的点击计数
  • 近似响应时间的直方图(使用%T参数)

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


一个非perl的示例是非200状态代码的每分钟快速命中率:

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

是的,我正在用那个grep作弊,假设quote-space-200-space只匹配http状态代码。


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

下面的脚本中有很多内容可供您参考,特别是如果您不熟悉perl。

  • 读取标准输入,因此您可以使用部分日志,使用尾巴(尤其是带有尾巴-f),带有或不带有抓钩和其他过滤功能...
  • 欺骗正则表达式和使用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”将所有日志汇总在一起(如果您在负载均衡器后面有多个apap)
  • webalizer(或awstats或其他常见分析器)。

3

在我的“ sed”示例中,它读取apache日志的默认格式,并将其转换为更便于自动处理的格式。整行定义为正则表达式,变量被保存并以'#'作为分隔符写入输出。

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

输入行示例: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'

感受正则表达式的力量:-)


这使得使用AWK进行处理变得轻而易举。正在寻找一种设置通用分隔符的快速方法,因此非常有用。
Citricguy 2014年

我已经感受到了正则表达式的强大功能,只是想通过我自己的调整来删除“ HTML / 1.1”并将协议(可能是非标准的兼容方式)分为自己的领域。享受:```cat access.log | sed's /^(.*)(。*)(。*)[(。*)] \“([[:::)] \ +)(。*)HTTP \ / 1 \ .1 \”( 。*)(。*)\“(。*)\” \“(。*)\” $ / \ 1#\ 2#\ 3#\ 4#\ 5#\ 6#\ 7#\ 8#\ 9#\ 10 / G”```
约什Rumbut

2

我经常通过尾随或整理文件来使用awk。每天晚上,我都会为每个服务器提供一份网络报告。根据您的日志文件和LogFormat,您需要编辑一些衬板才能为您工作。

这是一个简单的例子:

如果我只想为服务器上的日志添加404/500状态代码,则可以这样做:

# $6 is the status code in my log file

tail -f ${APACHE_LOG} |  awk  '$8 ~ /(404|500)/ {print $6}'

<片段>

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>


2

谁在热链接您的图像:

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

1

我通常大部分时间都会做的事情是根据时间读取日志的各个部分,因此我使用sed编写了以下脚本以提取我感兴趣的时间段,该脚本可用于我所访问的每个日志文件可以处理存档的日志。

#!/ bin / bash
#此脚本应在2个值之间返回一组行,主要目的是在2次之间搜索日志文件
#脚本用法:logship.sh“开始”“停止”文件

#如果文件在日期范围内包含任何“ /”,则以下两行添加转义字符,以便可以搜索这些字符
start = $(echo“ $ 1” | sed's / \ // \\\\ // g')
stop = $(echo“ $ 2” | sed's / \ // \\\ /// g')

zipped = $(echo“ $ 3” | grep -c“ gz $”)#确定文件是否压缩

如果[“ $ zipped” ==“ 1”]; 然后#如果文件已压缩,则在sed前将其通过zcat
        zcat $ 3 | sed -n“ / $ start /,/ $ stop / p”;
其他
        sed -n“ / $ start /,/ $ stop / p” $ 3; #如果未压缩,请运行sed
科幻

1

虽然没有sed或awk,但我发现有两件事对处理apache和icecast日志文件很有用。

AWStats有一个非常有用的脚本,称为logresolvemerge.pl,它将结合多个压缩或未压缩的日志文件,剥离重复并按时间戳排序。它还可以进行DNS查找,并配置为运行多线程。当与awstats一起使用时,这特别有用,因为awstats不能添加时间戳比当前数据库旧的日志行,因此必须按顺序添加所有日志行,但这非常容易,因为您只需将所有内容都放在logresolvemerge.pl中,并且所有内容都会弹出。

sed和awk在处理日期方面非常糟糕,因为它们通常将它们视为字符串。awk具有一些时间和日期功能,但功能不尽人意。例如,如果文件中没有出现那些确切的时间戳(即使它们之间的值确实出现了),则很难提取出两个时间戳之间的行范围-克里斯的示例正是有这个问题。为了解决这个问题,我编写了一个PHP脚本,该脚本报告日志文件时间戳范围,并且还可以使用您喜欢的任何日期或时间格式(根据日志文件的时间戳格式)按时间戳范围提取一块块。

为了使这个话题成为话题,这里有一些有用的警告:从apache或icecast日志中获取服务的字节总数:

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

从一个冰封日志中获取连接的总秒数:

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

+1为带有awk的简单字节加总Apache日志
rymo

0

恢复这一古老的线程,在ASQL为大日志文件放弃后,找了一个解决方案againg,也serverfault,我发现了大约WTOP 这里它是一个开源工具,它能够做现场监测或处理日志,并得到统计(上N),非常灵活和强大,官方位置在这里

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.