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 (comosys.stderro un archivo abierto) para enviar los registros. Si se especificafilename, 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.