A melhor maneira de seguir um log e executar um comando quando algum texto aparecer no log

54

Eu tenho um log do servidor que gera uma linha específica de texto em seu arquivo de log quando o servidor está ativo. Quero executar um comando quando o servidor estiver ativo e, portanto, faça algo como o seguinte:

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

Qual é a melhor maneira de fazer isso?

jonderry
fonte
3
Tenha certeza de usar tail -Fa rotação de log punho - ou seja, my.logtorna-se completa e se move para my.log.1e seu processo cria um novomy.log
sg

Respostas:

34

Uma maneira simples seria estranha.

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 sim, ambas são mensagens reais de um log do kernel. Perl pode ser um pouco mais elegante de usar e também pode substituir a necessidade de cauda. Se estiver usando perl, será algo parecido com isto:

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
fonte
Gosto da awksolução por ser curta e fácil de fazer em tempo real em uma linha. No entanto, se o comando que eu quero executar possui aspas, isso pode ser um pouco complicado, existe uma alternativa, talvez usando pipelines e comandos compostos que também permitam uma solução rápida de uma linha, mas não exijam que o comando resultante seja passado como uma corda?
precisa saber é o seguinte
11
@ jon Você pode escrever o awk como um script. Use "#! / Usr / bin awk -f" como a primeira linha do script. Isso eliminará a necessidade de aspas simples externas no meu exemplo e as libertará para uso dentro de um system()comando.
precisa saber é o seguinte
@ penguin359, Verdade, mas ainda assim seria interessante fazê-lo a partir da linha de comando. No meu caso, há uma variedade de coisas diferentes que eu gostaria de fazer, incluindo muitas coisas que não posso prever, por isso é conveniente poder iniciar o servidor e fazer tudo em uma linha.
precisa saber é o seguinte
Eu encontrei uma alternativa, embora eu não sei quão sólida é:tail -f /path/to/serverLog | grep "server is up" | head -1 && do_some_command
jonderry
@ Jon Isso parece um pouco frágil usando a cabeça dessa maneira. Mais importante, não é repetível como meus exemplos. Se o "servidor estiver ativo" estiver nas últimas dez linhas do log, ele disparará o comando e sairá imediatamente. Se você reiniciá-lo, provavelmente será acionado e sairá novamente, a menos que dez linhas que não contêm "o servidor esteja ativo" tenham sido adicionadas ao log. Uma modificação daquilo que pode funcionar melhor é tail -n 0 -f /path/to/serverLog Que lê as últimas 0 linhas do arquivo e aguarda a impressão de mais linhas.
precisa saber é o seguinte
16

Se você está procurando apenas uma possibilidade e deseja permanecer principalmente no shell, em vez de usar awkou perl, pode fazer algo como:

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

... que será executado my_commandsempre que "o servidor estiver ativo " aparecer no arquivo de log. Para várias possibilidades, talvez você possa largar o grepe, em vez disso, usar um casedentro do while.

A capital -Fdiz tailpara observar o arquivo de log ser girado; ou seja, se o arquivo atual for renomeado e outro arquivo com o mesmo nome substituir, ele tailpassará para o novo arquivo.

A --line-bufferedopção diz greppara liberar seu buffer após cada linha; caso contrário, my_commandpoderá não ser alcançado em tempo hábil (assumindo que os logs tenham linhas de tamanho razoável).

Jander
fonte
2
Eu realmente gosto desta resposta, mas não funcionou para mim no começo. Eu acho que você precisa adicionar a --line-bufferedopção grepou garantir que ela libere sua saída entre linhas: caso contrário, ela simplesmente trava e my_commandnunca é alcançada. Se você preferir ack, tem uma --flushbandeira; se preferir ag, tente encerrar com stdbuf. stackoverflow.com/questions/28982518/…
documentou
Eu tentei isso, usando do exit ;. Pareceu funcionar bem, mas a cauda nunca terminou e nosso script nunca passou para a próxima linha. Existe uma maneira de parar a cauda na doseção?
Machtyn 28/02
14

Esta pergunta parece já ter sido respondida, mas acho que há uma solução melhor.

Em vez disso tail | whatever, acho que você realmente quer swatch. O Swatch é um programa projetado explicitamente para fazer o que você está pedindo, assistindo a um arquivo de log e executando ações com base nas linhas de log. O uso tail|fooexigirá que você tenha um terminal em execução ativamente para fazer isso. Por outro lado, a amostra é executada como um daemon e sempre estará observando seus registros. O Swatch está disponível em todas as distribuições Linux,

Encorajo-vos a experimentar. Embora você possa pregar um prego na parte traseira de uma chave de fenda, isso não significa que você deva.

O melhor tutorial de 30 segundos sobre amostras que pude encontrar está aqui: http://www.campin.net/newlogcheck.html

bahamat
fonte
11
Esse tutorial é 404 agora: /
daí
11
O WebArchive é seu amigo: web.archive.org/web/20140922041805/http://campin.net/…
Sun Dom
10

É estranho que ninguém tenha mencionado sobre o multitailutilitário que possui essa funcionalidade pronta para uso. Um exemplo de uso:

Mostre a saída de um comando ping e, se ele exibir um tempo limite, envie uma mensagem para todos os usuários atualmente conectados

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

Veja também outros exemplos de multitailuso.

codificador php
fonte
+1, eu não fazia ideia de que o multitail tinha esse tipo de habilidade ninja escondido. Obrigado por apontar isso.
Caleb
8

poderia fazer o trabalho sozinho

Vamos ver como pode ser simples e legível:

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)

Enquanto você não usa o bash regex, isso pode ficar muito rápido!

Mas o + é um conjunto muito eficiente e interessante

Mas, para servidores de alta carga, e como eu gosto, sedporque é muito rápido e muito escalável, costumo usar isso:

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
fonte
6

Foi assim que eu comecei a fazer isso também, mas ficou muito mais sofisticado com isso. Algumas coisas para se preocupar:

  1. Se a cauda do log já contiver "o servidor está ativo".
  2. Finalizando automaticamente o processo de cauda assim que for encontrado.

Eu uso algo na mesma linha:

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}

Funciona mantendo tailaberto até que o ${RELEASE}arquivo contenha dados.

Depois que o grepconseguir:

  1. grava a saída na ${RELEASE}qual será
  2. encerrar o ${wait_pid}processo para
  3. saia do tail

Nota: sedPode ser mais sofisticado para determinar realmente o número de linhas que tailserão produzidas na inicialização e remover esse número. Mas geralmente são 10.

nix
fonte