Фильтрация записей файла журнала на основе диапазона дат
У моего сервера необычно высокая загрузка ЦП, и я вижу, что 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)"