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


54

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 del tipo:

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

Qual è il modo migliore per farlo?


3
assicurati di utilizzare tail -Fper gestire la rotazione del registro - ovvero my.logdiventa pieno e si sposta my.log.1e il tuo processo crea un nuovomy.log
sg

Risposte:


34

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

Mi piace la awksoluzione per essere breve e facile da fare al volo in una riga. Tuttavia, se il comando che voglio eseguire ha delle virgolette, questo può essere un po 'ingombrante, c'è un'alternativa, forse usando pipeline e comandi composti che consente anche una breve soluzione di una riga ma non richiede il comando risultante per essere passato come una stringa?
jonderry,

1
@jon Puoi scrivere awk come script. Usa "#! / Usr / bin awk -f" come prima riga dello script. Questo eliminerà la necessità delle virgolette singole esterne nel mio esempio e le libererà per l'uso all'interno di un system()comando.
penguin359,

@ penguin359, True, ma sarebbe comunque interessante farlo anche dalla riga di comando. Nel mio caso, ci sono una varietà di cose diverse che vorrei fare tra cui molte cose che non posso prevedere, quindi è conveniente essere in grado di avviare il server e fare tutto in una riga.
jonderry,

Ho trovato un'alternativa, anche se non so quanto sia solido:tail -f /path/to/serverLog | grep "server is up" | head -1 && do_some_command
jonderry,

@jon Sembra un po 'fragile usare la testa in quel modo. Ancora più importante, non è ripetibile come i miei esempi. Se "server è attivo" si trova nelle ultime dieci righe del registro, esso attiverà il comando e uscirà immediatamente. Se lo riavvii, molto probabilmente si accenderà e uscirà di nuovo a meno che nel registro non siano state aggiunte dieci righe che non contengono "server". Una modifica di ciò che potrebbe funzionare meglio è tail -n 0 -f /path/to/serverLog che leggerà le ultime 0 righe del file, quindi attenderà che vengano stampate più righe.
penguin359,

16

Se stai solo cercando una possibilità e vuoi rimanere principalmente nella shell piuttosto che usare awko 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_commandogni volta che " server è attivo " appare nel file di registro. Per più possibilità, potresti semplicemente rilasciare grepe invece utilizzare un caseall'interno di while.

La capitale -Fdice taildi controllare che il file di registro venga ruotato; cioè se il file corrente viene rinominato e un altro file con lo stesso nome prende il suo posto, tailpasserà al nuovo file.

L' --line-bufferedopzione dice grepdi svuotare il suo buffer dopo ogni riga; in caso contrario, my_commandpotrebbe non essere possibile raggiungere tempestivamente (supponendo che i registri abbiano linee di dimensioni ragionevoli).


2
Mi piace molto questa risposta, ma all'inizio non ha funzionato per me. Penso che sia necessario aggiungere l' --line-bufferedopzione a grep, o altrimenti assicurarsi che svuota il suo output tra le righe: altrimenti, si blocca e my_commandnon viene mai raggiunto. Se preferisci ack, ha una --flushbandiera; se preferisci ag, prova a avvolgere con stdbuf. stackoverflow.com/questions/28982518/...
doctaphred

Ho provato questo, usando do exit ;. Sembrava funzionare bene, ma la coda non è mai finita e la nostra sceneggiatura non è mai passata alla riga successiva. C'è un modo per fermare la coda nella dosezione?
Machtyn,

14

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. L'utilizzo tail|foorichiede che tu abbia un terminale in esecuzione attivamente 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 su swatch che ho trovato è qui: http://www.campin.net/newlogcheck.html


1
Quel tutorial ora è 404: /
da


10

È strano che nessuno abbia menzionato l' multitailutilità 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 di multitailutilizzo.


+1, non avevo idea che il multitail avesse nascosto quel tipo di abilità ninja. Grazie per la segnalazione.
Caleb,

8

poteva fare il lavoro da solo

Vediamo quanto semplice e leggibile potrebbe essere:

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)

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

Ma + è un tandem molto efficiente e interessante

Ma per server ad alto carico, e come mi piace sedperché è 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;
  ...
')

6

È così che ho iniziato a fare anche questo, 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 tailaperto finché il ${RELEASE}file non contiene dati.

Una volta che ci grepriesce:

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

Nota: sedpuò essere più sofisticato per determinare effettivamente il numero di righe tailprodotte all'avvio e rimuovere quel numero. Ma generalmente sono 10.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.