Meilleure façon de suivre un journal et d'exécuter une commande lorsqu'un texte apparaît dans le journal

54

J'ai un journal de serveur qui affiche une ligne de texte spécifique dans son fichier journal lorsque le serveur est opérationnel. Je veux exécuter une commande une fois que le serveur est en marche, et par conséquent, procédez comme suit:

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

Quelle est la meilleure façon de procéder?

Jonderry
la source
3
assurez-vous de vous en servir tail -Fpour gérer la rotation des journaux - c'est-à-dire qu'elle my.logdevient pleine et my.log.1que le processus en crée un nouveaumy.log
sg

Réponses:

34

Un moyen simple serait génial.

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

Et oui, les deux sont de vrais messages d'un journal de noyau. Perl pourrait être un peu plus élégant à utiliser pour cela et peut également remplacer le besoin de queue. Si vous utilisez perl, cela ressemblera à ceci:

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");
    }
}
pingouin359
la source
J'aime la awksolution pour être courte et facile à faire à la volée en une seule ligne. Cependant, si la commande que je veux exécuter a des guillemets, cela peut être un peu fastidieux. Existe-t-il une alternative, peut-être utiliser des pipelines et des commandes composées qui permet également une solution brève à une ligne sans que la commande résultante soit transmise comme une chaîne?
Jonderry
1
@jon Vous pouvez écrire awk sous forme de script. Utilisez "#! / Usr / bin awk -f" comme première ligne du script. Cela éliminera le besoin de guillemets simples extérieurs dans mon exemple et les libérera pour utilisation dans une system()commande.
penguin359
@ penguin359, c'est vrai, mais il serait quand même intéressant de le faire depuis la ligne de commande. Dans mon cas, il y a une variété de choses que je voudrais faire, y compris beaucoup de choses que je ne peux pas prévoir, il est donc pratique de pouvoir démarrer le serveur et de tout faire en une seule ligne.
Jonderry
J'ai trouvé une alternative, mais je ne sais pas à quel point elle est solide:tail -f /path/to/serverLog | grep "server is up" | head -1 && do_some_command
jonderry
@jon Cela semble un peu fragile en utilisant la tête de cette façon. Plus important encore, ce n'est pas répétable comme mes exemples. Si "le serveur est en service" figure dans les dix dernières lignes du journal, la commande est exécutée et sortie immédiatement. Si vous le redémarrez, il se déclenchera probablement et se fermera à nouveau à moins que dix lignes ne contenant pas "serveur en état" soient ajoutées au journal. Une modification de ce qui pourrait fonctionner mieux est tail -n 0 -f /path/to/serverLog celle qui lira les 0 dernières lignes du fichier, puis attendra que plus de lignes soient imprimées.
Penguin359
16

Si vous recherchez seulement une possibilité et souhaitez rester principalement dans la coquille plutôt que d'utiliser awkou perl, vous pouvez faire quelque chose comme:

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

... qui s'exécutera à my_commandchaque fois que "le serveur est en service " apparaît dans le fichier journal. Pour de multiples possibilités, vous pouvez peut-être laisser tomber le grepet utiliser plutôt un casedans le while.

La capitale -Findique tailde surveiller le fichier journal à faire pivoter; Par exemple, si le fichier en cours est renommé et qu'un autre fichier portant le même nom prend sa place, tailil basculera vers le nouveau fichier.

L' --line-bufferedoption indique grepde vider son tampon après chaque ligne; sinon, il my_commandpeut ne pas être atteint à temps (en supposant que les journaux contiennent des lignes de taille raisonnable).

Jander
la source
2
J'aime beaucoup cette réponse, mais cela n’a pas fonctionné pour moi au début. Je pense que vous devez ajouter l' --line-bufferedoption à grep, ou sinon vous assurer qu'elle purge sa sortie entre les lignes: sinon, elle se bloque et my_commandn'est jamais atteinte. Si vous préférez ack, il y a un --flushdrapeau; si vous préférez ag, essayez d'envelopper avec stdbuf. stackoverflow.com/questions/28982518/…
doctaphred le
J'ai essayé cela, en utilisant do exit ;. Cela semblait bien fonctionner, mais la queue ne finissait jamais et notre script ne passait jamais à la ligne suivante. Est-il possible d'arrêter la queue dans la dosection?
Machtyn le
14

On semble déjà avoir répondu à cette question, mais je pense qu'il existe une meilleure solution.

Plutôt que tail | whatever, je pense que ce que vous voulez vraiment, c'est swatch. Swatch est un programme conçu explicitement pour faire ce que vous demandez, regarder un fichier journal et exécuter des actions en fonction des lignes de journal. Pour utiliser tail|foocette fonction, vous devez disposer d’un terminal actif. Swatch, de son côté, fonctionne comme un démon et surveillera toujours vos journaux. Swatch est disponible dans toutes les distributions Linux,

Je vous encourage à l'essayer. Bien que vous puissiez enfoncer un clou dans le dos d’un tournevis, cela ne signifie pas que vous devriez le faire.

Le meilleur tutoriel de 30 secondes sur swatch que j'ai pu trouver est ici: http://www.campin.net/newlogcheck.html

bahamat
la source
1
Ce tutoriel est maintenant 404: /
therefromhere
10

Il est étrange que personne ne mentionne un multitailutilitaire doté de cette fonctionnalité immédiatement. Un exemple d'utilisation:

Affiche le résultat d'une commande ping et, s'il affiche un délai, envoie un message à tous les utilisateurs actuellement connectés.

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

Voir aussi un autre exemple d' multitailutilisation.

php-codeur
la source
+1, je ne savais pas que le multitail avait ce genre de compétences de ninja caché. Merci d'avoir fait remarquer cela.
Caleb
8

pourrait faire le travail tout seul

Voyons à quel point cela pourrait être simple et lisible:

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)

Bien que vous n'utilisiez pas bash regex, cela pourrait rester très rapide!

Mais + est un tandem très efficace et intéressant

Mais pour les serveurs à forte charge, et comme je l’aime bien sedparce que c’est très rapide et très évolutif, j’utilise souvent ceci:

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
la source
6

C'est comme ça que j'ai commencé à faire ça aussi mais que je suis devenu beaucoup plus sophistiqué avec ça. Quelques choses à se préoccuper de:

  1. Si la fin du journal contient déjà "le serveur est en service".
  2. Terminer automatiquement le processus de queue une fois qu'il est trouvé.

J'utilise quelque chose dans le sens de ceci:

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}

Cela fonctionne en maintenant tailouvert jusqu'à ce que le ${RELEASE}fichier contienne des données.

Une fois le grepréussit:

  1. écrit la sortie sur ${RELEASE}laquelle
  2. terminer le ${wait_pid}processus pour
  3. sortir de la tail

Remarque: Il sedpeut être plus sophistiqué de déterminer le nombre de lignes tailgénérées au démarrage et de supprimer ce nombre. Mais généralement, il est 10.

rien
la source