La mejor manera de seguir un registro y ejecutar un comando cuando aparece texto en el registro

54

Tengo un registro del servidor que genera una línea de texto específica en su archivo de registro cuando el servidor está activo. Quiero ejecutar un comando una vez que el servidor está activo y, por lo tanto, hacer algo como lo siguiente:

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

¿Cuál es la mejor manera de hacer esto?

jonderry
fuente
3
asegúrese de usar tail -Fpara manejar la rotación de registros, es decir, my.logse llena y avanza my.log.1y su proceso crea un nuevomy.log
sg

Respuestas:

34

Una manera simple sería 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") }'

Y sí, ambos son mensajes reales de un registro de kernel. Perl podría ser un poco más elegante de usar para esto y también puede reemplazar la necesidad de cola. Si usa perl, se verá más o menos así:

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");
    }
}
pingüino359
fuente
Me gusta la awksolución por ser corto y fácil de hacer sobre la marcha en una línea. Sin embargo, si el comando que quiero ejecutar tiene comillas, esto puede ser un poco engorroso, ¿hay alguna alternativa, tal vez usando tuberías y comandos compuestos que también permitan una breve solución de una línea pero no requieran que se pase el comando resultante? como una cuerda?
jonderry
1
@ Jon Puedes escribir awk como script. Use "#! / Usr / bin awk -f" como la primera línea del script. Esto eliminará la necesidad de las comillas simples externas en mi ejemplo y las liberará para su uso dentro de un system()comando.
penguin359
@ penguin359, Verdadero, pero también sería interesante hacerlo desde la línea de comandos. En mi caso, hay una variedad de cosas diferentes que me gustaría hacer, incluidas muchas cosas que no puedo prever, por lo que es conveniente poder iniciar el servidor y hacerlo todo en una sola línea.
jonderry
Encontré una alternativa, aunque no sé cuán sólida es:tail -f /path/to/serverLog | grep "server is up" | head -1 && do_some_command
jonderry
@ Jon Eso parece un poco frágil usando la cabeza de esa manera. Más importante aún, no es repetible como mis ejemplos. Si el "servidor está activo" está en las últimas diez líneas del registro, disparará el comando y saldrá inmediatamente. Si lo reinicia, lo más probable es que se active y salga de nuevo a menos que se hayan agregado al registro diez líneas que no contengan "servidor activo". Una modificación de eso que podría funcionar mejor es tail -n 0 -f /path/to/serverLog que leerá las últimas 0 líneas del archivo y luego esperará a que se impriman más líneas.
penguin359
16

Si solo está buscando una posibilidad y desea permanecer principalmente en el shell en lugar de usar awko perl, puede hacer algo como:

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

... que se ejecutará my_commandcada vez que aparezca " servidor activo " en el archivo de registro. Para múltiples posibilidades, tal vez podría soltar el grepy en su lugar usar un casedentro del while.

La mayúscula -Fle dice tailque esté atento a la rotación del archivo de registro; es decir, si el archivo actual se renombra y otro archivo con el mismo nombre toma su lugar, tailcambiará al nuevo archivo.

La --line-bufferedopción le dice grepque vacíe su búfer después de cada línea; de lo contrario, my_commandno se puede llegar a tiempo (suponiendo que los registros tengan líneas de tamaño razonable).

Jander
fuente
2
Realmente me gusta esta respuesta, pero al principio no me funcionó. Creo que debe agregar la --line-bufferedopción grepo asegurarse de que vacíe su salida entre líneas: de lo contrario, simplemente se bloquea y my_commandnunca se alcanza. Si lo prefieres ack, tiene una --flushbandera; si lo prefiere ag, intente envolverlo stdbuf. stackoverflow.com/questions/28982518/…
doctaphred
Intenté esto, usando do exit ;. Parecía funcionar bien, pero la cola nunca terminó y nuestro guión nunca pasó a la siguiente línea. ¿Hay alguna manera de detener la cola en la dosección?
Machtyn
14

Esta pregunta parece estar ya respondida, pero creo que hay una mejor solución.

En lugar de tail | whatevereso, creo que lo que realmente quieres es swatch. Swatch es un programa diseñado explícitamente para hacer lo que está pidiendo, ver un archivo de registro y ejecutar acciones basadas en líneas de registro. El uso tail|foorequerirá que tengas un terminal activo para hacer esto. Swatch, por otro lado, funciona como un demonio y siempre estará vigilando tus registros. Swatch está disponible en todas las distribuciones de Linux,

Te animo a que lo pruebes. Si bien puedes clavar un clavo con la parte posterior de un destornillador, no significa que debas hacerlo.

El mejor tutorial de 30 segundos sobre muestra que pude encontrar está aquí: http://www.campin.net/newlogcheck.html

bahamat
fuente
1
Ese tutorial es 404 ahora: /
desde allí el
10

Es extraño que nadie haya mencionado sobre la multitailutilidad que tiene esta funcionalidad lista para usar. Uno de ejemplo de uso:

Muestra el resultado de un comando ping y, si muestra un tiempo de espera excedido, envía un mensaje a todos los usuarios actualmente conectados

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

Vea también otros ejemplos de multitailuso.

php-codificador
fuente
+1, no tenía idea de que `multitail tenía ese tipo de habilidades ninja escondidas. Gracias por señalar eso.
Caleb
8

podría hacer el trabajo solo

Veamos cuán simple y legible podría ser:

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)

Si bien no usas bash regex, ¡esto podría ser muy rápido!

Pero + es un tándem muy eficiente e interesante

Pero para el servidor de alta carga, y como me gusta sedporque es muy rápido y muy escalable, a menudo uso esto:

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

Así es como empecé a hacer esto también, pero me he vuelto mucho más sofisticado. Un par de cosas para preocuparse:

  1. Si la cola del registro ya contiene "el servidor está activo".
  2. Terminando automáticamente el proceso de cola una vez que se encuentra.

Yo uso algo en la línea de esto:

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 manteniendo tailabierto hasta que el ${RELEASE}archivo contenga datos.

Una vez que lo greptiene éxito:

  1. escribe la salida a la ${RELEASE}que
  2. terminar el ${wait_pid}proceso para
  3. salir del tail

Nota: sedPuede ser más sofisticado determinar realmente el número de líneas que tailse producirán al inicio y eliminar ese número. Pero en general, son 10.

nada
fuente