Лучший способ следить за журналом и выполнять команду, когда в журнале появляется текст


54

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

tail -f /path/to/serverLog | grep "server is up" ...(now, e.g., wget on server)?

Каков наилучший способ сделать это?


3
Обязательно используйте tail -Fдля обработки ротации журналов - то есть my.logстановится полным и перемещается, my.log.1и ваш процесс создает новыйmy.log
sg

Ответы:


34

Простой способ был бы awk.

tail -f /path/to/serverLog | awk '
                    /Printer is on fire!/ { system("shutdown -h now") }
                    /new USB high speed/  { system("echo \"New USB\" | mail admin") }'

И да, оба являются реальными сообщениями из журнала ядра. Perl может быть немного более элегантным в использовании для этого и может также заменить необходимость в tail. Если использовать Perl, он будет выглядеть примерно так:

open(my $fd, "<", "/path/to/serverLog") or die "Can't open log";
while(1) {
    if(eof $fd) {
        sleep 1;
        $fd->clearerr;
        next;
    }
    my $line = <$fd>;
    chomp($line);
    if($line =~ /Printer is on fire!/) {
        system("shutdown -h now");
    } elsif($line =~ /new USB high speed/) {
        system("echo \"New USB\" | mail admin");
    }
}

Мне нравится awkрешение быть коротким и легко сделать на лету в одну строку. Однако, если команда, которую я хочу выполнить, имеет кавычки, это может быть немного громоздко, есть ли альтернатива, может быть, использование конвейеров и составных команд, которые также допускают краткое однострочное решение, но не требуют передачи результирующей команды как строка?
Jonderry

1
@jon Вы можете написать awk как скрипт. Используйте "#! / Usr / bin awk -f" в качестве первой строки скрипта. Это исключит необходимость использования внешних одинарных кавычек в моем примере и освободит их для использования внутри system()команды.
penguin359

@ penguin359, правда, но все равно было бы интересно сделать это и из командной строки. В моем случае есть множество разных вещей, которые я хотел бы сделать, в том числе много вещей, которые я не могу предвидеть, поэтому удобно иметь возможность просто запустить сервер и сделать все это в одну строку.
Jonderry

Я нашел альтернативу, хотя я не знаю, насколько она tail -f /path/to/serverLog | grep "server is up" | head -1 && do_some_command
солидна

@jon Это кажется немного хрупким, используя голову таким образом. Что еще более важно, это не повторяется, как мои примеры. Если «сервер включен» находится в последних десяти строках журнала, он запустит команду и немедленно завершит работу. Если вы перезапустите его, он, скорее всего, сработает и выйдет снова, если в журнал не будут добавлены десять строк, не содержащих «сервер включен». Модификация, которая могла бы работать лучше, это то, tail -n 0 -f /path/to/serverLog что будет читать последние 0 строк файла, а затем ждать, пока будет напечатано больше строк.
penguin359

16

Если вы ищете только одну возможность и хотите оставаться в основном в оболочке вместо использования awkили perl, вы можете сделать что-то вроде:

tail -F /path/to/serverLog | 
grep --line-buffered 'server is up' | 
while read ; do my_command ; done

... который будет запускаться my_commandкаждый раз, когда " сервер включен " появляется в файле журнала. Для нескольких возможностей, вы можете отказаться от grepи вместо этого использовать caseвнутри while.

Столица -Fговорит tailследить за тем, чтобы файл журнала вращался; т.е. если текущий файл будет переименован, а другой файл с таким же именем займет его место, tailон переключится на новый файл.

--line-bufferedОпция указывает grepпромывать его буфер после каждой строки; в противном случае, my_commandможет не быть достигнуто своевременно (при условии, что журналы имеют линии разумного размера).


2
Мне очень нравится этот ответ, но сначала он мне не помог. Я думаю, что вам нужно добавить --line-bufferedпараметр grepили иным образом убедиться, что он сбрасывает вывод между строк: в противном случае он просто зависает и my_commandникогда не достигается. Если вы предпочитаете ack, у него есть --flushфлаг; если вы предпочитаете ag, попробуйте обернуть с stdbuf. stackoverflow.com/questions/28982518/…
doctaphred

Я попробовал это, используя do exit ;. Казалось, что он работает нормально, но tail никогда не заканчивался, и наш скрипт никогда не переходил на следующую строку. Есть ли способ остановить хвост в doразделе?
Махтын

14

На этот вопрос, похоже, уже дан ответ, но я думаю, что есть лучшее решение.

Вместо того, чтобы tail | whatever, я думаю, вы действительно хотите swatch. Swatch - это программа, разработанная специально для выполнения ваших задач, просмотра файла журнала и выполнения действий на основе строк журнала. Использование tail|fooпотребует, чтобы у вас был активно запущенный терминал для этого. Swatch, с другой стороны, работает как демон и всегда будет следить за вашими журналами. Swatch доступен во всех дистрибутивах Linux,

Я призываю вас попробовать это. Хотя вы можете вбить гвоздь обратной стороной отвертки, это не значит, что вы должны это делать.

Лучший 30-секундный учебник по образцу, который я смог найти, находится здесь: http://www.campin.net/newlogcheck.html


1
Этот урок теперь 404: /
отсюда


10

Странно, что никто не упомянул об multitailутилите, которая имеет эту функциональность из коробки. Один из примеров использования:

Показать вывод команды ping и, если он отображает тайм-аут, отправить сообщение всем пользователям, которые в данный момент вошли в систему

multitail -ex timeout "echo timeout | wall" -l "ping 192.168.0.1"

Смотрите также другие примеры из multitailиспользования.


+1, я понятия не имел, `у мультитейла такие навыки ниндзя были спрятаны. Спасибо что подметил это.
Калеб

8

может сделать работу сам

Давайте посмотрим, насколько просто и читабельно это может быть:

mylog() {
    echo >>/path/to/myscriptLog "$@"
}

while read line;do
    case "$line" in
        *"Printer on fire"* )
            mylog Halting immediately
            shutdown -h now
            ;;
        *DHCPREQUEST* )
            [[ "$line" =~ DHCPREQUEST\ for\ ([^\ ]*)\  ]]
            mylog Incomming or refresh for ${BASH_REMATCH[1]}
            $HOME/SomethingWithNewClient ${BASH_REMATCH[1]}
            ;;
        * )
            mylog "untrapped entry: $line"
            ;;
    esac
  done < <(tail -f /path/to/logfile)

Пока вы не используете bash regex, это может остаться очень быстро!

Но + - очень эффективный и интересный тандем

Но для сервера с высокой нагрузкой, и, как мне нравится, sedпотому что он очень быстрый и очень масштабируемый, я часто использую это:

while read event target lost ; do
    case $event in
        NEW )
            ip2int $target intTarget
            ((count[intTarget]++))
        ...

    esac
done < <(tail -f /path/logfile | sed -une '
  s/^.*New incom.*from ip \([0-9.]\+\) .*$/NEW \1/p;
  s/^.*Auth.*ip \([0-9.]\+\) failed./FAIL \1/p;
  ...
')

6

Вот так я и начал это делать, но стал более изощренным. Пара вещей, которые нужно учитывать:

  1. Если хвост журнала уже содержит «сервер включен».
  2. Автоматически заканчивая процесс хвоста, как только он найден.

Я использую что-то вроде этого:

RELEASE=/tmp/${RANDOM}$$
(
  trap 'false' 1
  trap "rm -f ${RELEASE}" 0
  while ! [ -s ${RELEASE} ]; do sleep 3; done
  # You can put code here if you want to do something
  # once the grep succeeds.
) & wait_pid=$!
tail --pid=${wait_pid} -F /path/to/serverLog \
| sed "1,10d" \
| grep "server is up" > ${RELEASE}

Он работает, удерживая tailоткрытым, пока ${RELEASE}файл не содержит данных.

Как только это grepудастся:

  1. записывает вывод на ${RELEASE}который будет
  2. прекратить ${wait_pid}процесс, чтобы
  3. выйти из tail

Примечание: sedможет быть более сложным, чтобы фактически определить количество строк, которые tailбудут производиться при запуске, и удалить это число. Но в целом это 10.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.