web-dev-qa-db-fra.com

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

J'ai un journal de serveur qui produit une ligne de texte spécifique dans son fichier journal lorsque le serveur est en marche. Je veux exécuter une commande une fois que le serveur est en place, et donc faire quelque chose comme ceci:

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

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

56
jonderry

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, ces deux messages sont de vrais messages provenant d'un journal du 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");
    }
}
36
penguin359

Si vous ne recherchez qu'une seule possibilité et que vous souhaitez rester principalement dans le shell plutôt que d'utiliser awk ou 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_command chaque fois que "le serveur est en place" apparaît dans le fichier journal. Pour plusieurs possibilités, vous pouvez peut-être supprimer le grep et utiliser à la place un case dans le while.

La majuscule -F Indique à tail de surveiller la rotation du fichier journal; c'est-à-dire si le fichier actuel est renommé et qu'un autre fichier du même nom prend sa place, tail basculera vers le nouveau fichier.

L'option --line-buffered Indique à grep de vider son tampon après chaque ligne; sinon, my_command risque de ne pas être atteint en temps opportun (en supposant que les journaux contiennent des lignes de taille raisonnable).

20
Jander

Cette question semble déjà avoir reçu une réponse, 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 basées sur des lignes de journal. En utilisant tail|foo exigera que vous ayez un terminal en cours d'exécution pour ce faire. Swatch d'autre part 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 avec l'arrière 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 .

14
bahamat

Il est étrange que personne n'ait mentionné l'utilitaire multitail qui a cette fonctionnalité prête à l'emploi. Un exemple d'utilisation:

Afficher la sortie d'une commande ping et si elle affiche un délai d'expiration, envoyer un message à tous les utilisateurs actuellement connectés

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

Voir aussi autres exemples d'utilisation multitail.

11
php-coder

bash 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 le regex de bash, cela pourrait rester très rapide!

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

Mais pour les serveurs à forte charge, et comme j'aime sed parce 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;
  ...
')
8
F. Hauri

C'est comme ça que j'ai commencé à faire ça aussi, mais je suis devenu beaucoup plus sophistiqué avec ça. Quelques points à considérer:

  1. Si la queue du journal contient déjà "le serveur est en place".
  2. Mettre fin automatiquement au 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 tail ouvert jusqu'à ce que ${RELEASE} le fichier contient des données.

Une fois que grep réussit:

  1. écrit la sortie dans ${RELEASE} Qui va
  2. terminer le ${wait_pid} processus pour
  3. quitter le tail

Remarque: sed peut être plus sophistiqué pour déterminer réellement le nombre de lignes que tail produira au démarrage et supprimer ce nombre. Mais en général, c'est 10.

6
nix