Gestión de Registros en Python

La gestión de registros (logging) es fundamental para el desarrollo y depruación de software. Python proporciona el módulo logging para esta tarea, permitiendo registrar eventos, errores y otros estados de la aplicación.

Configuración Básica del Módulo Logging

Por defecto, el módulo logging de Python dirige los mensajes a la salida estándar (consola) y solo muestra aquellos de nivel WARNING o superior. Los niveles de registro, en orden de severidad de menor a mayor, son: DEBUG, INFO, WARNING, ERROR y CRITICAL.

Configuración Funcional Sencilla

Una configuración simple se puede realizar directamente llamando a las funciones de registro:


import logging

logging.debug('Mensaje de depuración')
logging.info('Mensaje informativo')
logging.warning('Mensaje de advertencia')
logging.error('Mensaje de error')
logging.critical('Mensaje crítico')
   

Configuración Flexible

Para una mayor flexibilidad en cuanto a nivel de registor, formato y destino de los mensajes, se utiliza la función basicConfig:


import logging

logging.basicConfig(
   level=logging.DEBUG,  # Nivel mínimo a registrar
   format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',  # Formato del mensaje
   datefmt='%a, %d %b %Y %H:%M:%S',  # Formato de fecha y hora
   filename='./test.log',  # Archivo de destino para los registros
   filemode='w'  # Modo de apertura del archivo ('w' para sobrescribir, 'a' para añadir)
)

logging.debug('Mensaje de depuración')
logging.info('Mensaje informativo')
logging.warning('Mensaje de advertencia')
logging.error('Mensaje de error')
logging.critical('Mensaje crítico')
   

Parámetros de Configuración

La función basicConfig acepta varios parámetros:

  • filename: Nombre del archivo donde se guardarán los registros.
  • filemode: Modo de apertura del archivo ('a' por defecto, 'w' para sobrescribir).
  • format: Cadena de formato para los mensajes de registro.
  • datefmt: Formato para la fecha y hora.
  • level: Nivel mínimo de registro (logging.DEBUG, logging.INFO, etc.).
  • stream: Un objeto stream (como sys.stderr o un archivo abierto) para enviar los registros. Si se especifica filename, este parámetro se ignora.

Los especificadores de formato comunes incluyen:

  • %(name)s: Nombre del logger.
  • %(levelname)s: Nivel de texto del mensaje (DEBUG, INFO, etc.).
  • %(message)s: El mensaje de registro.
  • %(asctime)s: Hora en que se creó el mensaje.
  • %(filename)s: Nombre del archivo donde se generó el mensaje.
  • %(lineno)d: Número de línea en el archivo.

Configuración mediante un Objeto Logger

Para un control más granular, se pueden crear objetos Logger explícitos, asociándoles Handlers (para definir destinos) y Formatters (para definir el formato).


import logging

# Obtener un logger (si no existe, se crea)
logger = logging.getLogger('mi_aplicacion')
logger.setLevel(logging.DEBUG)  # Establecer nivel mínimo para este logger

# Crear un handler para escribir en un archivo
fh = logging.FileHandler('app.log', encoding='utf-8')
fh.setLevel(logging.DEBUG) # Nivel para este handler

# Crear un handler para la salida a consola
ch = logging.StreamHandler()
ch.setLevel(logging.INFO) # Nivel para este handler

# Crear un formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# Asignar el formatter a los handlers
fh.setFormatter(formatter)
ch.setFormatter(formatter)

# Añadir los handlers al logger
logger.addHandler(fh)
logger.addHandler(ch)

# Registrar mensajes
logger.debug('Mensaje de depuración del logger')
logger.info('Mensaje informativo del logger')
logger.warning('Mensaje de advertencia del logger')
logger.error('Mensaje de error del logger')
logger.critical('Mensaje crítico del logger')
   

Los componentes clave del módulo logging son: Logger, Handler, Filter y Formatter.

Configuración mediante Dicionario

Una forma avanzada y recomendada de configurar el módulo logging es utilizando un diccionario, lo que permite definir de manera estructurada los formatters, handlers, filters y loggers.


import logging
import logging.config

LOGGING_CONFIG = {
   'version': 1,
   'disable_existing_loggers': False,
   'formatters': {
       'standard': {
           'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d][%(levelname)s][%(message)s]'
       },
       'simple': {
           'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
       },
   },
   'handlers': {
       'console': {
           'level': 'DEBUG',
           'class': 'logging.StreamHandler',
           'formatter': 'simple'
       },
       'file_handler': {
           'level': 'DEBUG',
           'class': 'logging.handlers.RotatingFileHandler',
           'formatter': 'standard',
           'filename': 'logs/app_debug.log',
           'maxBytes': 1024 * 1024 * 5,  # 5MB
           'backupCount': 5,
           'encoding': 'utf-8',
       },
   },
   'loggers': {
       '': {  # Logger raíz, captura todos los mensajes no capturados por otros loggers
           'handlers': ['file_handler', 'console'],
           'level': 'DEBUG',
           'propagate': True,
       },
       'modulo_especifico': {
           'handlers': ['file_handler'],
           'level': 'INFO',
           'propagate': False,
       }
   },
}

logging.config.dictConfig(LOGGING_CONFIG)

logger_raiz = logging.getLogger() # Logger raíz
logger_raiz.info('Mensaje desde el logger raíz')

logger_especifico = logging.getLogger('modulo_especifico')
logger_especifico.debug('Este mensaje no se mostrará en consola ni archivo específico')
logger_especifico.info('Este mensaje se escribirá en el archivo app_debug.log')
   

Procesamiento Asíncrono de Registros

Para aplicaciones de alto rendimiento, el registro síncrono puede convertirse en un cuello de botella. El módulo logging soporta, con configuraciones adicionales o implementaciones personalizadas, el registro asíncrono para mitigar este problema. Esto a menudo implica la creación de colas de mensajes y workers dedicados que procesan los registros en segundo plano.

Gestión de Registros en Django

Django ofrece una robusta integración con el módulo logging de Python. La configuración se realiza típicamente en el archivo settings.py dentro de la variable LOGGING.

Configuración en settings.py


# settings.py

LOGGING = {
   'version': 1,
   'disable_existing_loggers': False,
   'formatters': {
       'verbose': {
           'format': '[%(asctime)s] %(levelname)s [%(name)s:%(lineno)d] %(message)s'
       },
       'simple': {
           'format': '[%(levelname)s] %(message)s'
       },
   },
   'handlers': {
       'console': {
           'level': 'DEBUG',
           'class': 'logging.StreamHandler',
           'formatter': 'simple'
       },
       'file': {
           'level': 'INFO',
           'class': 'logging.handlers.RotatingFileHandler',
           'formatter': 'verbose',
           'filename': 'logs/django_app.log',
           'maxBytes': 1024 * 1024 * 5,  # 5MB
           'backupCount': 5,
           'encoding': 'utf-8',
       },
   },
   'loggers': {
       'django': {
           'handlers': ['console', 'file'],
           'level': 'INFO',
           'propagate': False,
       },
       'mi_app': {  # Logger para tu aplicación
           'handlers': ['file'],
           'level': 'DEBUG',
           'propagate': False,
       },
   }
}
   

Dentro de las vistas o cualquier otro componente de Django, se puede acceder al logger configurado:


# views.py
import logging

logger_mi_app = logging.getLogger('mi_app')

def mi_vista(request):
   logger_mi_app.debug(f"Acceso a la vista: {request.path}")
   # ... lógica de la vista ...
   return HttpResponse("Hello")
   

Configuración mediante Middleware

Se pueden crear middlewares personalizados para registrar información contextual en cada solicitud/respuesta, como la IP del cliente, el método HTTP, el código de estado, etc.


# middleware.py
import logging
import json
import socket
import threading

from django.utils.deprecation import MiddlewareMixin

# Usar un objeto thread-local para almacenar información específica de la solicitud
thread_locals = threading.local()

class RequestResponseLoggerFilter(logging.Filter):
   def filter(self, record):
       record.client_ip = getattr(thread_locals, 'client_ip', 'N/A')
       record.request_method = getattr(thread_locals, 'request_method', 'N/A')
       record.request_path = getattr(thread_locals, 'request_path', 'N/A')
       record.response_status = getattr(thread_locals, 'response_status', 'N/A')
       record.request_body = getattr(thread_locals, 'request_body', '{}')
       return True

class RequestResponseLoggingMiddleware(MiddlewareMixin):
   def __init__(self, get_response=None):
       self.get_response = get_response
       self.logger = logging.getLogger('mi_app') # Usar el logger configurado
       self.request_filter = RequestResponseLoggerFilter()
       self.logger.addFilter(self.request_filter)

   def __call__(self, request):
       # Registrar información de la solicitud
       try:
           body = json.loads(request.body)
       except (json.JSONDecodeError, ValueError):
           body = {}
       
       thread_locals.client_ip = request.META.get('REMOTE_ADDR', 'N/A')
       thread_locals.request_method = request.method
       thread_locals.request_path = request.path
       thread_locals.request_body = json.dumps(body)

       response = self.get_response(request)

       # Registrar información de la respuesta
       thread_locals.response_status = response.status_code

       # Registrar el evento completo
       self.logger.info(
           f"Request: {thread_locals.request_method} {thread_locals.request_path} | "
           f"Client IP: {thread_locals.client_ip} | "
           f"Body: {thread_locals.request_body} | "
           f"Status: {thread_locals.response_status}"
       )

       # Limpiar datos de thread-local después de procesar la solicitud
       del thread_locals.client_ip
       del thread_locals.request_method
       del thread_locals.request_path
       del thread_locals.request_body
       del thread_locals.response_status

       return response
   

Este middleware debe ser añadido a la lista MIDDLEWARE en settings.py.

Etiquetas: Python logging configuracion Django Middleware

Publicado el 7-21 22:31