personalize left custom change bar python multithreading logging

left - Python logging desde mĂșltiples hilos



tmux personalize (2)

El registro de Python es seguro para subprocesos:

Así que no tienes ningún problema en el código de Python (biblioteca).

La rutina a la que llama desde varios subprocesos ( WriteLog ) no se escribe en ningún estado compartido. Así que no tienes ningún problema en tu código.

Así que estás bien.

Tengo un módulo log.py , que se utiliza en al menos otros dos módulos ( server.py y device.py ).

Tiene estos globales:

fileLogger = logging.getLogger() fileLogger.setLevel(logging.DEBUG) consoleLogger = logging.getLogger() consoleLogger.setLevel(logging.DEBUG) file_logging_level_switch = { ''debug'': fileLogger.debug, ''info'': fileLogger.info, ''warning'': fileLogger.warning, ''error'': fileLogger.error, ''critical'': fileLogger.critical } console_logging_level_switch = { ''debug'': consoleLogger.debug, ''info'': consoleLogger.info, ''warning'': consoleLogger.warning, ''error'': consoleLogger.error, ''critical'': consoleLogger.critical }

Tiene dos funciones:

def LoggingInit( logPath, logFile, html=True ): global fileLogger global consoleLogger logFormatStr = "[%(asctime)s %(threadName)s, %(levelname)s] %(message)s" consoleFormatStr = "[%(threadName)s, %(levelname)s] %(message)s" if html: logFormatStr = "<p>" + logFormatStr + "</p>" # File Handler for log file logFormatter = logging.Formatter(logFormatStr) fileHandler = logging.FileHandler( "{0}{1}.html".format( logPath, logFile )) fileHandler.setFormatter( logFormatter ) fileLogger.addHandler( fileHandler ) # Stream Handler for stdout, stderr consoleFormatter = logging.Formatter(consoleFormatStr) consoleHandler = logging.StreamHandler() consoleHandler.setFormatter( consoleFormatter ) consoleLogger.addHandler( consoleHandler )

Y:

def WriteLog( string, print_screen=True, remove_newlines=True, level=''debug'' ): if remove_newlines: string = string.replace(''/r'', '''').replace(''/n'', '' '') if print_screen: console_logging_level_switch[level](string) file_logging_level_switch[level](string)

Llamo a LoggingInit desde server.py , que inicializa los registradores de archivos y consolas. Luego llamo a WriteLog desde cualquier lugar, por lo que múltiples hilos están accediendo a fileLogger y consoleLogger .

¿Necesito más protección para mi archivo de registro? La documentación indica que el bloqueo maneja los bloqueos de hilos.


La buena noticia es que no necesita hacer nada adicional por la seguridad de los hilos, y tampoco necesita nada extra o algo casi trivial para un cierre limpio. Voy a llegar a los detalles más tarde.

La mala noticia es que su código tiene un problema grave incluso antes de llegar a ese punto: fileLogger y consoleLogger son el mismo objeto. De la documentación para getLogger() :

Devuelva un registrador con el nombre especificado o, si no se especifica ningún nombre, devuelva un registrador que sea el registrador raíz de la jerarquía.

Entonces, está obteniendo el registrador raíz y almacenándolo como fileLogger , y luego está obteniendo el registrador raíz y almacenándolo como consoleLogger . Entonces, en LoggingInit , inicializa fileLogger , y luego reinicializa el mismo objeto con un nombre diferente con diferentes valores.

Puede agregar varios manejadores al mismo registrador y, dado que la única inicialización que realmente realiza para cada uno es addHandler , su código funcionará de la forma prevista, pero solo por accidente. Y sólo una especie de. Obtendrá dos copias de cada mensaje en ambos registros si pasa print_screen=True , y obtendrá copias en la consola incluso si pasa print_screen=False .

En realidad no hay ninguna razón para las variables globales en absoluto; El objetivo principal de getLogger() es que puede llamarlo cada vez que lo necesite y obtener el root logger global, por lo que no necesita almacenarlo en ningún lugar.

Un problema menor es que no está escapando el texto que inserta en HTML. En algún momento, intentarás registrar la cadena "a < b" y terminarás en problemas.

Menos seriamente, una secuencia de etiquetas <p> que no está dentro de un <body> dentro de un <html> no es un documento HTML válido. Pero muchos espectadores se encargarán de eso automáticamente, o puede postprocesar sus registros de forma trivial antes de mostrarlos. Pero si realmente desea que esto sea correcto, debe FileHandler subclase de FileHandler y hacer que su __init__ agregue un encabezado si recibe un archivo vacío y elimine un pie de página si está presente, luego __init__ que su close agregue un pie de página.

Volviendo a su pregunta real:

No necesita ningún bloqueo adicional. Si un controlador implementa correctamente createLock , acquire y release (y se llama en una plataforma con subprocesos), la maquinaria de registro se asegurará automáticamente de adquirir el bloqueo cuando sea necesario para asegurarse de que cada mensaje se registra de forma atómica.

Por lo que sé, la documentación no dice directamente que StreamHandler y FileHandler implementen estos métodos, sino que lo implican enérgicamente ( el texto que mencionó en la pregunta dice "El módulo de registro está diseñado para ser seguro para subprocesos sin ningún trabajo especial. necesidad de ser hecho por sus clientes ", etc.). Y puede mirar la fuente de su implementación (por ejemplo, CPython 3.3 ) y ver que ambos heredan los métodos implementados correctamente del logging.Handler . logging.Handler .

De la misma manera, si un manejador implementa correctamente el flush y el close , la maquinaria de registro se asegurará de que esté finalizado correctamente durante el apagado normal.

Aquí, la documentación explica qué es StreamHandler.flush() , FileHandler.flush() y FileHandler.close() . En su mayoría, son lo que usted esperaría, excepto que StreamHandler.close() no funciona, lo que significa que es posible que los mensajes de registro finales a la consola se pierdan. De los documentos:

Tenga en cuenta que el método close() se hereda de Handler y, por lo tanto, no tiene salida, por lo que a veces se puede necesitar una llamada explícita a flush()

Si esto te importa, y quieres solucionarlo, debes hacer algo como esto:

class ClosingStreamHandler(logging.StreamHandler): def close(self): self.flush() super().close()

Y luego use ClosingStreamHandler() lugar de StreamHandler() .

FileHandler no tiene tal problema.

La forma normal de enviar registros a dos lugares es simplemente usar el registrador raíz con dos controladores, cada uno con su propio formateador.

Además, incluso si desea dos registradores, no necesita los console_logging_level_switch separados console_logging_level_switch y file_logging_level_switch ; llamar a Logger.debug(msg) es exactamente lo mismo que llamar a Logger.log(DEBUG, msg) . Aún necesitará alguna forma de asignar sus nombres de nivel personalizados de debug , etc. a los nombres estándar DEBUG , etc., pero puede hacer una búsqueda, en lugar de hacerlo una vez por registrador (además, si sus nombres son solo el nombres estándar con diferente elenco, puedes engañar).

Todo esto se describe bastante bien en la sección ` Manejadores múltiples y formateadores , y el resto del libro de cocina de registro.

El único problema con la forma estándar de hacer esto es que no puede desactivar fácilmente el registro de la consola mensaje por mensaje. Eso es porque no es una cosa normal de hacer. Por lo general, solo inicia sesión por niveles y establece un nivel de registro más alto en el registro de archivos.

Pero, si quieres más control, puedes usar filtros. Por ejemplo, otorgue a su FileHandler un filtro que lo acepte todo, y a su ConsoleHandler un filtro que requiera algo que comience con la console , luego use la ''console'' if print_screen else '''' del filtro ''console'' if print_screen else '''' . Eso reduce WriteLog a casi una sola línea.

Aún necesita las dos líneas adicionales para eliminar las nuevas líneas, pero incluso puede hacerlo en el filtro, o mediante un adaptador, si lo desea. (Una vez más, vea el libro de cocina). Y luego WriteLog realmente es de una sola línea.