Как получить из лога данные за последнее %date% время?

Есть система на Linux. В ней есть лог. Внутри этого лога есть записи в формате:

2020-04-24 14:00:58.870+0300:Some log
2020-04-24 14:00:58.872+0300:Some another log
2020-04-24 14:00:58.891+0300:And another

Надо из этого лога получить записи за последние 10 минут, например (иногда будет необходимость брать за последние несколько секунд). Проблема в том, что лог может быть разбит на несколько строк, например:

2020-04-24 14:00:58.870+0300:Some log
2020-04-24 14:00:58.872+0300:Some
another
log
2020-04-24 14:00:58.891+0300:And another


Что я пробовал:

sed -n "/^$(date --date='10 min ago' '+%Y-%m-%d %H:%M:%S')/,\$p" log.log

Вариант хороший, но делает не то, что надо (ничего не найдётся, если не было логов в ту же секунду, когда и вызвали команду).

awk -v from_date="$(date --date='10 min ago' '+%Y-%m-%d %H:%M:%S')" -v to_date="date '+%Y-%m-%d %H:%M:%S'" '$0 > from_date && $0 < to_date || $0 ~ to_date' log.log

Этот вариант лучше, делает то, что надо, но ломается из-за того, что некоторые логи разбиты на несколько строк.


Как я вижу решение задачи:
1. Высчитать дату, от которой нужны логи %date% = date --date='10 min ago' '+%Y-%m-%d %H:%M:%S'
2. Идти по %строка% в логе с последней строки вверх
2.1. Если начало %строка% совпадает с регуляркой \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}
2.1.1. То
2.1.1.1. Если %строка% > %date% (в лексикографическом порядке), то вывести все пройденные строки; оборвать цикл.

Но, т.к. в bash не силён, то не понимаю, как это сделать.


Мои наброски алгоритма:

d=$(date --date='10 min ago' '+%Y-%m-%d %H:%M:%S'); tac log.log | (while read -r line; do ([[ $line =~ ([0-9]{4}-[0-9]{2}-[0-9]{2}.*) ]] && [[ "$line" > "$d" ]] && break) || echo $line; done;) | tac -

Но в таком случае выводится весь лог.

d=$(date --date='10 min ago' '+%Y-%m-%d %H:%M:%S'); tac log.log | (while read -r line; do ([[ $line =~ ([0-9]{4}-[0-9]{2}-[0-9]{2}.*) && $d > $line ]] && (break)) || echo $line; done;) | tac -

В данном случае выбираются действительно нужные даты, но не срабатывает break и выводятся "старшие" логи, которые не начинаются с даты. Также это работает очень долго, т.к., думаю, лог читается полностью.


Ограничения:

К системе есть доступ только на чтение. Т.е. нет возможности закидывать туда какие-то файлы, устанавливать какие-то утилиты, конфигурировать функционал имеющихся утилит.


Ответы (2 шт):

Автор решения: Ainar-G

Используя GNU AWK можно сделать так:

BEGIN {
    from_date = strftime("%Y-%m-%d %H:%M:%S", systime() - 10 * 60);
}

{
    if (/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/) {
        date = substr($0, 1, 19);
        if (date > from_date) {
            print;
        } else {
            nextfile;
        }
    } else {
        print;
    }
}
→ Ссылка
Автор решения: sanmai

Если проблему переформулировать как получение просто последних добавленных строк в лог-файле, без учёта формата времени и так далее, то всю задачу можно решить с помощью diff так:

tail -n 1000 error.log > error.log.new
touch error.log.old
diff --unchanged-line-format= --old-line-format= --new-line-format='%L' error.log.old error.log.new
mv error.log.new error.log.old

Что мы здесь делаем:

  • Берем последние 1000 строк из актуального лога.
  • Создаём файл старого лога, если тот не был создан.
  • Сравниваем файл со старыми 1000 строк с файлом с новыми, показывая только добавленные строки.
  • Заменяем файл старого лога новым.

Вывод из этого файла можно подать в другую программу для передачи, например, в общий чат в Слаке или где ещё.

Даже если к системе есть доступ только на чтение, файлы старого-нового конца логов можно положить в каталог в /tmp или /var/tmp.

→ Ссылка