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!