Вопрос или проблема
У меня есть большой файл журнала access.log с записями, например:
192.11.111.111 - - [05/Mar/2021:00:00:02 +0100] "GET ..."
192.250.14.80 - - [05/Mar/2021:00:00:09 +0100] "GET ..."
12.249.66.42 - - [05/Mar/2021:00:00:13 +0100] "GET ..."
Как я могу получить/отфильтровать записи только за последний час?
#!/bin/bash
age() { python -c '
import sys
from datetime import datetime
print(int((datetime.now() - datetime.strptime(sys.argv[1], sys.argv[2])).seconds))
' "$@"
}
tac access.log | while IFS= read line; do
date=$(cut -d' ' -f4 <<< "$line")
age=$(age "$date" "[%d/%b/%Y:%H:%M:%S")
[ $age -gt 3600 ] && break
printf '%s\n' "$line"
done
Объяснение:
- Создать функцию для получения возраста строки даты, для этого я использую
python
datetime
модуль. - Затем перебрать строки файла в обратном порядке с помощью
tac log | while ...
- Получить строку даты с помощью
cut
.- нужно удалить
-d' '
, если ваш разделитель – табуляция вместо пробела - или использовать
awk '{print $4}' <<< "$line"
вместо этого - или использовать
read ip some thing date tz else <<< "$line"
).
- нужно удалить
- Получить возраст с помощью функции, определенной на первом шаге
- Когда секунды > 3600 (=1 час), остановить цикл (
break
) - В противном случае, напечатать строку (или сделать что угодно)
Некоторые дополнительные заметки:
-
Поскольку я игнорирую часовой пояс (+0100), это предполагает, что мы находимся в том же часовом поясе, что и файл журнала. Если у вас есть файлы журналов из других часовых поясов, вам, возможно, потребуется улучшить функцию
age
. Смотрите здесь и читайте дату какcut -d' ' -f4,5
. -
Вы, очевидно, можете сделать всё это с помощью
python
, однако чтение файла в обратном порядке, не читая весь файл кажется не таким уж простым, и мне просто нравитсяtac
. -
Вы можете использовать
date
иbc
или что-то подобное для вычисления возраста (как это сделано здесь), но поскольку ваш формат даты не поддерживается “из коробки”, это будет трудно. ИМХО,datetime.strptime
идеально подходит для этого. -
Хорошая вещь в функции
age
: она очень повторно используема для любых видов задач. Просто вызовите её так же, какstrptime
может её прочитать и она документирована здесь.age "date_string" "format"
Сначала извлеките строки даты/времени из квадратных скобок:
$ awk -F'[][]' '{print $2}' access.log
05/Mar/2021:00:00:02 +0100
05/Mar/2021:00:00:09 +0100
05/Mar/2021:00:00:13 +0100
Используйте пару команд sed, чтобы сделать их подходящими для разбора командой date:
$ awk -F'[][]' '{print $2}' access.log | sed 's/:/ /' | sed 's|\/| |g'
05 Mar 2021 00:00:02 +0100
05 Mar 2021 00:00:09 +0100
05 Mar 2021 00:00:13 +0100
Теперь прочитайте каждую строку и преобразуйте их в секунды:
$ awk -F'[][]' '{print $2}' access.log | sed 's/:/ /' | sed 's|\/| |g' | while read seconds; do date -d "
$seconds" +%s; done
1614898802
1614898809
1614898813
Текущая дата в секундах:
$ date +%s
1718689281
Найти разницу между этим последним числом и числами вывода тривиально, и я оставлю эту часть вам.
С помощью perl
:
perl -MTime::Piece -ne '
BEGIN {$t = time - 3600}
print if m{\[\d+/.*?\]} &&
Time::Piece->strptime($&, "[%d/%b/%Y:%H:%M:%S %z]") >= $t' access.log
В журналах доступа веб-сервера обычно временная метка обозначает, когда запрос был получен от клиента, но строка журнала добавляется, когда ответ полностью сгенерирован и передан (что может занять произвольное время), поэтому строки не обязательно располагаются в хронологическом порядке, и вы не можете предполагать, что все строки после одной из менее чем часа назад также будут менее часа назад, и вам нужно разбирать временные метки во всех строках.
Поскольку временные метки находятся в местном времени (как указывают те смещения часового пояса +0100) и не в UTC, вы не можете просто произвести простое лексикографическое сравнение (после преобразования сокращений месяц в числа), так как это будет сбой около изменений перехода на летнее/зимнее время, поэтому нужно сделать преобразование обратно в момент времени, как мы делаем это здесь с Time::Piece
‘s strptime()
.
Чтобы найти временную метку в строке, мы ищем первый [<цифры>/<что угодно как можно короче>]
вместо просто [<что угодно как можно короче>]
(m{\[.*?\]}
), на случай если поле удаленного хоста, например, может быть IPv6-адресом, представленным как [hhhh:....]
.
Ответ или решение
Как отфильтровать записи последнего часа из файла журнала
Ведение журнала является важной частью управления сервером и анализа сетевой активности, и у вас есть задача отфильтровать записи из файла access.log
, чтобы показать только те, которые были созданы в течение последнего часа. Рассмотрим несколько подходов к этой задаче, используя различные инструменты и языки программирования.
1. Использование Bash и Python
Благодаря простоте и доступности командной оболочки Bash можно создать скрипт для выполнения данной задачи. Ниже представлен алгоритм, который использует Python для расчета возраста временных меток.
#!/bin/bash
# Функция для вычисления возраста временной метки
age() { python -c '
import sys
from datetime import datetime
print(int((datetime.now() - datetime.strptime(sys.argv[1], sys.argv[2])).seconds))
' "$@"
}
# Чтение файла с конца и фильтрация записей
tac access.log | while IFS= read line; do
date=$(echo "$line" | cut -d' ' -f4)
age=$(age "$date" "[%d/%b/%Y:%H:%M:%S")
[ $age -gt 3600 ] && break
printf '%s\n' "$line"
done
Объяснение работы скрипта:
- Функция age: Используется для вычисления разницы во времени между текущим моментом и указанной временной меткой. Библиотека
datetime
в Python позволяет легко обрабатывать временные данные. - Чтение файла с конца: Команда
tac
переворачивает порядок строк, что позволяет быстрее находить записи последнего часа. - Получение временной метки: С помощью
cut
извлекаем дату и время из каждой строки журнала. - Фильтрация: Если возраст больше одного часа (3600 секунд), мы прерываем цикл и не выводим дальнейшие строки.
2. Использование AWK и sed
Другой способ — воспользоваться мощью AWK и sed для обработки текста. Это может быть полезно, если вы хотите избежать использования Python.
awk -F'[][]' '{print $2}' access.log | sed 's/:/ /' | sed 's|\/| |g' | while read seconds; do
date -d "$seconds" +%s
done
Объяснение:
- Извлечение временных меток: Используя
awk
, мы извлекаем только временные метки из журнала и с помощьюsed
подготавливаем их для дальнейшей обработки. - Преобразование в секунды: Каждая временная метка преобразуется в формат секунд с использованием команды
date
.
3. Использование Perl
Можно также использовать Perl, который может быть более эффективным при работе с текстовыми данными, как показано ниже:
perl -MTime::Piece -ne '
BEGIN {$t = time - 3600}
print if m{\[\d+/.*?\]} && Time::Piece->strptime($&, "[%d/%b/%Y:%H:%M:%S %z]") >= $t' access.log
Объяснение:
- Импорт модуля Time::Piece: Модуль позволяет легко парсить временные метки и сравнивать их с текущим временем.
- Начальное время: Устанавливаем метку времени с учетом последнего часа для фильтрации.
- Печать строк: Для каждой строки проверяется, соответствует ли временная метка критериям, и если да, строка выводится.
Заключение
Существуют различные способы фильтрации записей журнала за последний час, и выбор подхода зависит от ваших предпочтений и требований к производительности. Использование Bash и Python обеспечит удобство и функциональность, в то время как AWK и Perl могут предложить более мощные инструменты для текстовой обработки. Всегда помните о косвенном влиянии часовых поясов при обработке временных меток, чтобы избежать ошибок в интерпретации данных.