awk: разделить файл журнала, начиная с первой строки, соответствующей минимальному значению, и до последней строки, перед тем как будет превышено максимальное значение.

Вопрос или проблема

У меня есть журнал логов от сервиса, который никогда не ротировался. Теперь я хочу разделить этот журнал логов на отдельные файлы, по одному на каждый месяц. Большинство строк начинаются с 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 и переменные для минимальных и максимальных значений, вы сможете извлечь необходимые временные метки, а также соответствующие сообщения.

Данный алгоритм разработан с учетом особенностей логирования и специфики формата вашего файла. Не забудьте протестировать полученное решение на небольших фрагментах лог-файла, чтобы гарантировать правильность обработки и избежать потери данных.

Оцените материал
Добавить комментарий

Капча загружается...