在bash中使用连续附加的日志文件

时间:2017-04-05 05:42:22

标签: bash awk

我有一个连续附加的日志文件,如下所示。我的目的是每分钟找到REJECT计数。一个微小的cron无法以恰好在9:15:00运行的方式同步,这使得这个问题变得棘手。

20160302-09:15:01.283619074 ResponseType:RMS_REJECT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:100107|TimeStamp:1490586301283617274
20160302-09:15:01.287619074 ResponseType:RMS_REJECT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:100107|TimeStamp:1490586301283617274
20160302-09:15:01.289619074 ResponseType:RMS_REJECT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:100107|TimeStamp:1490586301283617274
20160302-09:15:01.290619074 ResponseType:RMS_REJECT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:100107|TimeStamp:1490586301283617274
20160302-09:15:01.291619074 ResponseType:RMS_REJECT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:100107|TimeStamp:1490586301283617274
20160302-09:15:01.295619074 ResponseType:RMS_REJECT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:100107|TimeStamp:1490586301283617274
20160302-09:15:01.297619074 ResponseType:RMS_REJECT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:100107|TimeStamp:1490586301283617274
20160302-09:16:02.283619074 ResponseType:RMS_REJECT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:100107|TimeStamp:1490586301283617274
20160302-09:16:03.283619074 ResponseType:RMS_REJECT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:100107|TimeStamp:1490586301283617274
20160302-09:17:02.283619074 ResponseType:RMS_REJECT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:100107|TimeStamp:1490586301283617274
20160302-09:17:07.283619074 ResponseType:RMS_REJECT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:100107|TimeStamp:1490586301283617274
20160302-09:18:07.283619074 ResponseType:RMS_REJECT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:100107|TimeStamp:1490586301283617274
20160302-09:19:06.283619074 ResponseType:ACCEPT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:10|TimeStamp:1490586301283617274
20160302-09:19:07.283619074 ResponseType:ACCEPT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:10|TimeStamp:1490586301283617274
20160302-09:19:07.283619075 ResponseType:ACCEPT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:10|TimeStamp:1490586301283617274
20160302-09:20:07.283619075 ResponseType:ACCEPT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:10|TimeStamp:1490586301283617274
20160302-09:21:07.283619075 ResponseType:ACCEPT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:10|TimeStamp:1490586301283617274
20160302-09:22:07.283619074 ResponseType:RMS_REJECT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:100107|TimeStamp:1490586301283617274

预期输出

8 9:15 REJECT 7
10 9:16 REJECT 2
12 9:17 REJECT 2
#continue appending every 2 minutes or so by reading more logged lines eg. 9:19 then 9:22 and so on

我的代码:

#!/usr/bin/bash
today=$(date +%Y%m%d)
touch ${today}.log
counter=$(tail -1 ${today}.log | cut -d" " -f1) #get line number to tail from

re='^[0-9]+$'
if ! [[ $counter =~ $re ]] ; then
   counter=1
fi
echo "$counter"

echo "tail -n +$counter $1 | grep "ErrorCode:100107" |cut -d'|' -f1 | awk -F '[:-]' '{curr=$2":"$3} (prev!="") && (curr!=prev){print NR, prev, $NF, cnt; cnt=0} {cnt++; prev=curr}' >> ${today}.log"
tail -n +$counter $1 | grep "ErrorCode:100107" |cut -d'|' -f1 | awk -F '[:-]' '{curr=$2":"$3} (prev!="") && (curr!=prev){print NR, prev, $NF, cnt; cnt=0} {cnt++; prev=curr}' >> ${today}.log

我打算每2或3分钟运行一次这个脚本(延迟记录暂时没问题)。但是不想每次都重新读取整个文件(文件将以GB为单位),所以我尝试使用行号和尾部添加行。但是这在第二次迭代期间失败,线号被重置。有没有办法只在新添加的行上工作。

UPDATE1: 生成连续日志文件

while :; do echo "$(date +%Y%m%d-%H:%M:%S).21223 ResponseType:RMS_REJECT|OrderID:4000007|Symbol:-10|Side:S|Price:175|Quantity:10000|AccountID:BMW500  |ErrorCode:100107|TimeStamp:1490586301283617274";sleep $(shuf -i 5-20 -n 1);done >> test_log &

我使用test_log作为参数./myscript.sh test_log运行脚本 myscript.sh的内容

#!/usr/bin/bash

inputfile=$1

tail -f $inputfile | grep "ErrorCode:100107" |cut -d'|' -f1 | awk '/RMS_REJECT/{key=$2":"$3;a[key]++;if (LK && LK != key){print LK,a[LK]+1;delete a};LK=key}' FS='[-:]' #does not give output on teminal
# however this works=> cat $inputfile | grep "ErrorCode:100107" |cut -d'|' -f1 | awk '/RMS_REJECT/{key=$2":"$3;a[key]++;if (LK && LK != key){print LK,a[LK]+1;delete a};LK=key}' FS='[-:]'

这在某种程度上不会给我终端上的连续输出

参考: Counting lines in a file during particular timestamps in bash

6 个答案:

答案 0 :(得分:2)

gawk时间戳迭代中更新 结果

tail -f your_log_file|gawk '/RMS_REJECT/{key=$2":"$3;a[key]++;if (LK && LK != key){print LK,a[LK];delete a[LK]};LK=key}' FS='[-:]'

简单且最小的内存开销。

<强>解释

FS='[-:]'F ield S eparator使用-设置为:regex

/RMS_REJECT/仅考虑被拒绝的记录,因此我们会查找适当的模式,并且当行中不存在时,不执行任何操作。< / p>

key=$2":"$3:根据第二和第三个字段撰写key

a[key]++:将密钥存储在关联数组(哈希类型)中,并累计看到密钥的次数。

我们暂时会跳过if部分的解释。

LK=keyLK代表 Last Key ,因此我们使用此变量存储已处理的 last 键。

if (LK && LK != key){print LK,a[LK];delete a[LK]}:当我们的 Last Key 有一个值且其内容与当前值(key var)不同时,请打印 Last Key 及其哈希值和从数组中删除de key以避免内存开销。

示例

[klashxx]$ while :; do echo "$(date +%Y%m%d-%H:%M:%S).21223 ResponseType:RMS_REJECT";sleep $(shuf -i 5-20 -n 1);done >> test_log &
[1] 4040
[klashxx]$ tail -f test_log |gawk '/RMS_REJECT/{key=$2":"$3;a[key]++;if (LK && LK != key){print LK,a[LK];delete a[LK]};LK=key}' FS='[-:]'
09:22 6
09:23 5
09:24 6
09:25 3
^C 

(按下Control + C)

[klashxx]$ cat test_log 
20170405-09:22:13.21223 ResponseType:RMS_REJECT
20170405-09:22:20.21223 ResponseType:RMS_REJECT
20170405-09:22:29.21223 ResponseType:RMS_REJECT
20170405-09:22:44.21223 ResponseType:RMS_REJECT
20170405-09:22:53.21223 ResponseType:RMS_REJECT
20170405-09:23:07.21223 ResponseType:RMS_REJECT
20170405-09:23:22.21223 ResponseType:RMS_REJECT
20170405-09:23:38.21223 ResponseType:RMS_REJECT
20170405-09:23:45.21223 ResponseType:RMS_REJECT
20170405-09:23:55.21223 ResponseType:RMS_REJECT
20170405-09:24:05.21223 ResponseType:RMS_REJECT
20170405-09:24:16.21223 ResponseType:RMS_REJECT
20170405-09:24:24.21223 ResponseType:RMS_REJECT
20170405-09:24:31.21223 ResponseType:RMS_REJECT
20170405-09:24:43.21223 ResponseType:RMS_REJECT
20170405-09:24:56.21223 ResponseType:RMS_REJECT
20170405-09:25:13.21223 ResponseType:RMS_REJECT
20170405-09:25:23.21223 ResponseType:RMS_REJECT
20170405-09:25:43.21223 ResponseType:RMS_REJECT
20170405-09:26:01.21223 ResponseType:RMS_REJECT

<强> PS

不要grepcut,只需使用gawk,请检查此answer

<强> PS2

根据您的代码,最终的事情应该是这样的:

#!/usr/bin/bash

inputfile=$1

tail -f $inputfile | gawk -v errorcode="100107" '/RMS_REJECT/ && $20 == errorcode{key=$2":"$3;a[key]++;if (LK && LK != key){print LK,a[LK];delete a[LK]};LK=key}' FS='[-:|]'

答案 1 :(得分:1)

解决问题的一种方法是使用stat来保存日志大小,使用dd来跳过日志中的旧信息,以仅处理收到的REJECT代码最后一分钟。您可以通过{{1}使用sleep :00到下一分钟(例如sleep $((60 - $(date +%S)))秒)的秒数,在下一分钟准确开始下一个循环。

对于作为第一个参数(或默认为/path/to/logfile)的任何日志文件,一个简短示例可能类似于以下内容。您需要添加REJECT逻辑:

#!/bin/bash

lfn=${1:-/path/to/logfile}

size=$(stat -c "%s" "$lfn")         ## save original log size

while :; do

    newsize=$(stat -c "%s" "$lfn")  ## get new log size
    if ((newsize > size)); then     ## if change, use new info

        ## use dd to skip over existing text to new text
        newtext=$(dd if="$lfn" bs="$size" skip=1 2>/dev/null)

        ## process newtext however you need
        printf "\n newtext: %s\n" "$newtext"


    elif ((newsize < size)); then   ## handle log truncation

        newtext=$(dd if="lfn" 2>/dev/null)
        if [ -n "$newtext" ]; then

            ## process newtext however you need
            printf "\n newtext: %s\n" "$newtext"

        fi

    fi

    size=$((newsize));              ## update size to newsize

    sleep $((60 - $(date +%S)))     ## sleep seconds until next min

done

您可以使用类似nrejects=$(grep -c RMS_REJECT <<<"$newtext")的内容来获取每次迭代的拒绝次数。

答案 2 :(得分:1)

我提出了一个完全不同的方法!

我没有安装cpp,但cpp取决于libc,因此无论如何都应使用journal

我在互联网上找到this example来设置编程代码。

然后我使用this guide来学习journalctl commands

洞魔术......
您需要service内的systemd才能阅读。最简单的方法是致电systemctl list-units并搜索您想要阅读的服务。

然后您可以使用Journalctl -f(类似tail -f)打印出创建的每个新条目。您也可以指示输出。不再需要awk或类似,而是您可以使用JSON或您想要的任何内容。

我希望这也有助于您未来的项目!

答案 3 :(得分:0)

好吧,一种方法可能是tail -f日志文件并将其输出重定向到fifo并在fifo上有一个awk工作,如下所示:

模拟日志文件:

$ while true ; do echo foo ; sleep 1 ; done > simulated_logfile

制作一个fifo:

$ mkfifo fifo

tail -f日志文件并提供fifo:

$ tail -f simulated_logfile > fifo

连续使用awk处理输出:

$ awk '{print NR}' fifo

使你的awk脚本收集信息到一个哈希值,直到分钟更改然后输出,重置变量并继续收集,因为一旦awk脚本退出,tail进程也将停止。

答案 4 :(得分:0)

您可以尝试使用以下bash + awk来解决动态日志读取问题。

$ cat logappend.sh 
#!/bin/bash
start=1
end=$(wc -l < file)
while [ 1 ]
do
awk -v start=$start -v end=$end -F '[|:-]' '/ErrorCode:100107/ && (NR>=start && NR<= end) {curr=$2":"$3} (prev!="") && (curr!=prev){print NR, prev, $5, cnt; cnt=0} {cnt++; prev=curr}' file > output.txt
cat output.txt
sleep 20
start=$end
end=$(wc -l < file)
done
  

注意:您需要根据需要更改睡眠时间。

对于第一次运行,它将运行完整文件,从第二次运行,它将仅通过更改开始和结束变量来检查已添加到现有文件的新行。

处理......第一次运行 -

$ ./logappend.sh 
8 09:15 RMS_REJECT 7
10 09:16 RMS_REJECT 2
11 09:17 RMS_REJECT 1
18 09:15 RMS_REJECT 7
20 09:16 RMS_REJECT 2
script is going to sleep for 20 sec

此脚本将等待20秒,然后通过将前一个结束值分配给start变量并为结束变量设置新值来查找文件中的新更改。 为了测试,我只将前10行文件复制到现有文件

cp file tempfile
head tempfile >> file

###将10行附加到文件

同时你的20秒将完成,你可以看到新的输出 -

21 09:17 RMS_REJECT 20
28 09:15 RMS_REJECT 7
30 09:16 RMS_REJECT 2
script is going to sleep for 20 sec

答案 5 :(得分:0)

awk -F '[.|[:blank:]]+' -v FlushLast=0 '
   # assume first time for counter + init
   BEGIN {
      CounterFile = ARGV[ ARGC - 2]
      printf( "" ) >> CounterFile
      Count = 0
      }

   # Load last know reference
   FILENAME == CounterFile {
      Last = $1
      LastRef = 1
      next
      }

   # skip earlier stat
   $1 <= Last {
      next
      }

   # treat new date change
   Last != $1 {
      if( ! LastRef ) {
         print Last " " Count
         print Last " " Count >> CounterFile
         }
      LastRef = 0
      Last = $1
      Count = 0
      }

   # count reject
   $3 ~ /REJECT/ {
      Count++
      }

   # if you want the very last date also (if FlusLast==2 mean next cycle it s ommited )
   END {
      if( FlushLast ) {
         # printf( "flush: " )
         print Last " " Count
         if ( FlushLast > 1 ) print Last " " Count >> CounterFile
         }
      }
   ' ${today}.Counter ${today}.log

注意:

  • 参数FlushLast允许打印最后未完成的&#39; stat(仅打印1次,2次打印+计数器,0次不打开)
  • 创建一个计数器文件(假设每个今天名称,但也可以是uniq或其他任何地方)