Cuando desarrollamos aplicaciones Python, especialmente en entornos de producción, la capacidad de registrar eventos, errores e información de debug es fundamental. El módulo logging de Python es la herramienta estándar y más versátil para este propósito, ofreciendo mucho más que simples prints en la consola.
En esta guía completa, aprenderás desde los conceptos básicos hasta técnicas avanzadas de logging en Python, incluyendo configuración de handlers, formatters personalizados, filtros e integración con sistemas externos de monitoreo.
¿Qué es Python Logging?
El logging es el proceso de registrar información sobre la ejecución de un programa. Diferente del print(), el módulo logging ofrece una estructura jerárquica, múltiples niveles de severidad, configuración centralizada y la posibilidad de enviar logs a diferentes destinos simultáneamente.
Con un logging adecuado, puedes:
- Monitorear el comportamiento de la aplicación en producción
- Depurar problemas en entorno de desarrollo
- Auditar acciones de usuarios y eventos del sistema
- Integrar con herramientas de monitoreo como ELK, Datadog, New Relic
- Generar métricas de rendimiento y disponibilidad
Fuente: Python Documentation - Logging
Los 5 Niveles de Logging
Python define cinco niveles de logging, cada uno con un propósito específico:
import logging
# DEBUG - Información detallada para diagnóstico
logging.debug("Variable x = %d", x)
# INFO - Confirmación de que todo funciona correctamente
logging.info("Usuario %s ha iniciado sesión", username)
# WARNING - Algo inesperado ocurrió, pero la aplicación continúa
logging.warning("Memoria por encima del 80%%")
# ERROR - Problema serio que afectó una función
logging.error("Error al conectar con la base de datos")
# CRITICAL - Error crítico que puede detener la aplicación
logging.critical("¡El sistema de autenticación falló!")
Cada nivel tiene un valor numérico asociado: DEBUG=10, INFO=20, WARNING=30, ERROR=40, CRITICAL=50. Por defecto, solo se muestran WARNING y superiores.
Fuente: Real Python - Python Logging
Configuración Básica de Logging
La forma más simple de comenzar con logging es usando la función basicConfig():
import logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logging.info("¡Logging configurado exitosamente!")
Este código crea un logger raíz con salida a la consola. El formato incluye timestamp, nombre del logger, nivel y mensaje.
Configuración con diccionario (versión moderna)
A partir de Python 3.21, puedes usar diccionarios para la configuración:
import logging
import logging.config
config = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"standard": {
"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "standard",
"stream": "ext://sys.stdout"
},
"file": {
"class": "logging.FileHandler",
"level": "INFO",
"formatter": "standard",
"filename": "app.log",
"mode": "a"
}
},
"root": {
"level": "INFO",
"handlers": ["console", "file"]
}
}
logging.config.dictConfig(config)
Este enfoque es mucho más flexible y permite configuración sin modificar código, siendo ideal para entornos de producción.
Creando Loggers Personalizados
Para aplicaciones más grandes, es recomendable crear loggers específicos para cada módulo:
import logging
# Crear logger para un módulo específico
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# Agregar handler si no existe
if not logger.handlers:
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
# Usar el logger
logger.info("Procesando datos del usuario")
logger.error("Falló la validación")
Usar __name__ como argumento para getLogger() crea un logger con el nombre del módulo actual, lo que facilita la identificación del origen de los logs.
Handlers: Destinos de los Logs
Los handlers definen hacia dónde se enviarán los logs. Python ofrece varios tipos:
StreamHandler - Consola y Archivos
handler = logging.StreamHandler() # stdout (por defecto)
handler = logging.FileHandler('app.log', encoding='utf-8') # archivo
handler = logging.FileHandler('error.log', mode='a') # modo append
RotatingFileHandler - Logs con Rotación
Para evitar archivos de log enormes, usa rotación:
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler(
'app.log',
maxBytes=10 * 1024 * 1024, # 10 MB
backupCount=5 # mantener 5 archivos de respaldo
)
Cuando el archivo alcanza 10 MB, se renombra y se crea uno nuevo. El parámetro backupCount define cuántos archivos antiguos se mantienen.
Fuente: Python Logging Handlers
TimedRotatingFileHandler - Rotación por Tiempo
from logging.handlers import TimedRotatingFileHandler
handler = TimedRotatingFileHandler(
'app.log',
when='midnight', # rota a medianoche
interval=1,
backupCount=30 # mantener 30 días de logs
)
SysLogHandler - Enviar al Sistema
Para enviar logs a servidores syslog:
from logging.handlers import SysLogHandler
handler = SysLogHandler(address=('localhost', 514))
HTTPHandler - Enviar a API
Para enviar logs a servicios externos:
from logging.handlers import HTTPHandler
handler = HTTPHandler(
'api.monitoring.com',
'/logs',
method='POST'
)
Formatters: Formateando la Salida
Los formatters definen el diseño de los mensajes de log. Python ofrece varios atributos:
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
Atributos disponibles:
%(asctime)s- Timestamp formateado%(name)s- Nombre del logger%(levelname)s- Nivel del log (DEBUG, INFO, etc)%(levelno)s- Número del nivel%(message)s- El mensaje%(filename)s- Nombre del archivo%(funcName)s- Nombre de la función%(lineno)d- Número de línea%(process)d- ID del proceso%(thread)d- ID del hilo%(pathname)s- Ruta completa del archivo
Creando un Custom Formatter
class ColoredFormatter(logging.Formatter):
"""Formatter con colores para consola"""
grey = "\x1b[38;21m"
blue = "\x1b[38;5;39m"
yellow = "\x1b[38;5;226m"
red = "\x1b[38;5;196m"
bold_red = "\x1b[31;1m"
reset = "\x1b[0m"
FORMATS = {
logging.DEBUG: grey + "%(message)s" + reset,
logging.INFO: blue + "%(message)s" + reset,
logging.WARNING: yellow + "%(message)s" + reset,
logging.ERROR: red + "%(message)s" + reset,
logging.CRITICAL: bold_red + "%(message)s" + reset
}
def format(self, record):
log_fmt = self.FORMATS.get(record.levelno)
formatter = logging.Formatter(log_fmt)
return formatter.format(record)
Este formatter agrega colores a los logs en consola, facilitando la identificación visual del nivel de cada mensaje.
Filtros: Controlando el Flujo de Logs
Los filtros permiten un control más granular sobre qué mensajes se registran:
class SensitiveDataFilter(logging.Filter):
"""Filtro que elimina datos sensibles de los logs"""
SENSITIVE_PATTERNS = [
r'\b\d{3}-\d{2}-\d{4}\b', # CPF
r'\bpassword[=:]\S+', # Contraseña
r'\bapi_key[=:]\S+', # API Key
]
def filter(self, record):
import re
message = record.getMessage()
for pattern in self.SENSITIVE_PATTERNS:
message = re.sub(pattern, '***REDACTED***', message)
record.msg = message
return True
# Agregar filtro al handler
handler.addFilter(SensitiveDataFilter())
Este filtro es esencial para cumplimiento con LGPD/GDPR, eliminando información personal de los logs.
Filtro por Módulo
class ModuleFilter(logging.Filter):
"""Filtro que permite solo módulos específicos"""
def __init__(self, allowed_modules):
super().__init__()
self.allowed_modules = allowed_modules
def filter(self, record):
return record.name in self.allowed_modules or record.levelno >= logging.ERROR
Logging en Aplicaciones Web (Django/Flask)
Configuración para Django
# settings.py
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {message}',
'style': '{',
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
'file': {
'class': 'logging.FileHandler',
'filename': 'django.log',
'formatter': 'verbose',
},
},
'root': {
'handlers': ['console', 'file'],
'level': 'INFO',
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'WARNING',
'propagate': False,
},
'myapp': {
'handlers': ['console', 'file'],
'level': 'DEBUG',
'propagate': False,
},
},
}
Configuración para Flask
import logging
from logging.handlers import RotatingFileHandler
app = Flask(__name__)
if not app.debug:
file_handler = RotatingFileHandler(
'flask.log',
maxBytes=10240000,
backupCount=10
)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('Aplicación Flask iniciada')
Fuente: DigitalOcean - Python Logging
Logging Asíncrono
Para aplicaciones de alto rendimiento, considera logging asíncrono:
import logging
from logging.handlers import QueueHandler
import queue
import threading
# Crear cola para logs
log_queue = queue.Queue(-1)
# Handler que envía a la cola
queue_handler = QueueHandler(log_queue)
# Hilo que procesa la cola
def log_writer():
while True:
record = log_queue.get()
if record is None:
break
logger = logging.getLogger(record.name)
logger.handle(record)
# Iniciar hilo de escritura
writer_thread = threading.Thread(target=log_writer, daemon=True)
writer_thread.start()
# Configurar logger raíz con QueueHandler
root_logger = logging.getLogger()
root_logger.addHandler(queue_handler)
root_logger.setLevel(logging.DEBUG)
# Ahora todo el logging es asíncrono
logging.info("¡Logging asíncrono!")
Este enfoque es especialmente útil en aplicaciones web de alto tráfico donde el I/O de logging puede convertirse en un cuello de botella.
Mejores Prácticas de Logging
- Usa los niveles correctamente: No uses DEBUG en producción, ni ERROR para mensajes informativos.
- Incluye contexto: Agrega IDs de solicitud, nombres de usuario, datos relevantes.
- Evita datos sensibles: Nunca registres contraseñas, tokens o datos personales sin encriptación.
- Sé consistente: Usa el mismo formato en toda la aplicación.
- Configura rotación: Evita logs que consuman todo el espacio en disco.
- Monitorea tus logs: Usa herramientas como ELK, Datadog, Sentry.
- Documenta tu configuración: Toda la configuración debe estar documentada.
Ejemplo de Logger Estructurado
import logging
import json
from datetime import datetime
class JSONFormatter(logging.Formatter):
"""Formatter que salida JSON"""
def format(self, record):
log_data = {
"timestamp": datetime.utcnow().isoformat(),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
"module": record.module,
"function": record.funcName,
"line": record.lineno
}
if record.exc_info:
log_data["exception"] = self.formatException(record.exc_info)
return json.dumps(log_data)
Los logs en JSON son ideales para procesamiento por sistemas de monitoreo y agregación de logs.
Fuente: Loggly - Ultimate Guide to Python Logging
Logging y Manejo de Excepciones
Una práctica esencial es usar logging con manejo de excepciones:
import logging
import traceback
logger = logging.getLogger(__name__)
try:
result = process_data(data)
except Exception as e:
logger.error(
"Error al procesar datos: %s\n%s",
str(e),
traceback.format_exc()
)
raise
# Manera más limpia con logging.exception()
try:
result = process_data(data)
except Exception:
logger.exception("Error al procesar datos")
raise
El método logging.exception() automáticamente incluye el traceback completo.
Integración con Sistemas de Monitoreo
Sentry
import sentry_sdk
from sentry_sdk.integrations.logging import SentryHandler
sentry_sdk.init(
dsn="YOUR_SENTRY_DSN",
integrations=[LoggingIntegration()]
)
handler = SentryHandler()
logging.root.addHandler(handler)
Datadog
from datadog import DogStatsd
statsd = DogStatsd(host="localhost", port=8125)
class DatadogHandler(logging.Handler):
def emit(self, record):
if record.levelno >= logging.ERROR:
statsd.increment("log.error", tags=[
f"logger:{record.name}",
f"level:{record.levelname}"
])
logging.root.addHandler(DatadogHandler())
Conclusión
El módulo logging de Python es una herramienta extremadamente poderosa que va mucho más allá del simple print(). Con la configuración correcta de handlers, formatters y filtros, puedes crear un sistema de logging profesional adecuado para aplicaciones de cualquier tamaño.
Recuerda las mejores prácticas: usa niveles apropiados, incluye contexto relevante, evita datos sensibles y configura rotación de logs. Con un buen sistema de logging, el debugging y monitoreo se vuelve mucho más eficiente.
Continúa aprendiendo con las guías gratuitas del Universo Python: explora manejo de excepciones, tests automatizados con pytest, y crear APIs con FastAPI para elevar tus habilidades de desarrollo!