Jak zakończyć komendę tee Linuksa bez zabijania aplikacji, z której otrzymuje

19

Mam skrypt bash, który działa, dopóki maszyna Linux jest włączona. Zaczynam tak, jak pokazano poniżej:

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

Po tym, jak się śmieje, widzę polecenie tee na moim wyjściu ps, jak pokazano poniżej:

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

Mam funkcję, która monitoruje rozmiar logu tworzonego przez tee i zabija polecenie tee, gdy log osiągnie określony rozmiar:

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
}

Ku mojemu zaskoczeniu, zabicie polecenia tee zabija również przez instancję start.sh. Dlaczego to? Jak mogę zakończyć polecenie tee, ale mój start.sh nadal działa? Dzięki.

PhilBot
źródło

Odpowiedzi:

34

Po teezakończeniu polecenie karmiące je będzie kontynuowane, dopóki nie spróbuje zapisać więcej danych wyjściowych. Otrzyma SIGPIPE (13 w większości systemów) za próbę zapisu do potoku bez czytników.

Jeśli zmodyfikujesz skrypt, aby przechwycić SIGPIPE i podejmiesz odpowiednie działania (np. Przestań zapisywać dane wyjściowe), powinieneś mieć możliwość kontynuowania działania po zakończeniu tee.


Jeszcze lepiej, niż zabijanie tee w ogóle, korzystanie logrotatez copytruncateopcji dla uproszczenia.

Cytując logrotate(8):

copytruncate

Obetnij oryginalny plik dziennika po utworzeniu kopii, zamiast przenosić stary plik dziennika i opcjonalnie utworzyć nowy. Można go użyć, gdy nie można nakazać programowi, aby zamknął swój plik dziennika, a zatem może kontynuować zapisywanie (dołączanie) do poprzedniego pliku dziennika na zawsze. Zauważ, że pomiędzy kopiowaniem pliku a obcięciem jest bardzo krótki przedział czasu, więc niektóre dane rejestrowania mogą zostać utracone. Gdy ta opcja jest używana, opcja tworzenia nie przyniesie żadnego efektu, ponieważ stary plik dziennika pozostaje na swoim miejscu.

Dzika karta
źródło
9
Można by również użyć tee -ado teeotwarcia pliku w trybie dopisywania, w przeciwnym razie będzie tee kontynuować zapis do pliku w tym samym przesunięcie po jej (obciąć i na systemach, które nie obsługują plików rzadkich jak na MacOS, które będą ponownie przypisz sekcję pliku prowadzącą do tej pozycji, zajmując dwa razy więcej miejsca na dysku).
Stéphane Chazelas
4
Inną opcją byłoby logger -sprzejście do syslog, aby zajął się rejestrowaniem ( -saby wydrukować również na stderr).
Stéphane Chazelas
1
+1 dla logrotate. Świetny program
Dmitrij Kudriavtsev
2
Lub w systemie używającym systemd i journald, systemd-cat zamiast loggera. Następnie otrzymujesz wiele filtrów wyjściowych i rotacji za darmo.
Zan Lynx,
3

Wyjaśnienie „Dlaczego”

W skrócie: Jeśli zapisywanie nie powiedzie się, nie spowoduje zakończenia programu (domyślnie), będziemy mieli bałagan. Zastanów się find . | head -n 10- nie chcesz findkontynuować pracy, skanując resztę dysku twardego, po headzajęciu 10 potrzebnych linii i kontynuowaniu.

Zrób to lepiej: obróć wewnątrz rejestratora

Rozważ następujące, które w ogóle nie wykorzystują tee, jako przykładowy przykład:

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

Jeśli działa jako:

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

... zacznie się od dołączenia do /tmp/nginx/debug_log, zmiany nazwy pliku na, /tmp/nginx/debug_log.oldgdy obecna jest ponad 100 KB treści. Ponieważ sam rejestrator wykonuje obrót, nie ma zepsutej rury, błędu ani okna utraty danych, gdy ma miejsce obrót - każda linia zostanie zapisana w jednym lub drugim pliku.

Oczywiście implementacja tego w natywnym bashu jest nieefektywna, ale powyższy przykład jest przykładem. Dostępnych jest wiele programów, które zaimplementują dla Ciebie powyższą logikę. Rozważać:

  • svlogd, rejestrator usług z pakietu Runit.
  • s6-log, aktywnie utrzymywana alternatywa z pakietu skanet.
  • multilog od DJB Daemontools, dziadka z tej rodziny narzędzi do nadzoru i monitorowania procesów.
Charles Duffy
źródło