it-swarm.it

Il modo migliore per seguire un registro ed eseguire un comando quando viene visualizzato del testo nel registro

Ho un registro del server che genera una riga di testo specifica nel suo file di registro quando il server è attivo. Voglio eseguire un comando una volta che il server è attivo, e quindi fare qualcosa di simile al seguente:

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

Qual è il modo migliore per farlo?

56
jonderry

Un modo semplice sarebbe imbarazzante.

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

E sì, entrambi sono messaggi reali da un registro del kernel. Perl potrebbe essere un po 'più elegante da usare per questo e può anche sostituire la necessità di coda. Se si utilizza Perl, sarà simile a questo:

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");
    }
}
36
penguin359

Se stai cercando solo una possibilità e vuoi rimanere principalmente nella Shell piuttosto che usare awk o Perl, potresti fare qualcosa del tipo:

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

... che verrà eseguito my_command ogni volta che "il server è attivo" appare nel file di registro. Per più possibilità, potresti semplicemente rilasciare grep e invece utilizzare un case all'interno di while.

La maiuscola -F Dice a tail di controllare la rotazione del file di registro; ad es. se il file corrente viene rinominato e un altro file con lo stesso nome prende il suo posto, tail passerà al nuovo file.

L'opzione --line-buffered Dice a grep di svuotare il suo buffer dopo ogni riga; in caso contrario, my_command potrebbe non essere raggiunto in modo tempestivo (supponendo che i registri abbiano linee di dimensioni ragionevoli).

20
Jander

Questa domanda sembra già avere una risposta, ma penso che ci sia una soluzione migliore.

Piuttosto che tail | whatever, Penso che ciò che vuoi davvero sia swatch. Swatch è un programma progettato esplicitamente per fare ciò che stai chiedendo, guardando un file di registro ed eseguendo azioni basate su linee di registro. Utilizzando tail|foo richiederà che tu abbia un terminale attivo per farlo. Swatch invece funziona come un demone e guarderà sempre i tuoi log. Swatch è disponibile in tutte le distribuzioni Linux,

Ti incoraggio a provarlo. Mentre puoi battere un chiodo con la parte posteriore di un cacciavite non significa che dovresti.

Il miglior tutorial di 30 secondi sul campione che ho trovato è qui .

14
bahamat

È strano che nessuno abbia menzionato l'utilità multitail che ha questa funzionalità pronta all'uso. Uno degli esempi di utilizzo:

Mostra l'output di un comando ping e, se viene visualizzato un timeout, invia un messaggio a tutti gli utenti attualmente connessi

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

Vedi anche altri esempi dell'utilizzo di multitail.

11
php-coder

bash potrebbe fare il lavoro da solo

Vediamo quanto semplice e leggibile potrebbe essere:

mylog() {
    echo >>/path/to/myscriptLog "[email protected]"
}

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)

Anche se non usi regex di bash, questo potrebbe rimanere molto veloce!

Ma bash + sed è un tandem molto efficiente e interessante

Ma per server ad alto carico, e poiché mi piace sed perché è molto veloce e molto scalabile, lo uso spesso:

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;
  ...
')
8
F. Hauri

È così che ho iniziato a farlo anch'io, ma sono diventato molto più sofisticato. Un paio di cose di cui preoccuparsi:

  1. Se la coda del registro contiene già "il server è attivo".
  2. Termina automaticamente il processo di coda una volta trovato.

Uso qualcosa del genere:

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}

Funziona tenendo premuto tail fino a quando il file ${RELEASE} Contiene dati.

Una volta che grep ha avuto successo:

  1. scrive l'output su ${RELEASE} che lo farà
  2. termina il processo ${wait_pid} su
  3. esci da tail

Nota: sed può essere più sofisticato per determinare effettivamente il numero di righe che tail produrrà all'avvio e rimuovere quel numero. Ma generalmente sono 10.

6
nix