logging — Informe de estado, error y mensajes informativos

Propósito:Informe de estado, error y mensajes informativos.

El módulo de logging define una interfaz de programación estándar para informar errores e información de estado de aplicaciones y bibliotecas. El beneficio clave de tener la interfaz de programación de registro proporcionada por un módulo de biblioteca estándar es que todos los módulos de Python pueden participar en el registro, por lo que el registro de una aplicación puede incluir mensajes de módulos de terceros.

Componentes de registro

El sistema de registro está compuesto por cuatro tipos de objetos que interactúan. Cada módulo o aplicación que desea registrar utiliza una instancia de Logger para agregar información a los registros. Invocar al registrador crea un LogRecord, que se utiliza para mantener la información en la memoria hasta que se procesa. Un Logger puede tener varios objetos Handler configurados para recibir y procesar registros. El Handler usa un Formatter para convertir los registros en mensajes de salida.

Registro en aplicaciones versus bibliotecas

Los desarrolladores de aplicaciones y los autores de bibliotecas pueden usar logging, pero cada audiencia tiene diferentes consideraciones a tener en cuenta.

Los desarrolladores de aplicaciones configuran el módulo de logging dirigiendo los mensajes a los canales de salida apropiados. Es posible registrar mensajes con diferentes niveles de verbosidad o en diferentes destinos. Se incluyen todos los controladores para escribir mensajes de registro en archivos, ubicaciones HTTP GET/POST, correo electrónico a través de SMTP, sockets genéricos o mecanismos de registro específicos del sistema operativo, y es posible crear clases de destino de registro personalizadas para requisitos especiales no manejados por ninguno de las clases integradas.

Los desarrolladores de bibliotecas también pueden usar logging y tienen aún menos trabajo por hacer. Simplemente crea una instancia de registrador para cada contexto, usando un nombre apropiado, y luego registra los mensajes usando los niveles estándar. Siempre que una biblioteca use la interfaz de programación de registro con nombres y selecciones de nivel consistentes, la aplicación se puede configurar para mostrar u ocultar mensajes de la biblioteca, según lo desees.

Registro a un archivo

La mayoría de las aplicaciones están configuradas para registrar en un archivo. Usa la función basicConfig() para configurar el controlador predeterminado para que los mensajes de depuración se escriban en un archivo.

logging_file_example.py
import logging

LOG_FILENAME = 'logging_example.out'
logging.basicConfig(
    filename=LOG_FILENAME,
    level=logging.DEBUG,
)

logging.debug('This message should go to the log file')

with open(LOG_FILENAME, 'rt') as f:
    body = f.read()

print('FILE:')
print(body)

Después de ejecutar la secuencia de comandos el mensaje de registro se escribe en logging_example.out:

$ python3 logging_file_example.py

FILE:
DEBUG:root:This message should go to the log file

Rotación de archivos de registro

Ejecutar la secuencia de comandos repetidamente hace que se agreguen más mensajes al archivo. Para crear un nuevo archivo cada vez que se ejecuta el programa, pasa un argumento de filemode a basicConfig() con un valor de 'w'. Sin embargo, en lugar de administrar la creación de archivos de esta manera, es mejor usar un RotatingFileHandler, que crea nuevos archivos automáticamente y conserva el antiguo archivo de registro al mismo tiempo.

logging_rotatingfile_example.py
import glob
import logging
import logging.handlers

LOG_FILENAME = 'logging_rotatingfile_example.out'

# Set up a specific logger with our desired output level
my_logger = logging.getLogger('MyLogger')
my_logger.setLevel(logging.DEBUG)

# Add the log message handler to the logger
handler = logging.handlers.RotatingFileHandler(
    LOG_FILENAME,
    maxBytes=20,
    backupCount=5,
)
my_logger.addHandler(handler)

# Log some messages
for i in range(20):
    my_logger.debug('i = %d' % i)

# See what files are created
logfiles = glob.glob('%s*' % LOG_FILENAME)
for filename in sorted(logfiles):
    print(filename)

El resultado son seis archivos separados, cada uno con parte del historial de registro de la aplicación.

$ python3 logging_rotatingfile_example.py

logging_rotatingfile_example.out
logging_rotatingfile_example.out.1
logging_rotatingfile_example.out.2
logging_rotatingfile_example.out.3
logging_rotatingfile_example.out.4
logging_rotatingfile_example.out.5

El archivo más actual siempre es logging_rotatingfile_example.out, y cada vez que alcanza el límite de tamaño se renombra con el sufijo .1. Se cambia el nombre de cada uno de los archivos de copia de seguridad existentes para incrementar el sufijo (.1 se convierte en .2, etc.) y se borra el archivo .5.

Nota

Obviamente, este ejemplo establece la longitud del registro demasiado pequeña como un ejemplo extremo. Establece maxBytes en un valor más apropiado en un programa real.

Niveles de verbosidad

Otra característica útil de la interfaz de programación de logging es la capacidad de producir diferentes mensajes en diferentes niveles de registro. Esto significa que el código se puede instrumentar con mensajes de depuración, por ejemplo, y el nivel de registro se puede configurar para que esos mensajes de depuración no se escriban en un sistema de producción. the table below enumera los niveles de registro definidos por logging.

Niveles de registro
Nivel Valor
CRITICAL 50
ERROR 40
WARNING 30
INFO 20
DEBUG 10
UNSET 0

El mensaje de registro solo se emite si el controlador y el registrador están configurados para emitir mensajes de ese nivel o superior. Por ejemplo, si un mensaje es CRITICAL y el registrador está configurado en ERROR, se emite el mensaje (50> 40). Si un mensaje es una WARNING, y el registrador está configurado para producir solo mensajes configurados en ERROR, el mensaje no se emite (30 <40).

logging_level_example.py
import logging
import sys

LEVELS = {
    'debug': logging.DEBUG,
    'info': logging.INFO,
    'warning': logging.WARNING,
    'error': logging.ERROR,
    'critical': logging.CRITICAL,
}

if len(sys.argv) > 1:
    level_name = sys.argv[1]
    level = LEVELS.get(level_name, logging.NOTSET)
    logging.basicConfig(level=level)

logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical error message')

Ejecuta la secuencia de comandos con un argumento como “debug” o “warning” ara ver qué mensajes aparecen en diferentes niveles:

$ python3 logging_level_example.py debug

DEBUG:root:This is a debug message
INFO:root:This is an info message
WARNING:root:This is a warning message
ERROR:root:This is an error message
CRITICAL:root:This is a critical error message

$ python3 logging_level_example.py info

INFO:root:This is an info message
WARNING:root:This is a warning message
ERROR:root:This is an error message
CRITICAL:root:This is a critical error message

Instancias de registro con nombres

Todos los mensajes de registro anteriores tienen “raíz” incrustada porque el código usa el registrador raíz. Una manera fácil de saber de dónde proviene un mensaje de registro específico es usar un objeto de registro por separado para cada módulo. Los mensajes de registro enviados a un registrador incluyen el nombre de ese registrador. Aquí hay un ejemplo de cómo registrar desde diferentes módulos para que sea fácil rastrear la fuente del mensaje.

logging_modules_example.py
import logging

logging.basicConfig(level=logging.WARNING)

logger1 = logging.getLogger('package1.module1')
logger2 = logging.getLogger('package2.module2')

logger1.warning('This message comes from one module')
logger2.warning('This comes from another module')

La salida muestra los diferentes nombres de módulos para cada línea de salida.

$ python3 logging_modules_example.py

WARNING:package1.module1:This message comes from one module
WARNING:package2.module2:This comes from another module

El árbol de registro

Las instancias de Logger se configuran en una estructura de árbol, en función de sus nombres, como se ilustra en the figure. Normalmente, cada aplicación o biblioteca define un nombre base, con registradores para módulos individuales configurados como elementos secundarios. El registrador raíz no tiene nombre.

digraph {
   rankdir = BT;

   node [shape = doublecircle];
   "";

   node [shape = rect];
   "myapp" -> "";
   "package1" -> "";
   "package1.module1" -> "package1";
   "package2" -> "";
   "package2.module2" -> "package2";
}

Ejemplo de árbol de registro

La estructura de árbol es útil para configurar el registro porque significa que cada registrador no necesita su propio conjunto de controladores. Si un registrador no tiene ningún controlador, el mensaje se entrega a su padre para su procesamiento. Esto significa que para la mayoría de las aplicaciones solo es necesario configurar controladores en el registrador raíz, y toda la información de registro se recopilará y enviará al mismo lugar, como se muestra en la the figure.

digraph {
   rankdir = BT;

   node [shape = doublecircle];
   "";

   node [shape = rect];
   "package1" -> "";
   "package1.module2" -> "package1";
   "package2" -> "";
   "package2.module2" -> "package2";
   "myapp" -> "";

   node [shape = note];
   "" -> "/var/log/app.log";

   {rank = same; ""; "/var/log/app.log"}
}

Un controlador de registro

La estructura de árbol también permite establecer diferentes niveles de verbosidad, controladores y formateadores para diferentes partes de la aplicación o biblioteca para controlar qué mensajes se registran y hacia dónde van, como en la the figure.

digraph {
   rankdir = BT;

   node [shape = doublecircle];
   "";

   node [shape = rect];
   "package1" -> "" [label="level=INFO"];
   "package1.module2" -> "package1";
   "package2" -> "" [label="level=WARNING"];
   "package2.module2" -> "package2";
   "myapp" -> "" [label="level=DEBUG"];
}

Diferentes niveles y controladores

Integración con el módulo warnings

El módulo de registro se integra con warnings a través de captureWarnings(), que configura warnings para enviar mensajes a través del sistema de registro en lugar de enviarlos directamente.

logging_capture_warnings.py
import logging
import warnings

logging.basicConfig(
    level=logging.INFO,
)

warnings.warn('This warning is not sent to the logs')

logging.captureWarnings(True)

warnings.warn('This warning is sent to the logs')

El mensaje de advertencia se envía a un registrador llamado py.warnings usando el nivel WARNING.

$ python3 logging_capture_warnings.py

logging_capture_warnings.py:13: UserWarning: This warning is not
 sent to the logs
  warnings.warn('This warning is not sent to the logs')
WARNING:py.warnings:logging_capture_warnings.py:17: UserWarning:
 This warning is sent to the logs
  warnings.warn('This warning is sent to the logs')

Ver también