Najlepszy sposób na śledzenie dziennika i wykonanie polecenia, gdy w dzienniku pojawi się tekst

54

Mam dziennik serwera, który wyświetla określony wiersz tekstu w pliku dziennika, gdy serwer jest uruchomiony. Chcę wykonać polecenie po uruchomieniu serwera, a zatem wykonaj następujące czynności:

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

Jak najlepiej to zrobić?

Jerry
źródło
3
pamiętaj, aby użyć tail -Fdo obsługi rotacji kłód - tzn. my.logzapełnia się i przechodzi do, my.log.1a proces tworzy nowymy.log
sg

Odpowiedzi:

34

Prostym sposobem byłoby 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") }'

I tak, oba są prawdziwymi komunikatami z dziennika jądra. Perl może być nieco bardziej elegancki w użyciu do tego i może również zastąpić potrzebę ogona. Jeśli używasz perla, będzie on wyglądał mniej więcej tak:

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");
    }
}
penguin359
źródło
Podoba mi się to, że mogę awkbyć krótki i łatwy do zrobienia w locie w jednej linii. Jeśli jednak polecenie, które chcę uruchomić, zawiera cudzysłowy, może to być nieco kłopotliwe, czy istnieje alternatywa, być może użycie potoków i poleceń złożonych, które również pozwalają na krótkie rozwiązanie jednoliniowe, ale nie wymagają przekazania wynikowego polecenia jako sznurek?
poniedziałek
1
@jon Możesz napisać awk jako skrypt. Użyj „#! / Usr / bin awk -f” jako pierwszego wiersza skryptu. To wyeliminuje potrzebę stosowania zewnętrznych pojedynczych cudzysłowów w moim przykładzie i zwolni je do użycia wewnątrz system()polecenia.
penguin359
@ penguin359, Prawda, ale nadal byłoby interesujące zrobić to z wiersza poleceń. W moim przypadku jest wiele różnych rzeczy, które chciałbym zrobić, w tym wiele rzeczy, których nie mogę przewidzieć, więc wygodnie jest po prostu uruchomić serwer i zrobić to wszystko w jednym wierszu.
poniedziałek
Znalazłem alternatywę, choć nie wiem, jak to jest stałe:tail -f /path/to/serverLog | grep "server is up" | head -1 && do_some_command
jonderry
@jon To wydaje się trochę kruche, używając głowy w ten sposób. Co ważniejsze, nie jest to powtarzalne jak moje przykłady. Jeśli „serwer jest włączony” znajduje się w ostatnich dziesięciu wierszach dziennika, uruchomi polecenie i natychmiast zakończy działanie. Jeśli uruchomisz go ponownie, najprawdopodobniej zostanie uruchomiony i zakończy działanie, chyba że do dziennika zostanie dodanych dziesięć wierszy nie zawierających „serwer jest włączony”. Modyfikacją tego, która może działać lepiej, jest tail -n 0 -f /path/to/serverLog odczytanie ostatnich 0 wierszy pliku, a następnie poczekanie na wydrukowanie kolejnych wierszy.
penguin359 27.04.11
16

Jeśli szukasz tylko jednej możliwości i chcesz pozostać w powłoce, zamiast używać awklub perl, możesz zrobić coś takiego:

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

... który będzie działał za my_commandkażdym razem, gdy w pliku dziennika pojawi się „ serwer jest uruchomiony ”. Aby uzyskać wiele możliwości, możesz upuścić grepi zamiast tego użyć znaku casewewnątrz while.

Stolica -Fkaże tailuważać, aby plik dziennika został obrócony; tzn. jeśli nazwa bieżącego pliku zostanie zmieniona, a inny plik o tej samej nazwie tailzostanie zastąpiony , przełączy się na nowy plik.

--line-bufferedOpcja nakazuje grepopróżnić swój bufor po każdej linii; w przeciwnym razie my_commandmoże nie zostać osiągnięte w odpowiednim czasie (zakładając, że kłody mają linie o rozsądnych rozmiarach).

Jander
źródło
2
Naprawdę podoba mi się ta odpowiedź, ale na początku nie zadziałała. Myślę, że musisz dodać --line-bufferedopcję grep, lub w inny sposób upewnić się, że opróżnia swój wynik między wierszami: w przeciwnym razie po prostu zawiesza się i my_commandnigdy nie jest osiągany. Jeśli wolisz ack, ma --flushflagę; jeśli wolisz ag, spróbuj owinąć stdbuf. stackoverflow.com/questions/28982518/…
doctaphred,
Próbowałem tego, używając do exit ;. Wydawało się, że działa dobrze, ale ogon nigdy się nie skończył, a nasz skrypt nigdy nie przeszedł do następnego wiersza. Czy jest jakiś sposób, aby zatrzymać ogon w dosekcji?
Machtyn
14

Wydaje się, że na to pytanie już udzielono odpowiedzi, ale myślę, że istnieje lepsze rozwiązanie.

Zamiast tail | whatevertego myślę, że tak naprawdę chcesz swatch. Swatch to program zaprojektowany specjalnie do robienia tego, o co prosisz, oglądania pliku dziennika i wykonywania działań opartych na wierszach dziennika. Korzystanie z niego tail|foobędzie wymagało aktywnego terminalu, aby to zrobić. Z drugiej strony Swatch działa jako demon i zawsze będzie obserwował twoje dzienniki. Swatch jest dostępny we wszystkich dystrybucjach Linuksa,

Zachęcam do wypróbowania tego. Chociaż możesz wbić gwóźdź tylną stroną śrubokręta, nie oznacza to, że powinieneś.

Najlepszy 30-sekundowy samouczek na temat próbki, jaki udało mi się znaleźć, znajduje się tutaj: http://www.campin.net/newlogcheck.html

bahamat
źródło
1
Że poradnik jest 404 teraz: /
therefromhere
1
WebArchive jest Twoim przyjacielem: web.archive.org/web/20140922041805/http://campin.net/…
niedz.
10

Dziwne, że nikt nie wspomniał o multitailnarzędziu, które ma tę funkcjonalność po wyjęciu z pudełka. Jeden z przykładów użycia:

Pokaż wyniki polecenia ping i jeśli wyświetli się limit czasu, wyślij wiadomość do wszystkich zalogowanych użytkowników

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

Zobacz także kolejne przykłady z multitailużytkowania.

koder php
źródło
+1, nie miałem pojęcia, że ​​multitail miał schowane takie umiejętności ninja. Dzięki za zwrócenie na to uwagi.
Caleb
8

mógł wykonać tę pracę sam

Zobaczmy, jak proste i czytelne może być:

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)

Chociaż nie używasz bash regex, może to być bardzo szybkie!

Ale + to bardzo wydajny i interesujący tandem

Ale w przypadku serwera o wysokim obciążeniu i jak mi się podoba, sedponieważ jest bardzo szybki i bardzo skalowalny, często używam tego:

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;
  ...
')
F. Hauri
źródło
6

Tak też zacząłem to robić, ale stałem się o wiele bardziej wyrafinowany. Kilka rzeczy, którymi należy się martwić:

  1. Jeśli ogon dziennika zawiera już „serwer jest włączony”.
  2. Automatyczne kończenie procesu ogona po jego znalezieniu.

Używam czegoś takiego:

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}

Działa, trzymając tailotwarte, dopóki ${RELEASE}plik nie zawiera danych.

Gdy to się greppowiedzie:

  1. zapisuje dane wyjściowe, do ${RELEASE}których będzie
  2. zakończyć ${wait_pid}proces do
  3. wyjdź z tail

Uwaga: sedMogą być bardziej wyrafinowane, aby faktycznie określić liczbę linii, które tailzostaną wygenerowane podczas uruchamiania i usuń tę liczbę. Ale ogólnie jest 10.

nic
źródło