Как получить из лога данные за последнее %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 шт):
Используя 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;
}
}
Если проблему переформулировать как получение просто последних добавленных строк в лог-файле, без учёта формата времени и так далее, то всю задачу можно решить с помощью 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.