Este artículo cubre los fundamentos del uso del módulo de registro estándar que viene con todas las distribuciones de Python. Después de leerlo, deberías ser capaz de integrar fácilmente el registro en tu aplicación Python.
Módulo de registro de la biblioteca estándar
Python viene con un módulo de registro en la biblioteca estándar que proporciona un marco flexible para emitir mensajes de registro desde los programas Python. Este módulo es ampliamente utilizado por las bibliotecas y es el primer punto de acceso para la mayoría de los desarrolladores cuando se trata de registro.
El módulo proporciona una forma para que las aplicaciones configuren diferentes manejadores de registro y una forma de enrutar los mensajes de registro a estos manejadores. Esto permite una configuración muy flexible que puede hacer frente a una gran cantidad de casos de uso diferentes.
Para emitir un mensaje de registro, un llamador primero solicita un registrador con nombre. El nombre puede ser utilizado por la aplicación para configurar diferentes reglas para diferentes registradores. Este registrador entonces puede ser utilizado para emitir mensajes de formato simple en diferentes niveles de registro (DEBUG, INFO, ERROR, etc.), que de nuevo puede ser utilizado por la aplicación para manejar los mensajes de mayor prioridad diferente a los de menor prioridad. Aunque pueda parecer complicado, puede ser tan sencillo como esto:
import logginglog = logging.getLogger("my-logger")log.info("Hello, world")
Internamente, el mensaje se convierte en un objeto LogRecord y se dirige a un objeto Handler registrado para este logger. El handler utilizará entonces un Formatter para convertir el LogRecord en una cadena y emitir esa cadena.
Afortunadamente, la mayoría de las veces los desarrolladores no tienen que estar al tanto de los detalles. La documentación de Python contiene un excelente artículo sobre el módulo de registro y cómo funciona todo junto. El resto de este artículo se centrará en las mejores prácticas en lugar de todas las formas posibles de utilizar este módulo.
Niveles de registro
No todos los mensajes de registro se crean igual. Los niveles de registro se enumeran aquí en la documentación de Python; los incluiremos aquí como referencia. Cuando estableces un nivel de registro en Python usando el módulo estándar, le estás diciendo a la biblioteca que quieres manejar todos los eventos a partir de ese nivel. Si estableces el nivel de registro en INFO, incluirá los mensajes INFO, WARNING, ERROR y CRITICAL. Los mensajes NOTSET y DEBUG no se incluirán aquí.
Nivel | Valor numérico | Crítico | 50 |
ERROR | 40 |
AVISO | 30 | INFO | 20 | DEBUG | 10 |
NOTSET | 0 |
Registro desde módulos
Una aplicación deorganizada de Python está probablemente compuesta por muchos módulos. A veces estos módulos están pensados para ser utilizados por otros programas, pero a menos que estés desarrollando intencionadamente módulos reutilizables dentro de tu aplicación, lo más probable es que utilices módulos disponibles en pypi y módulos que escribas tú mismo específicos para tu aplicación.
En general, un módulo debería emitir mensajes de registro como mejor práctica y no debería configurar cómo se manejan esos mensajes. Eso es responsabilidad de la aplicación.
La única responsabilidad que tienen los módulos es facilitar a la aplicación el enrutamiento de sus mensajes de registro. Por esta razón, es una convención para cada módulo simplemente usar un logger llamado como el propio módulo. Esto facilita a la aplicación el enrutamiento de los diferentes módulos de manera diferente, mientras que también mantiene el código de registro en el módulo simple. El módulo sólo necesita dos líneas para configurar el registro, y luego usar el registrador nombrado:
import logginglog = logging.getLogger(__name__)def do_something(): log.debug("Doing something!")
Eso es todo. En Python, __name__
contiene el nombre completo del módulo actual, así que esto simplemente funcionará en cualquier módulo.
Configuración del registro
Tu aplicación principal debe configurar el subsistema de registro para que los mensajes de registro vayan a donde deben. El módulo de registro de Python proporciona un gran número de maneras de afinar esto, pero para casi todas las aplicaciones, la configuración puede ser muy simple.
En general, una configuración consiste en añadir un Formatter
y un Handler
al registrador raíz. Debido a que esto es tan común, el módulo de registro proporciona una función de utilidad llamada basicConfig
que maneja la mayoría de los casos de uso.
Las aplicaciones deben configurar el registro tan pronto como sea posible, preferiblemente como lo primero en la aplicación, para que los mensajes de registro no se pierdan durante el inicio.
Por último, las aplicaciones deben envolver un bloque try/except alrededor del código principal de la aplicación para enviar cualquier excepción a través de la interfaz de registro en lugar de sólo a stderr
. Esto se conoce como un manejador global de try catch. No debería ser donde manejes todo tu registro de excepciones, deberías continuar planeando las excepciones en bloques try catch en los puntos necesarios de tu código como regla general.
Ejemplo 1 – Registro en la salida estándar para Systemd
Esta es la opción más simple y probablemente la mejor para configurar el registro en estos días. Cuando se utiliza systemd para ejecutar un demonio, las aplicaciones pueden simplemente enviar mensajes de registro a stdout o stderr y hacer que systemd reenvíe los mensajes a journald y syslog. Como ventaja adicional, esto ni siquiera requiere capturar excepciones, ya que Python ya las escribe en el error estándar. Dicho esto, sigue la convención adecuada y maneja tus excepciones.
En el caso de ejecutar Python en contenedores como Docker, el registro en la salida estándar es también a menudo el movimiento más fácil ya que esta salida puede ser gestionada directamente y fácilmente por el propio contenedor.
import loggingimport oslogging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))exit(main())
Eso es todo. La aplicación registrará ahora todos los mensajes con nivel INFO o superior en stderr
, utilizando un formato sencillo:
ERROR:the.module.name:The log message
La aplicación puede incluso configurarse para incluir mensajes DEBUG, o quizás sólo ERROR, estableciendo la variable de entorno LOGLEVEL
.
El único problema de esta solución es que las excepciones se registran como múltiples líneas, lo que puede causar problemas para su posterior análisis. Lamentablemente, configurar Python para que envíe las excepciones de varias líneas como una sola línea no es tan sencillo, pero ciertamente es posible. Fíjate en la implementación de aquí abajo, llamando a logging.exception
es la abreviatura equivalente a logging.error(..., exc_info=True)
.
import loggingimport os class OneLineExceptionFormatter(logging.Formatter): def formatException(self, exc_info): result = super().formatException(exc_info) return repr(result) def format(self, record): result = super().format(record) if record.exc_text: result = result.replace("\n", "") return result handler = logging.StreamHandler()formatter = OneLineExceptionFormatter(logging.BASIC_FORMAT)handler.setFormatter(formatter)root = logging.getLogger()root.setLevel(os.environ.get("LOGLEVEL", "INFO"))root.addHandler(handler) try: exit(main())except Exception: logging.exception("Exception in main(): ") exit(1)
Ejemplo 2 – Syslog
La alternativa es enviarlo directamente a syslog. Esto es genial para sistemas operativos antiguos que no tienen systemd. En un mundo ideal esto debería ser sencillo, pero lamentablemente, Python requiere una configuración un poco más elaborada para poder enviar mensajes de registro unicode. Aquí hay un ejemplo de implementación.
import loggingimport logging.handlersimport os class SyslogBOMFormatter(logging.Formatter): def format(self, record): result = super().format(record) return "ufeff" + result handler = logging.handlers.SysLogHandler('/dev/log')formatter = SyslogBOMFormatter(logging.BASIC_FORMAT)handler.setFormatter(formatter)root = logging.getLogger()root.setLevel(os.environ.get("LOGLEVEL", "INFO"))root.addHandler(handler) try: exit(main())except Exception: logging.exception("Exception in main()") exit(1)
Ejemplo 3 – Archivo de registro
La última opción es registrar los mensajes directamente en un archivo. Esto es raramente útil en estos días, ya que los administradores pueden configurar syslog para escribir ciertos mensajes en archivos específicos, o si se despliega dentro de contenedores, esto es un anti-patrón. Además, si se utiliza el registro centralizado, tener que lidiar con archivos de registro adicionales es una preocupación añadida. Pero es una opción que todavía está disponible.
Cuando se registra en archivos, lo principal a tener en cuenta es que los archivos de registro deben ser rotados regularmente. La aplicación necesita detectar el archivo de registro que está siendo renombrado y manejar esa situación. Aunque Python proporciona su propio controlador de rotación de archivos, es mejor dejar la rotación de registros a herramientas dedicadas como logrotate. El WatchedFileHandler
hará un seguimiento del archivo de registro y lo reabrirá si se rota, lo que hace que funcione bien con logrotate sin requerir ninguna señal específica.
Aquí tienes un ejemplo de implementación.
import loggingimport logging.handlersimport os handler = logging.handlers.WatchedFileHandler( os.environ.get("LOGFILE", "/var/log/yourapp.log"))formatter = logging.Formatter(logging.BASIC_FORMAT)handler.setFormatter(formatter)root = logging.getLogger()root.setLevel(os.environ.get("LOGLEVEL", "INFO"))root.addHandler(handler) try: exit(main())except Exception: logging.exception("Exception in main()") exit(1)
Otros destinos
Es posible utilizar otros destinos de registro, y ciertos frameworks hacen uso de esto (por ejemplo, Django puede enviar ciertos mensajes de registro como correo electrónico). El HTTPHandler
puede ser útil cuando se ejecuta en un PaaS y no se tiene acceso directo al host para configurar el syslog o se está detrás de un firewall que bloquea el syslog saliente, y se puede utilizar para registrar directamente a los sistemas de registro centralizados como SolarWinds® Loggly®.
Muchos de los manejadores de registro más elaborados en la biblioteca de registro pueden bloquear fácilmente la aplicación, causando interrupciones simplemente porque la infraestructura de registro no respondía. Por estas razones, es mejor mantener la configuración de registro de una aplicación lo más simple posible.
Una tendencia creciente en el registro en general es separarlo tanto como sea posible de la funcionalidad principal de su aplicación. De esta manera puedes tener un comportamiento diferente en diferentes entornos o contextos de despliegue. Utilizar el HTTPHandler con un sistema como Loggly es una forma sencilla de conseguir esto directamente en tu aplicación.
Cuando despliegues en contenedores, intenta mantener las cosas lo más simples posible. Registre en el estándar out/err y confíe en su anfitrión de contenedor o plataforma de orquestación para manejar averiguar qué hacer con los registros. Usted todavía puede utilizar los servicios de centralización de registro, pero con un enfoque de sidecar o log shipper.
Si la configuración de grano fino es deseable, el módulo de registro también proporciona la capacidad de cargar la configuración de registro de un archivo de configuración. Esto es bastante potente, pero rara vez es necesario. Al cargar la configuración de registro desde un archivo, especifique disable_existing_loggers=False
. El valor por defecto, que está ahí sólo por compatibilidad con versiones anteriores, deshabilitará cualquier registrador creado por los módulos. Esto rompe muchos módulos, así que úsalo con precaución.
Resumen
El registro en Python es simple y bien estandarizado, gracias a un poderoso marco de registro justo en la biblioteca estándar.
Los módulos deberían simplemente registrar todo en una instancia de registro para su nombre de módulo. Esto hace que sea fácil para la aplicación dirigir los mensajes de registro de diferentes módulos a diferentes lugares, si es necesario.
Las aplicaciones tienen entonces varias opciones para configurar el registro. En una infraestructura moderna, sin embargo, seguir las mejores prácticas simplifica mucho esto. A menos que sea específicamente necesario, simplemente registrar en stdout
stderr
y dejar que system
o su contenedor gestione los mensajes de registro es suficiente y el mejor enfoque.
Véalo. Analícelo. Inspecciónalo. Resuélvelo
Ve lo que importa.
Comienza la prueba gratuita
0 comentarios