Adicionar contexto aos logs é uma prática essencial em aplicações reais. Em vez de registrar apenas uma mensagem como “falha ao processar pedido”, é muito mais útil incluir informações como identificador da requisição, usuário, serviço, ambiente e número do pedido. O logging.LoggerAdapter foi criado para facilitar esse tipo de enriquecimento. Nas versões recentes do Python, o parâmetro merge_extra torna o comportamento ainda mais flexível ao permitir combinar o contexto fixo do adaptador com dados extras enviados em cada chamada de log.
Neste guia, você vai entender como o merge_extra funciona, quando usá-lo, quais problemas ele resolve e como evitar colisões de chaves e vazamento de dados sensíveis.
O que é LoggerAdapter
O LoggerAdapter envolve um logger comum e injeta dados adicionais em cada registro. Ele é útil quando várias mensagens compartilham o mesmo contexto. Por exemplo, durante o processamento de uma requisição, todos os logs podem carregar o mesmo request_id.
import logging
logging.basicConfig(
level=logging.INFO,
format="%(levelname)s %(request_id)s %(message)s",
)
logger = logging.getLogger("api")
adapter = logging.LoggerAdapter(logger, {"request_id": "req-123"})
adapter.info("requisição recebida")
O adaptador adiciona o dicionário informado em extra ao objeto LogRecord. O formatador pode então acessar essas chaves.
O problema antes de merge_extra
Tradicionalmente, o contexto armazenado no adaptador tinha prioridade sobre um extra enviado na chamada. Isso significa que dados específicos da mensagem podiam ser descartados ou substituídos. Em sistemas com observabilidade detalhada, essa limitação obrigava a criar vários adaptadores, mesclar dicionários manualmente ou implementar uma subclasse personalizada.
O parâmetro merge_extra=True permite combinar os dois conjuntos de dados. O contexto permanente do adaptador continua disponível, mas cada chamada pode acrescentar campos específicos.
Exemplo com merge_extra
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"},
)
O resultado contém os campos do adaptador e o campo fornecido somente naquela chamada. Isso evita recriar o contexto completo em cada mensagem.
Quem vence em uma colisão
Ao combinar dicionários, você precisa saber qual valor prevalece quando a mesma chave aparece nos dois lados. Com merge_extra=True, os dados enviados na chamada podem sobrescrever valores equivalentes do adaptador. Esse comportamento é útil quando um campo precisa ser refinado, mas deve ser usado com cuidado.
logger.info(
"usuário temporariamente substituído",
extra={"user_id": "u-admin", "order_id": "ord-1002"},
)
Se a equipe considera user_id imutável durante a requisição, permitir essa substituição pode gerar logs incorretos. Uma alternativa é reservar chaves diferentes, como actor_user_id e target_user_id.
Contexto por requisição
Em aplicações web, crie um adaptador no início da requisição e passe-o para serviços internos. Isso mantém os logs correlacionados sem depender de variáveis globais.
def criar_logger_requisicao(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 processar_pagamento(logger, payment_id):
logger.info(
"pagamento iniciado",
extra={"payment_id": payment_id, "stage": "start"},
)
Esse padrão combina bem com o artigo sobre logging no Python, com o guia de contextvars no Python, com o conteúdo sobre decorators no Python e com o tutorial de exceções no Python.
Uso com JSON
Logs estruturados são mais fáceis de consultar em plataformas como Elasticsearch, Loki, Datadog e CloudWatch. O LoggerAdapter não transforma automaticamente o registro em JSON, mas fornece os campos que um formatador JSON pode serializar.
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)
Para produção, prefira bibliotecas maduras ou um formatador bem testado. A documentação oficial de logging do Python descreve o LoggerAdapter, e o Logging Cookbook reúne padrões avançados.
Evite chaves reservadas
O objeto LogRecord já possui atributos como name, levelname, message, filename e module. Tentar sobrescrever esses nomes por meio de extra pode gerar erro. Defina uma convenção de chaves, por exemplo, sempre usar nomes como request_id, customer_id, job_id e operation.
Dados sensíveis
Não registre senhas, tokens, cookies completos, chaves de API, números de cartão ou documentos pessoais. O fato de merge_extra facilitar o acréscimo de campos aumenta também o risco de incluir informações demais. Crie uma lista explícita de campos permitidos.
CAMPOS_PERMITIDOS = {"order_id", "payment_id", "stage"}
def extra_seguro(dados):
return {
chave: valor
for chave, valor in dados.items()
if chave in CAMPOS_PERMITIDOS
}
Compatibilidade entre versões
Antes de usar merge_extra, confirme a versão mínima do Python do projeto. Em ambientes antigos, o construtor pode não aceitar o argumento. Uma estratégia é criar uma função de compatibilidade que inspecione a assinatura ou use uma subclasse própria quando necessário.
import inspect
import logging
def criar_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)
Testes automatizados
Teste não apenas a mensagem, mas também os campos adicionados. O unittest oferece assertLogs, e no pytest o fixture caplog permite inspecionar registros.
def test_contexto_do_log(caplog):
base = logging.getLogger("teste")
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"
Quando usar
Use LoggerAdapter com merge_extra quando existe um contexto estável compartilhado por várias mensagens e, ao mesmo tempo, cada evento precisa acrescentar dados próprios. É ideal para requisições HTTP, tarefas em filas, comandos de CLI, pipelines de dados e operações distribuídas.
Para contextos profundamente assíncronos, contextvars pode ser mais conveniente. Em bibliotecas reutilizáveis, filtros de logging também podem funcionar melhor. O adaptador é uma solução simples, explícita e fácil de testar.
Conclusão
O parâmetro merge_extra torna o LoggerAdapter mais útil para logs estruturados. Ele permite manter campos permanentes, como identificadores de requisição e serviço, e combinar informações específicas de cada evento sem duplicação manual. Com convenções de nomes, testes, controle de colisões e proteção de dados sensíveis, esse recurso ajuda a construir logs mais claros e confiáveis.







