Вопрос или проблема
У меня есть журнал логов от сервиса, который никогда не ротировался. Теперь я хочу разделить этот журнал логов на отдельные файлы, по одному на каждый месяц. Большинство строк начинаются с unix-времени, заключённого в скобки, однако есть лог-сообщения, занимающие несколько строк (вывод из dig
), которые также нужно захватить. Кроме того, следующая строка с временной меткой после многострочного сообщения не обязательно принадлежит тому же месяцу. Как в примере ниже.
1700653509 = Ср 22 Ноя 12:45:09 CET 2023
1700798246 = Пт 24 Ноя 04:57:26 CET 2023
1701385200 = Пт 1 Дек 00:00:00 CET 2023
[1700653509] unbound[499:0] debug: module config: "subnetcache validator iterator"
[1700798246] unbound[1506:0] info: incoming scrubbed packet: ;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 0
;; flags: qr aa ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
chat.cdn.whatsapp.net. IN A
;; ANSWER SECTION:
chat.cdn.whatsapp.net. 60 IN A 157.240.252.61
;; AUTHORITY SECTION:
;; ADDITIONAL SECTION:
;; MSG SIZE rcvd: 55
[1701385200] unbound[1506:0] debug: iter_handle processing q with state QUERY RESPONSE STATE
Мой первый подход заключался в том, чтобы определить минимальные и максимальные значения (первая и последняя секунда месяца) и проверить, попадает ли временная метка в этот диапазон. Если да, записать её в новый журнал логов и продолжить. Мне нужен этот подход, так как не каждая первая или последняя секунда месяца присутствует в журнале логов.
Пример:
for YEAR in {2023..2024}; do
for MONTH in {1..12}; do
# Рассчитать первую и последнюю секунду каждого месяца
FIRST_SECOND="$(date -d "$(date +"$YEAR"/"$MONTH"/01)" "+%s")"
LAST_SECOND="$(date -d "$(date +"$YEAR"/"$MONTH"/01) + 1 month - 1 second" "+%s")"`
awk -F'[\\[\\]]' -v MIN="${FIRST_SECOND}" -v MAX="${LAST_SECOND}" '{if($2 >= MIN && $2 <= MAX) print}' unbound.log >> "unbound-$YEAR-$MONTH.log
done;
done
Затем я столкнулся с многострочными сообщениями и наткнулся на препятствие.
В основном, что мне нужно сейчас, это своего рода “захватить все соответствующие и несоответствующие строки, пока не дойдёшь до первого значения, превышающего MAX”. Я подумал о том, чтобы получить номера первой и последней подходящей строки и просто использовать их. Но тогда у меня снова возникает та же проблема с многострочными сообщениями.
Есть идеи?
С помощью GNU awk
(для strftime
):
awk '
$1 ~ /^\[[0-9]+\]$/ {
f = "unbound-" strftime("%m-%Y", substr($1, 2, length($1)-2)) ".log"
}
{
print > f
}' unbound.log
Для каждой строки, где первое поле – это [временная метка]
(то есть соответствует регулярному выражению ^\[[0-9]+\]$
), мы используем substr
и length
, чтобы извлечь временную метку
, strftime
, чтобы преобразовать её в строку mm-YYYY
и присваиваем "unbound-mm-YYYY.log"
переменной f
. Во втором блоке, который применяется ко всем строкам, мы печатаем текущую строку в файл f
. Примечание: в отличие от перенаправлений в оболочке, в awk
print > FILE
дописывает в FILE
.
Ответ или решение
Для решения вашей задачи по разделению лог-файла на отдельные файлы для каждого месяца с учетом превосходящих временных меток, давайте проанализируем предложенное вами решение и рассмотрим подход с использованием awk
.
Задача
У вас есть лог-файл, который не подвергался ротации и содержит много строк с временными отметками в секундах в квадратных скобках. Вам требуется извлечь строки, начиная с первой строки, соответствующей минимальному значению времени до последней строки, где временная метка не превышает максимальное значение.
Пошаговое решение
1. Определение временных интервалов
Вам необходимо будет определить первый и последний момент времени для каждого месяца. Это можно сделать с помощью утилиты date
.
for YEAR in {2023..2024}; do
for MONTH in {1..12}; do
FIRST_SECOND="$(date -d "$(date +"$YEAR"/"$MONTH"/01)" "+%s")"
LAST_SECOND="$(date -d "$(date +"$YEAR"/"$MONTH"/01) + 1 month - 1 second" "+%s")"
2. Обработка лог-файла с использованием awk
Следующий код awk
позволит обработать лог-файл с учетом многострочных сообщений:
awk -v MIN="$FIRST_SECOND" -v MAX="$LAST_SECOND" '
# Если текущая строка является временной меткой
$0 ~ /^\[[0-9]+\]/ {
timestamp = substr($0, 2, length($0) - 2)
# Проверяем, попадает ли временная метка в заданный диапазон
if (timestamp >= MIN && timestamp <= MAX) {
# Указываем, что мы находимся в пределах нужного месяца
inMonth = 1
} else if (inMonth && timestamp > MAX) {
# Если временная метка превышает MAX и мы уже были в месяце, выходим
exit
}
}
# Печатаем каждую строку, если мы находимся в нужном месяце
inMonth { print }
' unbound.log > "unbound-$YEAR-$MONTH.log"
3. Обработка многострочных логов
При необходимости выделить многострочные сообщения, логику можно немного адаптировать:
awk -v MIN="$FIRST_SECOND" -v MAX="$LAST_SECOND" '
{
# Проверяем условие для каждой строки
if ($0 ~ /^\[[0-9]+\]/) {
timestamp = substr($0, 2, length($0) - 2)
if (timestamp >= MIN && timestamp <= MAX) {
inMonth = 1 # Мы в пределах этого месяца
} else if (inMonth && timestamp > MAX) {
inMonth = 0 # Выходим если превышено
exit
}
}
# Печатаем строки, если мы читаем нужный месяц
if (inMonth) {
print
}
}
' unbound.log > "unbound-$YEAR-$MONTH.log"
Заключение
Таким образом, предложенный подход позволяет надежно разделить лог-файлы по месяцам, учитывая многострочные сообщения. Используя awk
и переменные для минимальных и максимальных значений, вы сможете извлечь необходимые временные метки, а также соответствующие сообщения.
Данный алгоритм разработан с учетом особенностей логирования и специфики формата вашего файла. Не забудьте протестировать полученное решение на небольших фрагментах лог-файла, чтобы гарантировать правильность обработки и избежать потери данных.