同事说能否分享一下linux日志分析的心得,尽管我也只是初学小菜,不过既然问到了,那就献丑了。
linux里面有那么几个常用的命令,在大部分日志分析场景里面都会用到:
cut 按照特定分割符分成若干列,取出其中的某一列。
-d" " 表示用空格作为分割符默认好像是逗号;
-f8 表示取出第8列。
sort 按照特定的分隔符分成若干列,按照某一列进行排序。
-d" " 表示用空格(默认应该就是空格),-f1,2表示用第1、2列输出;
-g 表示按照数字进行排序;
-r 表示逆序;(默认从小到大);
-k8 表示根据第8个字段进行排序;(默认只根据第1列排序,可以通过设置IFS来指定分隔符)
-k1,3 表示根据第1至3个字段之间的内容进行排序;
-u 表示相同的元素只输出一次。
uniq 如果下一行重复则消除。
-c 表示统计出现次数(重复一次计数为2)。
head 输出最开始的几行。
-n8 表示输出头8行。(无参数时默认10行)
tail 输出最末尾的几行。
-n8 表示输出尾8行。(无参数时默认10行)
wc 输出指定文字出现的次数。
-l 表示输出出现该文字的行数而不是次数。
grep 输出包含指定文字的若干行。
-i 表示大小写不敏感;
-e 表示使用正则表达式;(等同于egrep命令)
-v 表示输出不等于制定文字的若干行;
-m8 表示指定文件中的输出前8个;
-a 表示如果是二进制文件,则仅对比里面是文字的部分;(比如要找squid缓存文件当中记录的,与该文件对应的uri时就特别有用);
-r 表示递归搜索所有文件夹中的指定文件;
-H 在每一行输出中均包括文件名;(搜索单个文件时默认不输出)
-h 在每一行输出中均不包含文件名;(搜索多个文件时默认输出)
-l 仅输出有命中的文件名;
-L 仅输出没有命中的文件名;
-B8 输出命中该行之前的8行;
-A8 输出命中该行之后的8行;
-c 仅输出命中次数。
上面这个命令组合通常就可以进行大部分的日志分析工作,下面举例说明。
我们假设日志名称为squid.log,格式如下所示(有点像Squid,但实际上不是真实的日志格式,只是为了便于说明):
- - [2012/1/1 04:23:44] 223.33.54.123 "GET /pages/head.htm HTTP/1.1" {www.fix.com|http://www.google.com/search} 200 - - TCP_HIT:NONE "Mozilla/4.0"
例1 查看最后若干个访问"/pages/head.htm"的IP都来自哪里:
grep -i "/pages/head.htm" squid.log | tail -n20 | cut -d" " -f5
注意:
- 日期和时间当中有一个空格,因此是两个字段,而不会因为方括号而认为是一个字段。同样的,单引号双引号也不会影响以空格为分割符的分割;
- 如果文件非常大导致明显非常慢,可以尝试用tail作为开头进一步限制,如:
tail -n1000 squid.log | grep -i "/pages/head.htm" | tail -n20 | cut -d" " -f5
例2 统计最后若干个访问"/pages/head.htm"的IP的次数:
grep -i "/pages/head.htm" squid.log | tail -n1000 | cut -d" " -f5 | sort | uniq -c | sort -g
备注:这里的技巧是,先要sort,然后再通过uniq -c来统计次数,然后再通过sort来进行排序。这是因为对于uniq命令来说,之比较上一行和下一行,而不会考虑交错的情况,例如对于文件tmp如下:
A
A
B
B
A
A
C
用uniq -c tmp来检查,输出如下:
2 A
2 B
2 A
1 C
而实际上我们希望的输出类似如下:
1 B
2 C
4 A
于是只能够通过sort先将相同的内容放到一起,然后再用uniq来统计。sort命令当中的-u参数并不会统计处数量,于是必须使用uniq命令。同时uniq输出的顺序是原始文本出现的顺序,而不是根据数量进行排序的,因此最后还需要用sort进行排序。
例3 统计最后若干个访问"/pages/head.htm"的IP中,出现次数最多的前三名以及其次数:
grep -i "/pages/head.htm" squid.log | tail -n1000 | cut -d" " -f5 | sort | uniq -c | sort -gr | head -n3
例4 统计后端返回状态为503的访问次数:
cut -d" " -f10 "/pages/head.htm" squid.log | grep 503 | wc -l
例5 统计后端返回状态为200,且访问"/pages/head.htm"的HIT和MISS的情况(日志中TCP_HIT:NONE的那一列):
这一个需求比较复杂,我们需要动用上面没有介绍到的另一个命令:awk
awk 对每一行按照指定分隔符进行分割,并根据指定的条件输出指定的内容。
-F" " 表示输出以空格作为分割符。
完整命令格式如下:
awk [条件] ['指令'] [待分析文件]
之所以指令是可选的,是因为参数中还有一个可以指定指令文件的参数;而待分析文件可选,是因为可以通过管道提供待分析文件。
其中的指令部分比较复杂,完整的这里就不介绍了,请自行谷歌之。这里介绍非常简单的几个常用部分。
$1 表示第一个字段
$1>2 表示第一个字段是数字且大于数字2
$1>"2" 表示第一个字段大于字符2,也就是说"12"<"2"
$1==$2 表示第一个字段等于第二个字段
&& 表示与
|| 表示或
a { b } 表示如果前面的a部分匹配,则执行b部分
print 表示输出整行
print $1 表示输出第一个字段
; 表示两个指令之间的分割
于是
$1==200 { print; }
就是说第一列等于200就输出整行。
对于本示例,将需要使用如下命令:
awk -F" " '$10==200 { print; }' squid.log | grep -i "/pages/head.htm" | cut -d" " -f13 | sort | uniq -c | sort -g
例5 统计UserAgent的情况,但前面的访问路径处可能包含不定数量的空格,比如某些访问没有发出HTTP/1.1,于是只有"GET /pages/head.htm"。
这个问题很容易想到:啊,如果能够忽略双引号中的空格多好啊!嗯,这种思路应该也是可以的,但是比起下面这种思路来说,有的时候真的太复杂了。
cut -d\" -f4 squid.log | sort | uniq -c | sort -g
“……靠,还能用双引号做分割符,咋就没想到呢?”好吧,再提示一下:每一个命令可以考虑使用不同的分割符,而不需要每一次都用同一个分割符。
没有评论:
发表评论