LoggerAdapter merge_extra: contexto dinámico en logs

Publicado el: 03/10/2026
Tempo de leitura: 5 minutos
Desarrollador configurando logs estructurados con LoggerAdapter merge_extra en Python

Los registros útiles necesitan contexto. Un mensaje como “falló el pago” resulta mucho más práctico cuando incluye el identificador de la solicitud, el usuario, el servicio, el entorno, el pago y la etapa del proceso. logging.LoggerAdapter permite añadir contexto compartido a muchos registros. En versiones recientes de Python, el parámetro merge_extra facilita combinar ese contexto permanente con el diccionario extra enviado en una llamada concreta.

En esta guía aprenderás qué cambia merge_extra, cómo usarlo en logs estructurados, cómo manejar colisiones de claves y cómo evitar que información sensible termine en los registros.

Qué hace LoggerAdapter

LoggerAdapter envuelve un logger normal y agrega campos a cada registro. Es útil cuando una secuencia de mensajes pertenece a la misma solicitud, tarea, cliente o transacción.

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(levelname)s %(request_id)s %(message)s",
)

base_logger = logging.getLogger("api")
logger = logging.LoggerAdapter(
    base_logger,
    {"request_id": "req-123"},
)

logger.info("solicitud recibida")

El adaptador coloca su diccionario en el LogRecord. Después, un formateador puede incluir esos valores en texto, JSON u otro formato.

Por qué merge_extra es importante

Sin fusión, el contexto guardado en el adaptador puede impedir que los campos específicos de una llamada se combinen como el desarrollador espera. Antes era común crear un adaptador nuevo, copiar diccionarios manualmente o implementar una subclase.

Con merge_extra=True, los campos permanentes y los campos enviados en una llamada se combinan. Este patrón es ideal cuando todos los eventos necesitan datos de correlación compartidos, pero cada evento también posee detalles propios.

Ejemplo básico

import logging

logging.basicConfig(
    level=logging.INFO,
    format=(
        "%(levelname)s request=%(request_id)s "
        "user=%(user_id)s order=%(order_id)s %(message)s"
    ),
)

base_logger = logging.getLogger("checkout")
logger = logging.LoggerAdapter(
    base_logger,
    {"request_id": "req-987", "user_id": "u-42"},
    merge_extra=True,
)

logger.info(
    "pedido validado",
    extra={"order_id": "ord-1001"},
)

El registro contiene los tres identificadores. La solicitud y el usuario pertenecen al adaptador, mientras que el pedido pertenece únicamente a ese evento.

Colisiones de claves

Cuando una clave aparece en ambos diccionarios, el valor específico de la llamada puede sustituir al valor del adaptador. Puede ser útil, pero también puede romper el significado de un contexto que debería permanecer estable.

logger.info(
    "acción realizada en nombre de otro usuario",
    extra={"user_id": "u-admin", "order_id": "ord-1002"},
)

Si user_id identifica al usuario autenticado durante toda la solicitud, reemplazarlo genera registros engañosos. Es mejor usar nombres separados como authenticated_user_id, actor_user_id y target_user_id. El conjunto de campos de logging también funciona como una API y debe documentarse.

Logging por solicitud

Un patrón limpio consiste en crear un adaptador al inicio de la solicitud y pasarlo a los servicios internos. Así se evita el estado global mutable y las dependencias quedan explícitas.

def crear_logger_solicitud(base_logger, request_id, user_id):
    return logging.LoggerAdapter(
        base_logger,
        {
            "request_id": request_id,
            "user_id": user_id,
            "service": "payments",
        },
        merge_extra=True,
    )


def procesar_pago(logger, payment_id):
    logger.info(
        "pago iniciado",
        extra={"payment_id": payment_id, "stage": "start"},
    )

Para ampliar estos conceptos, consulta los artículos de Academify sobre logging en Python, contextvars en Python, decoradores en Python y excepciones en Python.

Logs JSON estructurados

LoggerAdapter no genera JSON por sí solo. Enriquece el registro para que un formateador JSON pueda serializar los campos.

class JsonFormatter(logging.Formatter):
    def format(self, record):
        import json

        payload = {
            "level": record.levelname,
            "message": record.getMessage(),
            "request_id": getattr(record, "request_id", None),
            "user_id": getattr(record, "user_id", None),
            "order_id": getattr(record, "order_id", None),
        }
        return json.dumps(payload, ensure_ascii=False)

Los registros estructurados se filtran y agregan con facilidad en Elasticsearch, Loki, Datadog, CloudWatch y OpenSearch. La documentación oficial de logging de Python explica la API, y el Logging Cookbook muestra patrones avanzados.

Evita nombres reservados

Un LogRecord ya contiene atributos como name, levelname, filename, module y message. Intentar sobrescribir nombres reservados mediante extra puede producir una excepción. Define una convención estable, por ejemplo request_id, customer_id, job_id, operation y duration_ms.

Protección de datos sensibles

Nunca registres contraseñas, tokens de acceso, cookies completas, claves de API, datos de tarjetas o información personal innecesaria. Como la fusión facilita añadir diccionarios arbitrarios, también aumenta el riesgo de filtrar información. Usa una lista de campos permitidos.

CAMPOS_PERMITIDOS = {"order_id", "payment_id", "stage", "duration_ms"}


def extra_seguro(datos):
    return {
        clave: valor
        for clave, valor in datos.items()
        if clave in CAMPOS_PERMITIDOS
    }

También conviene truncar textos largos y normalizar valores. Un objeto enorme o mal formado no debería producir registros gigantes ni bloquear el sistema de observabilidad.

Compatibilidad de versiones

Comprueba la versión mínima de Python del proyecto antes de pasar merge_extra. Un intérprete antiguo puede rechazar el argumento. Una función de compatibilidad puede inspeccionar el constructor y usar el adaptador tradicional como alternativa.

import inspect
import logging


def crear_adapter(logger, contexto):
    parametros = inspect.signature(logging.LoggerAdapter).parameters
    if "merge_extra" in parametros:
        return logging.LoggerAdapter(
            logger,
            contexto,
            merge_extra=True,
        )
    return logging.LoggerAdapter(logger, contexto)

Si la fusión por llamada es imprescindible en versiones antiguas, crea una subclase pequeña cuyo método process combine los diccionarios de forma explícita. Prueba especialmente la prioridad en caso de colisión.

Pruebas automatizadas

Las pruebas deben verificar el mensaje y los campos adjuntos. En pytest, caplog permite inspeccionar los objetos LogRecord capturados.

def test_contexto_log(caplog):
    base = logging.getLogger("test")
    adapter = logging.LoggerAdapter(
        base,
        {"request_id": "req-test"},
        merge_extra=True,
    )

    with caplog.at_level(logging.INFO):
        adapter.info("ok", extra={"order_id": "ord-test"})

    registro = caplog.records[0]
    assert registro.request_id == "req-test"
    assert registro.order_id == "ord-test"

También es útil probar colisiones, campos opcionales ausentes, redacción y serialización JSON. El código de logging suele ejecutarse durante errores, por lo que no debe lanzar una segunda excepción que oculte el problema original.

Uso en código asíncrono

Pasar el adaptador de manera explícita funciona bien en muchas aplicaciones async. En frameworks con muchas capas, contextvars puede ser una forma más cómoda de mantener valores locales a la solicitud. Un filtro de logging puede copiar esos valores a cada registro. Ambas técnicas se complementan: las variables de contexto aportan datos estables y merge_extra añade campos específicos del evento.

Cuándo usar merge_extra

Úsalo cuando varios registros comparten contexto estable y cada evento necesita atributos adicionales. Casos comunes son solicitudes HTTP, tareas en colas, operaciones de CLI, pipelines de datos, tareas programadas y transacciones distribuidas.

Un logger simple es suficiente para scripts pequeños. Un filtro puede ser mejor para campos globales del proceso. Una variable de contexto puede ser mejor para contexto async implícito. El adaptador destaca cuando deseas contexto explícito, comprobable y ligado a una operación.

Lista de comprobación

  • Define nombres y significados estables.
  • Evita atributos reservados de LogRecord.
  • Decide si una llamada puede sobrescribir valores del adaptador.
  • Usa una lista permitida para datos de usuarios o solicitudes.
  • Prueba la salida formateada y los atributos del registro.
  • Confirma la compatibilidad con la versión de Python.
  • Evita que un fallo de logging oculte el error de la aplicación.

Conclusión

LoggerAdapter con merge_extra es una base práctica para logs estructurados en Python. Combina campos permanentes de correlación con detalles específicos del evento, reduce la manipulación repetitiva de diccionarios y mantiene el contexto visible en el código. Con nombres claros, reglas de colisión, pruebas, compatibilidad y protección de datos sensibles, los registros resultan más fáciles de buscar, depurar y confiar.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Código en una pantalla que representa t-strings en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    T-strings en Python 3.14: templates estructurados

    Aprende t-strings en Python 3.14, explora Template e Interpolation y crea renderizadores estructurados, extensibles y seguros.

    Ler mais

    Tempo de leitura: 6 minutos
    16/10/2026
    CPU y módulos de memoria que representan shared_memory track en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    shared_memory track: controla el resource tracker

    Aprende shared_memory track en Python para controlar el resource tracker, la propiedad y la limpieza entre procesos.

    Ler mais

    Tempo de leitura: 5 minutos
    16/10/2026
    Unidad de almacenamiento de datos que representa compression.zstd en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    compression.zstd: streaming y compresión segura

    Aprende compression.zstd en Python para comprimir archivos y datos con velocidad, streaming, diccionarios y límites seguros.

    Ler mais

    Tempo de leitura: 7 minutos
    15/10/2026
    Código en pantalla para diagnosticar procesos con sys.remote_exec en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    sys.remote_exec: diagnostica procesos Python

    Aprende sys.remote_exec en Python para diagnosticar procesos activos con scripts controlados, permisos seguros y buenas prácticas.

    Ler mais

    Tempo de leitura: 7 minutos
    15/10/2026
    Código Python y configuración de variables de entorno con os.reload_environ
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    os.reload_environ: threads y variables de entorno

    Aprende os.reload_environ en Python para actualizar variables de entorno externas con validación, compatibilidad y precauciones de concurrencia.

    Ler mais

    Tempo de leitura: 5 minutos
    14/10/2026
    Código Python que representa contextvars.Token y aislamiento de contexto asíncrono
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    contextvars.Token: restaura contexto async

    Aprende contextvars.Token en Python para valores temporales, restauración automática y aislamiento seguro entre tareas asíncronas.

    Ler mais

    Tempo de leitura: 7 minutos
    14/10/2026