Mantener el registro y / o stdout / stderr en Python Daemon

Cada receta que he encontrado para crear un proceso de daemon en Python implica forking dos veces (para Unix) y luego cerrar todos los descriptores de archivos abiertos. (Consulte http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ para ver un ejemplo).

Todo esto es lo suficientemente simple pero parece que tengo un problema. En la máquina de producción que estoy configurando, mi daemon está abortando, en silencio desde que se cerraron todos los descriptores de archivos abiertos. Estoy teniendo un tiempo difícil de depurar el problema actualmente y me pregunto cuál es la forma correcta de detectar y registrar estos errores.

¿Cuál es la forma correcta de configurar el registro de manera que continúe funcionando después de la demonización? ¿Acabo de llamar a logging.basicConfig() por segunda vez después de la demonización? ¿Cuál es la forma correcta de capturar stdout y stderr ? Estoy confuso en los detalles de por qué todos los archivos están cerrados. Idealmente, mi código principal podría simplemente llamar daemon_start(pid_file) y el registro continuaría funcionando.

Utilizo la biblioteca del python-daemon para mi comportamiento de daemonización.

Interfaz descrita aquí:

Implementación aquí:

Permite especificar un argumento files_preserve , para indicar los descriptores de archivo que no deben cerrarse al desemejar.

Si necesita iniciar sesión a través de las mismas instancias de Handler antes y después de la daemonización, puede:

  1. Primero configure sus manejadores de registro utilizando basicConfig o dictConfig o lo que sea.
  2. Cosas de registro
  3. Determine de qué descriptores de archivo dependen sus Handler . Desafortunadamente esto depende de la subclase Handler . Si su Handler instalado por primera vez es un StreamHandler , es el valor de logging.root.handlers[0].stream.fileno() ; si su segundo Handler instalado es un SyslogHandler , desea el valor de logging.root.handlers[1].socket.fileno() ; Esto es desordenado 🙁
  4. Daemonice su proceso creando un DaemonContext con files_preserve igual a una lista de los descriptores de archivos que determinó en el paso 3.
  5. Continuar el registro; sus archivos de registro no deberían haber sido cerrados durante la doble horquilla.

Una alternativa podría ser, como lo sugirió @Exelian, usar realmente diferentes instancias de Handler antes y después de la demonio. Inmediatamente después de la daemonización, destruya los controladores existentes ( logger.root.handlers de logger.root.handlers ?) Y cree los nuevos idénticos; no puede volver a llamar a basicConfig debido al problema que señaló @ dave-mankoff.

Puede simplificar el código para esto si configura los objetos del controlador de registro por separado del objeto del registrador raíz y luego agrega los objetos del controlador como un paso independiente en lugar de hacerlo todo al mismo tiempo. Lo siguiente debería funcionar para usted.

 import daemon import logging logger = logging.getLogger() logger.setLevel(logging.DEBUG) fh = logging.FileHandler("./foo.log") logger.addHandler(fh) context = daemon.DaemonContext( files_preserve = [ fh.stream, ], ) logger.debug( "Before daemonizing." ) context.open() logger.debug( "After daemonizing." ) 

Acabamos de tener un problema similar, y debido a algunas cosas más allá de mi control, las cosas del demonio estaban separadas de las cosas que creaban el registrador. Sin embargo, el registrador tiene atributos .handlers y .parent que lo hacen posible con algo como:

  self.files_preserve = self.getLogFileHandles(self.data.logger) def getLogFileHandles(self,logger): """ Get a list of filehandle numbers from logger to be handed to DaemonContext.files_preserve """ handles = [] for handler in logger.handlers: handles.append(handler.stream.fileno()) if logger.parent: handles += self.getLogFileHandles(logger.parent) return handles