Come terminare il comando tee Linux senza uccidere l'applicazione da cui riceve


19

Ho uno script bash che funziona finché la macchina Linux è accesa. Lo avvio come mostrato di seguito:

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

Dopo che si avvia, posso vedere il comando tee nel mio output ps come mostrato di seguito:

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

Ho una funzione che monitora la dimensione del registro che produce tee e uccide il comando tee quando il registro raggiunge una certa dimensione:

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
}

Con mia sorpresa, uccidere il comando tee uccide anche per istanza start.sh. Perchè è questo? Come posso terminare il comando tee ma far avviare il mio start.sh? Grazie.

Risposte:


34

Al teetermine, il comando di alimentazione continuerà a essere eseguito, fino a quando non tenta di scrivere più output. Quindi otterrà un SIGPIPE (13 sulla maggior parte dei sistemi) per provare a scrivere su una pipe senza lettori.

Se modifichi lo script in modo da intercettare SIGPIPE e intraprendi alcune azioni appropriate (ad esempio, interrompi la scrittura dell'output), dovresti riuscire a farlo continuare dopo che tee è stato terminato.


Meglio ancora, piuttosto che uccidere tee affatto, usare logrotatecon l' copytruncateopzione per semplicità.

Per citare logrotate(8):

copytruncate

Tronca il file di registro originale in posizione dopo aver creato una copia, anziché spostare il vecchio file di registro e crearne uno nuovo. Può essere utilizzato quando ad alcuni programmi non è possibile dire di chiudere il suo file di registro e quindi potrebbe continuare a scrivere (aggiungendo) al file di registro precedente per sempre. Si noti che c'è un intervallo di tempo molto piccolo tra la copia del file e il suo troncamento, quindi alcuni dati di registrazione potrebbero andare persi. Quando viene utilizzata questa opzione, l'opzione di creazione non avrà alcun effetto, poiché il vecchio file di registro rimane in posizione.


9
Dovresti anche usare tee -aper teeaprire il file in modalità append, altrimenti tee continuerà a scrivere nel file con lo stesso offset dopo averlo troncato (e su sistemi che non supportano file sparsi come su macOS che riallocare la sezione del file che porta a quella posizione, occupando il doppio dello spazio su disco).
Stéphane Chazelas il

4
Un'altra opzione sarebbe quella di reindirizzare affinché logger -ssyslog si occupasse della registrazione ( -sper stampare anche su stderr).
Stéphane Chazelas il

1
+1 per logrotate. Ottimo programma
Dmitry Kudriavtsev il

2
O su un sistema che utilizza systemd e journald, systemd-cat anziché logger. Quindi ottieni un sacco di filtri di output e rotazione gratuitamente.
Zan Lynx,

3

Spiegare il "Perché"

In breve: se le scritture non riuscite non causavano la chiusura di un programma (per impostazione predefinita), avremmo un pasticcio. Considera find . | head -n 10: non vuoi findcontinuare a correre, scansionando il resto del tuo disco rigido, dopo headaver già preso le 10 linee necessarie e procedendo.

Farlo meglio: ruota all'interno del tuo logger

Considera quanto segue, che non usa teeaffatto, come esempio dimostrativo:

#!/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

Se eseguito come:

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

... questo inizierà aggiungendo /tmp/nginx/debug_log, rinominando il file /tmp/nginx/debug_log.oldquando sono presenti oltre 100 KB di contenuti. Poiché il logger stesso sta eseguendo la rotazione, non vi è alcuna pipe interrotta, nessun errore e nessuna finestra di perdita di dati quando si verifica la rotazione: ogni riga verrà scritta in un file o in un altro.

Naturalmente, implementare questo in bash nativo è inefficiente, ma quanto sopra è un esempio illustrativo. Ci sono numerosi programmi disponibili che implementeranno la logica sopra per te. Ritenere:

  • svlogd, il servizio di registrazione della suite Runit.
  • s6-log, un'alternativa mantenuta attivamente dalla suite skanet.
  • multilog da DJB Daemontools, il nonno di questa famiglia di strumenti di supervisione e monitoraggio dei processi.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.