Cómo terminar el comando tee de Linux sin matar la aplicación que está recibiendo de

19

Tengo un script bash que se ejecuta mientras la máquina Linux está encendida. Lo comienzo como se muestra a continuación:

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

Después de que se inicia, puedo ver el comando tee en mi salida ps como se muestra a continuación:

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

Tengo una función que monitorea el tamaño del registro que produce tee y mata el comando tee cuando el registro alcanza un cierto tamaño:

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
}

Para mi sorpresa, matar el comando tee también mata por instancia de start.sh. ¿Por qué es esto? ¿Cómo puedo finalizar el comando tee pero que mi start.sh continúe ejecutándose? Gracias.

PhilBot
fuente

Respuestas:

34

Cuando teetermina, el comando que lo alimenta continuará ejecutándose, hasta que intente escribir más resultados. Luego obtendrá un SIGPIPE (13 en la mayoría de los sistemas) por intentar escribir en una tubería sin lectores.

Si modifica su script para atrapar SIGPIPE y toma alguna acción apropiada (como, dejar de escribir la salida), entonces debería poder continuar después de que finalice tee.


Mejor aún, en lugar de matar tee en absoluto, úselo logrotatecon la copytruncateopción de simplicidad.

Para citar logrotate(8):

copytruncate

Trunca el archivo de registro original en su lugar después de crear una copia, en lugar de mover el archivo de registro anterior y, opcionalmente, crear uno nuevo. Se puede usar cuando no se puede indicar a algún programa que cierre su archivo de registro y, por lo tanto, puede continuar escribiendo (anexando) en el archivo de registro anterior para siempre. Tenga en cuenta que hay un intervalo de tiempo muy pequeño entre copiar el archivo y truncarlo, por lo que podrían perderse algunos datos de registro. Cuando se utiliza esta opción, la opción de creación no tendrá ningún efecto, ya que el archivo de registro anterior permanece en su lugar.

Comodín
fuente
99
También querrá usar tee -apara teeabrir el archivo en modo anexar, de lo contrario, tee continuará escribiendo en el archivo con el mismo desplazamiento después de truncarlo (y en sistemas que no admiten archivos dispersos como en macOS que reasignar la sección del archivo que conduce a esa posición, ocupando el doble de espacio en disco).
Stéphane Chazelas
44
Otra opción sería canalizar logger -spara que syslog se encargue del registro ( -spara imprimir también en stderr).
Stéphane Chazelas
1
+1 para logrotate. Gran programa
Dmitry Kudriavtsev
2
O en un sistema que usa systemd y journald, systemd-cat en lugar de logger. Luego obtienes una gran cantidad de filtrado y rotación de salida de forma gratuita.
Zan Lynx
3

Explicando el "por qué"

En resumen: si el error de escritura no causa que un programa salga (por defecto), tendríamos un desastre. Considere find . | head -n 10: no desea findseguir ejecutándose, escaneando el resto de su disco duro, después de headhaber tomado las 10 líneas que necesitaba y continuar.

Haciéndolo mejor: gire dentro de su registrador

Considere lo siguiente, que no se usa teeen absoluto, como un ejemplo demostrativo:

#!/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 se ejecuta como:

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

... esto comenzará agregando /tmp/nginx/debug_log, renombrando el archivo a /tmp/nginx/debug_log.oldmás de 100 KB de contenido. Debido a que el registrador mismo está haciendo la rotación, no hay una tubería rota, ningún error ni una ventana de pérdida de datos cuando se realiza la rotación: cada línea se escribirá en un archivo u otro.

Por supuesto, implementar esto en bash nativo es ineficiente, pero lo anterior es un ejemplo ilustrativo. Existen numerosos programas disponibles que implementarán la lógica anterior para usted. Considerar:

  • svlogd, el registrador de servicios de la suite Runit.
  • s6-log, una alternativa mantenida activamente de la suite skanet.
  • multilog de DJB Daemontools, el abuelo de esta familia de herramientas de supervisión y monitoreo de procesos.
Charles Duffy
fuente