【shell】shell脚本在大文件日志中按照时间段快速搜索日志

来源:互联网 发布:python 函数 编辑:程序博客网 时间:2024/05/21 09:48

问题描述:

在大流量线上服务中,日志系统会产生数量庞大的日志,动辄就是几十G。在如此之大的文件中快速搜索日志是运维人员经常遇见的问题。我们经常遇见的问题是查询一段时间内的某些条日志。比如,今天有一个访问失败了,大约是在上午9点,把这条日志找出来,然后查找失败原因。

常见处理方式及缺点:

1.如果文件比较小,100m以内使用grep、awk或者sed进行逐条匹配比较方便,但是文件非常大时,其查找效率是非常低的,运行时间长达几十分钟甚至上小时。

2.使用hadoop大数据处理,查询速度快,效率高。但是需要我们搭一套hadoop环境,需要巨大的计算量。感觉就是杀鸡焉用牛刀。

3.按照时间手动查找,使用tail -c size filename 命令 提取出一段日志,看看时间是否符合条件。如果不符合就调整size参数直到找到9点之前的时间段,然后从这里开始查起,使用grep、awk或者sed进行逐条匹配,直到找到目标日志然后ctrl+c停止,这样确实能省下很多时间,也是我以前经常用的一种方式。

解决方案:

很显然,手动查找不是一个程序员的理想解决方案,那么能不能写一个脚本自动按照时间段查找超大日志呢?常用的命令如,cat、head、tail、grep、more、less、sed、awk貌似都不能提供有效的方案。下面就是Blog介绍的方法。

下面就是我的脚本,姑且称它为探针法:

#!/bin/bash#读入文件名file_name=$1#读入查询起始时间start=$2#读取文件大小file_size=$(stat --format=%s $file_name)#设置探针步长,一般为文件大小的百分之一到千分之一step=500000000#如果文件大小小于步长,则size为文件大小,否则size为步长[ $file_size -lt $step ] && size=$file_size || size=$step#初始化探针时间test_time="00:00:00"#循环检测,直到探针时间大于查询时间,停止while [[ ${test_time} < ${start} ]]do size=$(($size+$step))    test_time=$(dd if=$file_name skip=$(($size/10000)) ibs=10000 count=1 2>/dev/null | sed -n "2p" | awk '{print $3}')done#读取此时的查询size,使用tail -c命令即从目标时间开始查日志。tail -c $(($file_size-$size+$step)) $file_name


执行sh find.sh filename.log 09:00:00 , 然后再使用grep等命令过滤。

解释一下,这里主要用到了dd命令:拷贝一个文件,并按照参数对其处理转换。

具体的介绍可以查看相关文档,例如,http://blog.chinaunix.net/uid-24958038-id-3416169.html

这里利用dd命令从超大文件中复制出一小块,提取出时间,也就是test_time,判断test_time是否符合条件,不符合就使size增加一个步长然后继续提取时间,直到符合条件。因为dd命令有skip选项,可以直接跳过指定大小部分截取到一段,其效率是非常高的,循环500次耗时大于1s,也就是说探针测试500次的时间是1s左右。

dd命令截取出一段日志后使用sed取出完整的一行,然后用awk取出时间戳。

 注意,设定时间为09:00:00,查询结果并不是严格从09:00:00开始的,一般会往前一点,这里受step参数影响。step越小越精确,而探针尝试次数越多。


-----------------------------------------------------分割线------------------------------------------------------------

以上第一版方法效率并不理想,而且只有开始时间,没有结束时间,所以对脚本做优化改进如下:

1.探针法改为二分查找,精确定位到目标时间

2.增加了结束时间的设置

PS:  

脚本中没有为dd设置输出文件of参数,默认输出到终端,如有需要可以添加该参数。

在GetLocation方法中,使用awk提取时间戳,可根据日志格式的不同修改。

#!/bin/bashif [[ $# -lt 3 ]];thenecho "$0 FILE STARTTIME ENDTIME"echo "STARTTIME and ENDTIME should be in format like 09:00:00"echo exit 1fifile_name=$1start=$2end=$3file_size=$(stat --format=%s $file_name)function GetLocation{echo $(dd if=$file_name skip=$(($location/10000)) ibs=10000 count=1 2>/dev/null | sed -n "2p" | awk '{print substr($3, 1,8)}')}function BinSearch{low=0hight=$file_sizelocation=0while [[ $low -le $hight ]]domid=$((($low+$hight)/2));location=$midtmp_time=`GetLocation`if [[ $1 = $tmp_time ]]thenecho $locationbreakfiif [[ $1 < $tmp_time ]]thenhight=$midfiif [[ $1 > $tmp_time ]]thenlow=$midfidone}start_local=`BinSearch $2`echo $start_localend_local=`BinSearch $3`echo $end_locallength=$(($end_local-$start_local))echo $lengthdd if=$file_name bs=1000 skip=$(($start_local/1000)) count=$(($length/1000)) 2>/dev/null



 

原创,转发请注明。 By LZJing

1 0
原创粉丝点击