Найкращий спосіб слідкувати за журналом та виконувати команду, коли в журналі з'являється якийсь текст


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

Простий спосіб був би дивним.

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 може бути трохи елегантнішим для цього, а також може замінити потребу в хвості. Якщо ви використовуєте 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рішення є коротким і легким для виконання на льоту в один рядок. Однак, якщо команда, яку я хочу запустити, має лапки, це може бути трохи громіздко, чи є альтернатива, можливо, використовуючи конвеєри та складені команди, які також дозволяють коротке рішення з одноланцюговим рішенням, але не вимагає передачі отриманої команди як струна?
веселощі

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

@ penguin359, правда, але все ж було б цікаво це зробити і з командного рядка. У моєму випадку, я хочу зробити багато різних речей, включаючи багато речей, яких я не можу передбачити, тому зручно мати можливість лише запустити сервер і зробити все це в один рядок.
веселощі

Я знайшов альтернативу, хоча не знаю, наскільки це 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 ;. Здавалося, він працює добре, але хвіст так і не закінчився, і наш сценарій ніколи не перейшов на наступний рядок. Чи є спосіб зупинити хвіст в doрозрізі?
Махтін

14

Здається, на це питання вже відповіли, але я думаю, що є кращого рішення.

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

Я закликаю вас спробувати це. Хоча ви можете забити цвях заднім боком викрутки, це не означає, що вам слід.

Найкращий підручник із зразок 30 секунд, який я міг знайти, тут: http://www.campin.net/newlogcheck.html


1
Цей підручник зараз 404: /
звідти

1
Веб - архів є вашим другом: web.archive.org/web/20140922041805/http: //campin.net / ...
Нд

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's 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.