Извлечь последние 10 минут из файла журнала

Пытаясь найти простой способ просмотра последних событий (менее чем за 10 минут), я попробовал это:

awk "/^$(date --date="-10 min" "+%b %_d %H:%M")/{p++} p" /root/test.txt

но это не работает, как ожидалось...

Лог-файлы в форме:

Dec 18 09:48:54 Blah
Dec 18 09:54:47 blah bla
Dec 18 09:55:33 sds
Dec 18 09:55:38 sds
Dec 18 09:57:58 sa
Dec 18 09:58:10 And so on...

6 ответов

Решение

Вы можете сопоставить диапазон дат с помощью простого сравнения строк, например:

d1=$(date --date="-10 min" "+%b %_d %H:%M")
d2=$(date "+%b %_d %H:%M")
while read line; do
    [[ $line > $d1 && $line < $d2 || $line =~ $d2 ]] && echo $line
done

Например, если d1='Dec 18 10:19' а также d2='Dec 18 10:27' тогда вывод будет:

Dec 18 10:19:16
Dec 18 10:19:23
Dec 18 10:21:03
Dec 18 10:22:54
Dec 18 10:27:32

Или используя awk если вы хотите:

awk -v d1="$d1" -v d2="$d2" '$0 > d1 && $0 < d2 || $0 ~ d2'

Вот хороший диапазон инструментов, который вы хотите от -10 до сих пор

sed -n "/^$(date --date='10 minutes ago' '+%b %_d %H:%M')/,\$p" /var/log/blaaaa

Это (обычная) работа для Perl!

Просто и эффективно:

perl -MDate::Parse -ne 'print if/^(.{15})\s/&&str2time($1)>time-600' /path/log

Эта версия печатает последние 10 минут, до сих пор, используя time функция.

Вы можете проверить это с:

sudo cat /var/log/syslog |
  perl -MDate::Parse -ne '
    print if /^(\S+\s+\d+\s+\d+:\d+:\d+)\s/ && str2time($1) > time-600'

Обратите внимание, что первое представление использует только 15 символов из каждой строки, в то время как вторая конструкция использует более детальное регулярное выражение.

Как Perl-скрипт: last10m.pl

#!/usr/bin/perl -wn

use strict;
use Date::Parse;
print if /^(\S+\s+\d+\s+\d+:\d+:\d+)\s/ && str2time($1) > time-600

Строго: извлеките последние 10 минут из файла журнала

Значение не по отношению к текущему времени, а к последней записи в лог-файле:

Существует два способа получения конца периода:

date -r logfile +%s
tail -n1 logfile | perl -MDate::Parse -nE 'say str2time($1) if /^(.{15})/'

Если логически, время последнего изменения файла журнала должно быть временем последней записи.

Таким образом, команда может стать:

perl -MDate::Parse -ne 'print if/^(.{15})\s/&&str2time($1)>'$(
    date -r logfile +%s)

или вы можете взять последнюю запись в качестве ссылки:

perl -MDate::Parse -E 'open IN,"<".$ARGV[0];seek IN,-200,2;while (<IN>) {
    $ref=str2time($1) if /^(\S+\s+\d+\s+\d+:\d+:\d+)/;};seek IN,0,0;
    while (<IN>) {print if /^(.{15})\s/&&str2time($1)>$ref-600}' logfile

Вторая версия кажется сильнее, но доступ к файлу только один раз.

Как Perl-скрипт, это может выглядеть так:

#!/usr/bin/perl -w

use strict;
use Date::Parse;
my $ref;                 # The only variable I will use in this.

open IN,"<".$ARGV[0];    # Open (READ) file submited as 1st argument
seek IN,-200,2;          # Jump to 200 character before end of logfile. (This
                         # could not suffice if log file hold very log lines! )
while (<IN>) {           # Until end of logfile...
    $ref=str2time($1) if /^(\S+\s+\d+\s+\d+:\d+:\d+)/;
};                       # store time into $ref variable.
seek IN,0,0;             # Jump back to the begin of file
while (<IN>) {
    print if /^(.{15})\s/&&str2time($1)>$ref-600;
}

Но если вы действительно хотите использовать Bash

Существует очень быстрый чистый bash- скрипт:

Предупреждение: это использовать последние bashisms, требуют $BASH_VERSION 4.2 или выше.

#!/bin/bash

declare -A month

for i in {1..12};do
    LANG=C printf -v var "%(%b)T" $(((i-1)*31*86400))
    month[$var]=$i
  done

printf -v now "%(%s)T" -1
printf -v ref "%(%m%d%H%M%S)T" $((now-600))

while read line;do
    printf -v crt "%02d%02d%02d%02d%02d" ${month[${line:0:3}]} \
        $((10#${line:4:2})) $((10#${line:7:2})) $((10#${line:10:2})) \
        $((10#${line:13:2}))
    # echo " $crt < $ref ??"   # Uncomment this line to print each test
    [ $crt -gt $ref ] && break
done
cat

Сохраните этот скрипт и запустите:

cat >last10min.sh
chmod +x last10min.sh
sudo cat /var/log/syslog | ./last10min.sh

Строго: извлеките последние 10 минут из файла журнала

Просто замените строку 10, но вы должны поместить имя файла в скрипт и не использовать его в качестве фильтра:

#!/bin/bash

declare -A month

for i in {1..12};do
    LANG=C printf -v var "%(%b)T" $(((i-1)*31*86400))
    month[$var]=$i
  done

read now < <(date -d "$(tail -n1 $1|head -c 15)" +%s)
printf -v ref "%(%m%d%H%M%S)T" $((now-600))

export -A month

{
    while read line;do
        printf -v crt "%02d%02d%02d%02d%02d" ${month[${line:0:3}]} \
            $((10#${line:4:2})) $((10#${line:7:2})) $((10#${line:10:2})) \
            $((10#${line:13:2}))
        [ $crt -gt $ref ] && break
    done
    cat
} <$1

В Bash вы можете использовать date Команда для анализа меток времени. Спецификатор формата "%s" преобразует данную дату в количество секунд с 1970-01-01 00:00:00 UTC. Это простое целое число легко и точно выполнить основную арифметику.

Если вы хотите сообщения журнала за последние 10 минут фактического времени:

now10=$(($(date +%s) - (10 * 60)))

while read line; do
    [ $(date -d "${line:0:15}" +%s) -gt $now10 ] && printf "$line\n"
done < logfile

Обратите внимание ${line:0:15} выражение - это расширение параметра bash, которое дает первые 15 символов строки, то есть саму метку времени.

Если вы хотите сообщения журнала за последние 10 минут относительно конца журнала:

$ lastline=$(tail -n1 logfile)
$ last10=$(($(date -d "$lastline" +%s) - (10 * 60)))
$ while read line; do
> [ $(date -d "${line:0:15}" +%s) -gt $last10 ] && printf "$line\n"
> done < logfile
Dec 18 10:19:16
Dec 18 10:19:23
Dec 18 10:21:03
Dec 18 10:22:54
Dec 18 10:27:32
$ 

Вот небольшое улучшение производительности по сравнению с вышеупомянутым:

$ { while read line; do
> [ $(date -d "${line:0:15}" +%s) -gt $last10 ] && printf "$line\n" && break
> done ; cat ; }  < logfile
Dec 18 10:19:16
Dec 18 10:19:23
Dec 18 10:21:03
Dec 18 10:22:54
Dec 18 10:27:32
$ 

Это предполагает, что записи в журнале находятся в строгом хронологическом порядке. Как только мы сопоставим метку времени, мы выходим из цикла for, а затем просто используем cat чтобы сбросить оставшиеся записи.

В Python вы можете сделать следующее:

from datetime import datetime

astack=[]
with open("x.txt") as f:
    for aline in f:
        astack.append(aline.strip())
lasttime=datetime.strptime(astack[-1], '%b %d %I:%M:%S')
for i in astack:
    if (lasttime - datetime.strptime(i, '%b %d %I:%M:%S')).seconds <= 600:
        print i

Поместите строки из файла в стек (список Python). вставьте последний элемент и получайте разницу между последовательными элементами даты, пока разница не станет меньше 600 секунд.

Запустив на ваш вход, я получаю следующее:

Dec 18 10:19:16
Dec 18 10:19:23
Dec 18 10:21:03
Dec 18 10:22:54
Dec 18 10:27:32

Решение Ruby (протестировано на ruby ​​1.9.3)

Вы можете передать дни, часы, минуты или секунды в качестве параметра, и он будет искать выражение и указанный файл (или каталог, в этом случае он будет добавлять '/*' к имени):

В вашем случае просто вызовите скрипт так: $0 -m 10 "expression" log_file

Примечание: также, если вы знаете местоположение 'ruby', измените shebang (первая строка скрипта) по соображениям безопасности.

#! /usr/bin/env ruby

require 'date'
require 'pathname'

if ARGV.length != 4
        $stderr.print "usage: #{$0} -d|-h|-m|-s time expression log_file\n"
        exit 1
end
begin
        total_amount = Integer ARGV[1]
rescue ArgumentError
        $stderr.print "error: parameter 'time' must be an Integer\n"
        $stderr.print "usage: #{$0} -d|-h|-m|-s time expression log_file\n"
end

if ARGV[0] == "-m"
        gap = Rational(60, 86400)
        time_str = "%b %d %H:%M"
elsif ARGV[0] == "-s"
        gap = Rational(1, 86400)
        time_str = "%b %d %H:%M:%S"
elsif ARGV[0] == "-h"
        gap = Rational(3600, 86400)
        time_str = "%b %d %H"
elsif ARGV[0] == "-d"
        time_str = "%b %d"
        gap = 1
else
        $stderr.print "usage: #{$0} -d|-h|-m|-s time expression log_file\n"
        exit 1
end

pn = Pathname.new(ARGV[3])
if pn.exist?
        log = (pn.directory?) ? ARGV[3] + "/*" : ARGV[3]
else
        $stderr.print "error: file '" << ARGV[3] << "' does not exist\n"
        $stderr.print "usage: #{$0} -d|-h|-m|-s time expression log_file\n"
end

search_str = ARGV[2]
now = DateTime.now

total_amount.times do
        now -= gap
        system "cat " << log << " | grep '" << now.strftime(time_str) << ".*" << search_str << "'"
end
Другие вопросы по тегам