Фильтрация записей файла журнала на основе диапазона дат

У моего сервера необычно высокая загрузка ЦП, и я вижу, что Apache использует слишком много памяти. У меня такое чувство, что я работаю в DOS по одному IP - может, ты поможешь мне найти его?

Я использовал следующую строку, чтобы найти 10 самых "активных" IP-адресов:

cat access.log | awk '{print $1}' |sort  |uniq -c |sort -n |tail

Топ-5 IP-адресов имеют примерно в 200 раз больше запросов к серверу, чем "средний" пользователь. Тем не менее, я не могу узнать, являются ли эти 5 просто очень частыми посетителями, или они атакуют серверы.

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

Ура!

ОБНОВЛЕНО 23 октября 2011 - Команды, которые мне были нужны:

Получить записи в течение последних X часов [Здесь два часа]

awk -vDate=`date -d'now-2 hours' +[%d/%b/%Y:%H:%M:%S` ' { if ($4 > Date) print Date FS $4}' access.log

Получить наиболее активные IP-адреса за последние X часов [Здесь два часа]

awk -vDate=`date -d'now-2 hours' +[%d/%b/%Y:%H:%M:%S` ' { if ($4 > Date) print $1}' access.log | sort  |uniq -c |sort -n | tail

Получить записи в течение относительного времени

awk -vDate=`date -d'now-4 hours' +[%d/%b/%Y:%H:%M:%S` -vDate2=`date -d'now-2 hours' +[%d/%b/%Y:%H:%M:%S` ' { if ($4 > Date && $4 < Date2) print Date FS Date2 FS $4}' access.log

Получить записи в течение абсолютного времени

awk -vDate=`date -d '13:20' +[%d/%b/%Y:%H:%M:%S` -vDate2=`date -d'13:30' +[%d/%b/%Y:%H:%M:%S` ' { if ($4 > Date && $4 < Date2) print $0}' access.log 

Получить наиболее активные IP-адреса в течение абсолютного времени

awk -vDate=`date -d '13:20' +[%d/%b/%Y:%H:%M:%S` -vDate2=`date -d'13:30' +[%d/%b/%Y:%H:%M:%S` ' { if ($4 > Date && $4 < Date2) print $1}' access.log | sort  |uniq -c |sort -n | tail

5 ответов

Решение

Да, есть несколько способов сделать это. Вот как бы я поступил по этому поводу. Для начала, не нужно передавать вывод cat, просто откройте файл журнала с awk,

awk -vDate=`date -d'now-2 hours' +[%d/%b/%Y:%H:%M:%S` '$4 > Date {print Date, $0}' access_log

при условии, что ваш журнал выглядит как мой (они настраиваются), чем дата хранится в поле 4. и заключена в квадратные скобки. Что я делаю выше, так это нахожу все за последние 2 часа. Note the -d'now-2 hours' или переводится буквально сейчас минус 2 часа, что для меня выглядит примерно так: [10/Oct/2011:08:55:23

Поэтому я сохраняю отформатированное значение два часа назад и сравниваю его с полем четыре. Условное выражение должно быть прямым. Затем я печатаю дату, за которой следует разделитель поля вывода (в данном случае OFS - или пробел), за которым следует целая строка $0. Вы можете использовать свое предыдущее выражение и просто напечатать $1 (IP-адреса)

awk -vDate=`date -d'now-2 hours' +[%d/%b/%Y:%H:%M:%S` '$4 > Date {print $1}' | sort  |uniq -c |sort -n | tail

Если вы хотите использовать диапазон, укажите две переменные даты и создайте свое выражение соответствующим образом.

так что если вы хотите найти что-то 2-4 часа назад, ваше выражение может выглядеть примерно так

awk -vDate=`date -d'now-4 hours' +[%d/%b/%Y:%H:%M:%S` -vDate2=`date -d'now-2 hours' +[%d/%b/%Y:%H:%M:%S` '$4 > Date && $4 < Date2 {print Date, Date2, $4} access_log'

Вот вопрос, на который я ответил относительно дат в bash, которые могут оказаться полезными. Вывести дату на понедельник текущей недели (в bash)

Поскольку это обычная задача Perl

И потому что это не совсем то же самое, что извлекать последние 10 минут из файла журнала, где это занимает кучу времени до конца файла журнала.

И поскольку они мне нужны, я (быстро) написал это:

#!/usr/bin/perl -ws
# This script parse logfiles for a specific period of time

sub usage {
    printf "Usage: %s -s=<start time> [-e=<end time>] <logfile>\n";
    die $_[0] if $_[0];
    exit 0;
}

use Date::Parse;

usage "No start time submited" unless $s;
my $startim=str2time($s) or die;

my $endtim=str2time($e) if $e;
$endtim=time() unless $e;

usage "Logfile not submited" unless $ARGV[0];
open my $in, "<" . $ARGV[0] or usage "Can't open '$ARGV[0]' for reading";
$_=<$in>;
exit unless $_; # empty file
# Determining regular expression, depending on log format
my $logre=qr{^(\S{3}\s+\d{1,2}\s+(\d{2}:){2}\d+)};
$logre=qr{^[^\[]*\[(\d+/\S+/(\d+:){3}\d+\s\+\d+)\]} unless /$logre/;

while (<$in>) {
    /$logre/ && do {
        my $ltim=str2time($1);
        print if $endtim >= $ltim && $ltim >= $startim;
    };
};

Это может быть использовано как:

./timelapsinlog.pl -s=09:18 -e=09:24 /path/to/logfile

для печати журналов с 09:00 до 09:00.

./timelapsinlog.pl -s='2017/01/23 09:18:12' /path/to/logfile

для печати из january 23th, 9h18'12" до сих пор.

Чтобы уменьшить Perl-код, я использовал -s переключиться, чтобы разрешить автоматическое назначение переменных из командной строки: -s=09:18 заполнит переменную $s который будет содержать 09:18, Старайтесь не пропустить знак равенства = и без пробелов!

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

^(\S{3}\s+\d{1,2}\s+(\d{2}:){2}\d+)         # ^Jan  1 01:23:45
^[^\[]*\[(\d+/\S+/(\d+:){3}\d+\s\+\d+)\]    # ^... [01/Jan/2017:01:23:45 +0000]

Если кто-то сталкивается с awk: invalid -v optionВот скрипт для получения наиболее активных IP-адресов в заданном диапазоне времени:

cat <FILE_NAME> | awk '$4 >= "[04/Jul/2017:07:00:00" && $4 < "[04/Jul/2017:08:00:00"' | awk '{print $1}' | sort -n | uniq -c | sort -nr | head -20

Очень быстрый и читаемый способ сделать это на Python. Кажется, это быстрее, чем версия bash. (Расчетное время отображается с помощью внутреннего модуля, который был удален из этого кода)

./ext_lines.py -v -s '12 февраля 00:23:00' -e '15 февраля 00:23:00' -i /var/log/syslog.1

       Total time                : 445 ms 187 musec
Time per line             : 7 musec 58 ns
Number of lines           : 63,072
Number of extracted lines : 29,265

Я не могу сравнить этот код с файлом daemon.log, используемым другими... Но вот мой конфиг

Операционная система: Kubuntu 22.10KDE Plasma Версия: 5.25.5KDE Frameworks Версия: 5.98.0 Версия
Qt: 5.15.6
Версия ядра: 6.2.0-060200rc8-generic (64-разрядная)
Графическая платформа: X11 Процессоры: 16 × AMD Ryzen 7 5700U с графической памятью Radeon
: 14,9 ГБ ОЗУ

Основной код мог бы уместиться всего в одну строку (dts = ...), но для большей удобочитаемости он "разбит" на три. Он не только довольно быстрый, но и очень компактный :-)

      from argparse import ArgumentParser, FileType
from datetime import datetime
from os.path import basename
from sys import argv, float_info
from time import mktime, localtime, strptime

__version__ = '1.0.0'                     # Workaround (internal use)

now = datetime.now

progname = basename(argv[0])

parser = ArgumentParser(description = 'Is Python strptime faster than sed and Perl ?',
                        prog = progname)

parser.add_argument('--version',
                    dest = 'version',
                    action = 'version',
                    version = '{} : {}'.format(progname,
                                               str(__version__)))
parser.add_argument('-i',
                    '--input',
                    dest = 'infile',
                    default = '/var/log/syslog.1',
                    type = FileType('r',
                                    encoding = 'UTF-8'),
                    help = 'Input file (stdin not yet supported)')
parser.add_argument('-f',
                    '--format',
                    dest = 'fmt',
                    default = '%b %d %H:%M:%S',
                    help = 'Date input format')
parser.add_argument('-s',
                    '--start',
                    dest = 'start',
                    default = None,
                    help = 'Starting date : >=')
parser.add_argument('-e',
                    '--end',
                    dest = 'end',
                    default = None,
                    help = 'Ending date : <=')
parser.add_argument('-v',
                    dest = 'verbose',
                    action = 'store_true',
                    default = False,
                    help = 'Verbose mode')

args = parser.parse_args()
verbose = args.verbose
start = args.start
end = args.end
infile = args.infile
fmt = args.fmt

############### Start code ################

lines = tuple(infile)

# Use defaut values if start or end are undefined
if not start :
    start = lines[0][:14]

if not end :
    end = lines[-1][:14]

# Convert start and end to timestamp
start = mktime(strptime(start,
                        fmt))
end = mktime(strptime(end,
                      fmt))

# Extract matching lines
t1 = now()
dts = [(x, line) for x, line in [(mktime(strptime(line[:14 ],
                                                  fmt)),
                                  line) for line in lines] if start <= x <= end]
t2 = now()

# Print stats
if verbose :
    total_time = 'Total time'
    time_p_line = 'Time per line'
    n_lines = 'Number of lines'
    n_ext_lines = 'Number of extracted lines'

    print(f'{total_time:<25} : {((t2 - t1) * 1000)} ms')
    print(f'{time_p_line:<25} : {((t2 -t1) / len(lines) * 1000)} ms')
    print(f'{n_lines:<25} : {len(lines):,}')
    print(f'{n_ext_lines:<25} : {len(dts):,}')

# Print extracted lines
print(''.join([x[1] for x in dts]))

Чтобы разобратьaccess.logточно в указанном диапазоне , в данном случае только последние 10 минут (на основеEPOCHака количество секунд с 1970/01/01) :

Входной файл:

      172.16.0.3 - - [17/Feb/2023:17:48:41 +0200] "GET / HTTP/1.1" 200 123 "" "Mozilla/5.0 (compatible; Konqueror/2.2.2-2; Linux)"
172.16.0.4 - - [17/Feb/2023:17:25:41 +0200] "GET / HTTP/1.1" 200 123 "" "Mozilla/5.0 (compatible; Konqueror/2.2.2-2; Linux)"
172.16.0.5 - - [17/Feb/2023:17:15:41 +0200] "GET / HTTP/1.1" 200 123 "" "Mozilla/5.0 (compatible; Konqueror/2.2.2-2; Linux)"

Oneliner Perl:

С надежнымTime::Pieceпарсер времени, использующийstrptime()кpсвидание, иstrftime()кfформатировать новый. Этот модуль установлен вcore(по умолчанию) это не относится к ненадежнымDate::Parse

      $ perl -MTime::Piece -sne '
    BEGIN{
        my $t = localtime;
        our $now = $t->epoch;
        our $monthsRe = join "|", $t->mon_list;
    }
    m!\[(\d{2}/(?:$monthsRe)/\d{4}:\d{2}:\d{2}:\d{2})\s!;
    my $d = Time::Piece->strptime("$1", "%d/%b/%Y:%H:%M:%S");
    my $old = $d->strftime("%s");
    my $diff = (($now - $old) + $gap);
    if ($diff > $min and $diff < $max) {print}
' -- -gap=$({ echo -n "0"; date "+%:::z*3600"; } | bc) \
     -min=0 \
     -max=600 access.log

Объяснение аргументов: , , переключатели

  • -gapв$((7*3600))он же25200секунд, это разрыв с: +7 часов в секундах в моем текущем случае (тайскийTZ) ¹ переписано как{ echo -n "0"; date "+%:::z*3600"; } | bcесли у вас есть ГНУdate. Если нет, используйте другой способ установки зазора
  • -minмин. секунд с тех пор, как мы печатаем соответствующие строки журнала
  • -maxмаксимальное количество секунд, пока мы не напечатаем строку соответствия журнала
  • узнать разрыв отUTC, взгляните на:

¹

      $ LANG=C date
Fri Feb 17 15:50:13 +07 2023

The +07это разрыв.

Таким образом, вы можете фильтровать точно в диапазоне секунд с помощью этого фрагмента.

Пример вывода

      172.16.0.3 - - [17/Feb/2023:17:48:41 +0200] "GET / HTTP/1.1" 200 123 "" "Mozilla/5.0 (compatible; Konqueror/2.2.2-2; Linux)"
Другие вопросы по тегам