Comment terminer la commande Linux tee sans tuer l'application qu'elle reçoit

19

J'ai un script bash qui s'exécute tant que la machine Linux est sous tension. Je le démarre comme indiqué ci-dessous:

( /mnt/apps/start.sh 2>&1 | tee /tmp/nginx/debug_log.log ) &

Après son lancement, je peux voir la commande tee dans ma sortie ps comme indiqué ci-dessous:

$ ps | grep tee
  418 root       0:02 tee /tmp/nginx/debug_log.log
3557 root       0:00 grep tee

J'ai une fonction qui surveille la taille du journal produit par tee et tue la commande tee lorsque le journal atteint une certaine taille:

monitor_debug_log_size() {
                ## Monitor the file size of the debug log to make sure it does not get too big
                while true; do
                                cecho r "CHECKING DEBUG LOG SIZE... "
                                debugLogSizeBytes=$(stat -c%s "/tmp/nginx/debug_log.log")
                                cecho r "DEBUG LOG SIZE: $debugLogSizeBytes"
                                if [ $((debugLogSizeBytes)) -gt 100000 ]; then
                                                cecho r "DEBUG LOG HAS GROWN TO LARGE... "
                                                sleep 3
                                                #rm -rf /tmp/nginx/debug_log.log 1>/dev/null 2>/dev/null
                                                kill -9 `pgrep -f tee`
                                fi
                                sleep 30
                done
}

À ma grande surprise, tuer la commande tee tue également par l'instance start.sh. Pourquoi est-ce? Comment puis-je terminer la commande tee mais que mon start.sh continue de s'exécuter? Merci.

PhilBot
la source

Réponses:

34

Une fois teeterminée, la commande qui l'alimente continuera de s'exécuter jusqu'à ce qu'elle tente d'écrire davantage de résultats. Ensuite, il obtiendra un SIGPIPE (13 sur la plupart des systèmes) pour essayer d'écrire sur un canal sans lecteur.

Si vous modifiez votre script pour intercepter SIGPIPE et prenez une action appropriée (comme, arrêter d'écrire la sortie), vous devriez pouvoir le continuer après la fin du tee.


Mieux encore, plutôt que de tuer tee du tout, utilisez logrotatel' copytruncateoption de simplicité.

Pour citer logrotate(8):

copytruncate

Tronquez le fichier journal d'origine en place après avoir créé une copie, au lieu de déplacer l'ancien fichier journal et éventuellement d'en créer un nouveau. Il peut être utilisé lorsqu'un programme ne peut pas être invité à fermer son fichier journal et peut donc continuer d'écrire (ajouter) dans le fichier journal précédent pour toujours. Notez qu'il existe un très petit intervalle de temps entre la copie du fichier et sa troncature, de sorte que certaines données de journalisation peuvent être perdues. Lorsque cette option est utilisée, l'option de création n'aura aucun effet, car l'ancien fichier journal reste en place.

Caractère générique
la source
9
Vous voudrez également utiliser tee -apour teeouvrir le fichier en mode ajout, sinon, tee continuera à écrire dans le fichier avec le même décalage après l'avoir tronqué (et sur les systèmes qui ne prennent pas en charge les fichiers clairsemés comme sur macOS qui réallouer la section du fichier menant à cette position, en prenant deux fois plus d'espace disque).
Stéphane Chazelas
4
Une autre option serait de diriger vers logger -ssyslog pour prendre en charge la journalisation ( -spour également imprimer sur stderr).
Stéphane Chazelas
1
+1 pour logrotate. Excellent programme
Dmitry Kudriavtsev
2
Ou sur un système utilisant systemd et journald, systemd-cat au lieu d'enregistreur. Ensuite, vous obtenez gratuitement beaucoup de filtrage et de rotation de sortie.
Zan Lynx
3

Expliquer le "pourquoi"

En bref: si les échecs d'écriture ne provoquaient pas la fermeture d'un programme (par défaut), nous aurions un gâchis. Considérez find . | head -n 10- vous ne voulez findpas continuer à courir, à scanner le reste de votre disque dur, après headavoir déjà pris les 10 lignes nécessaires et continuer.

Faire mieux: faites pivoter l'intérieur de votre enregistreur

Considérez ce qui suit, qui n'utilise pas teedu tout, comme exemple démonstratif:

#!/usr/bin/env bash

file=${1:-debug.log}                     # filename as 1st argument
max_size=${2:-100000}                    # max size as 2nd argument
size=$(stat --format=%s -- "$file") || exit  # Use GNU stat to retrieve size
exec >>"$file"                           # Open file for append

while IFS= read -r line; do              # read a line from stdin
  size=$(( size + ${#line} + 1 ))        # add line's length + 1 to our counter
  if (( size > max_size )); then         # and if it exceeds our maximum...
    mv -- "$file" "$file.old"            # ...rename the file away...
    exec >"$file"                        # ...and reopen a new file as stdout
    size=0                               # ...resetting our size counter
  fi
  printf '%s\n' "$line"                  # regardless, append to our current stdout
done

Si exécuté en tant que:

/mnt/apps/start.sh 2>&1 | above-script /tmp/nginx/debug_log

... cela commencera par ajouter à /tmp/nginx/debug_log, renommer le fichier /tmp/nginx/debug_log.oldlorsque plus de 100 Ko de contenu sont présents. Parce que l'enregistreur lui-même effectue la rotation, il n'y a pas de canal cassé, pas d'erreur et pas de fenêtre de perte de données lorsque la rotation a lieu - chaque ligne sera écrite dans un fichier ou un autre.

Bien sûr, l'implémentation de cela dans bash natif est inefficace, mais ce qui précède est un exemple illustratif. Il existe de nombreux programmes disponibles qui mettront en œuvre la logique ci-dessus pour vous. Considérer:

  • svlogd, l'enregistreur de service de la suite Runit.
  • s6-log, une alternative activement maintenue de la suite skanet.
  • multilog de DJB Daemontools, le grand-père de cette famille d'outils de supervision et de surveillance de processus.
Charles Duffy
la source