<h2>O módulo logging nativo do Python</h2>
<p>O módulo <code>logging</code> é parte da biblioteca padrão do Python e fornece um sistema flexível para registrar mensagens de eventos durante a execução de um programa. Ao contrário de simplesmente usar <code>print()</code>, o logging oferece controle sobre o nível de severidade das mensagens, permite direcioná-las para diferentes saídas (arquivo, console, email) e facilita a depuração em ambientes de produção.</p>
<p>O <code>logging</code> funciona através de uma hierarquia de componentes: <strong>loggers</strong> criam os registros, <strong>handlers</strong> determinam para onde as mensagens vão, <strong>formatadores</strong> definem como as mensagens aparecem, e <strong>filtros</strong> controlam quais mensagens são processadas. Essa separação de responsabilidades torna o sistema robusto e reutilizável em projetos de qualquer tamanho.</p>
<h3>Configuração básica</h3>
<p>A forma mais simples de usar logging é chamar <code>logging.basicConfig()</code> antes de qualquer log ser registrado. Esse método configura um logger padrão com um handler de console.</p>
<pre><code class="language-python">import logging
Configuração básica
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%d/%m/%Y %H:%M:%S'
)
logger = logging.getLogger(__name__)
logger.debug("Mensagem de debug")
logger.info("Informação geral")
logger.warning("Aviso importante")
logger.error("Erro que ocorreu")
logger.critical("Erro crítico do sistema")</code></pre>
<p>Quando executado, você verá algo assim:</p>
<pre><code>12/01/2024 14:30:45 - __main__ - DEBUG - Mensagem de debug
12/01/2024 14:30:45 - __main__ - INFO - Informação geral
12/01/2024 14:30:45 - __main__ - WARNING - Aviso importante
12/01/2024 14:30:45 - __main__ - ERROR - Erro que ocorreu
12/01/2024 14:30:45 - __main__ - CRITICAL - Erro crítico do sistema</code></pre>
<h3>Loggers, Handlers e Formatadores</h3>
<p>Para aplicações reais, você precisa entender como compor esses elementos manualmente. Um <strong>logger</strong> é obtido com <code>logging.getLogger('nome')</code>, onde o nome geralmente é o nome do módulo. Um <strong>handler</strong> define o destino (arquivo, console, etc.). Um <strong>formatador</strong> define a estrutura das mensagens.</p>
<pre><code class="language-python">import logging
Criar logger
logger = logging.getLogger('minha_aplicacao')
logger.setLevel(logging.DEBUG)
Handler para arquivo
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.ERROR)
Handler para console
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
Formatador
formatter = logging.Formatter(
'%(asctime)s | %(name)s | %(levelname)s | %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
Aplicar formatador aos handlers
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
Adicionar handlers ao logger
logger.addHandler(file_handler)
logger.addHandler(console_handler)
Usar o logger
logger.debug("Este debug só aparece no console")
logger.error("Este erro aparece em ambos")</code></pre>
<p>Neste exemplo, mensagens <code>DEBUG</code> e <code>INFO</code> vão apenas para o console, enquanto erros vão para arquivo. Isso demonstra como você pode ter diferentes níveis de severidade para diferentes destinos em uma mesma aplicação.</p>
<h3>Configuração via arquivo</h3>
<p>Para projetos mais complexos, é comum usar um arquivo de configuração YAML ou JSON. Aqui usaremos um dicionário Python (que pode ser carregado de um JSON).</p>
<pre><code class="language-python">import logging.config
import json
config = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"standard": {
"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
},
"detailed": {
"format": "%(asctime)s [%(levelname)s] %(name)s.%(funcName)s:%(lineno)d - %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "standard",
"stream": "ext://sys.stdout"
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"level": "WARNING",
"formatter": "detailed",
"filename": "app.log",
"maxBytes": 10485760,
"backupCount": 5
}
},
"loggers": {
"": {
"handlers": ["console", "file"],
"level": "DEBUG",
"propagate": True
}
}
}
logging.config.dictConfig(config)
logger = logging.getLogger(__name__)
logger.info("Aplicação iniciada")
logger.warning("Atenção: memória baixa")</code></pre>
<p>Esse padrão é escalável e permite mudar o comportamento do logging sem alterar o código da aplicação.</p>
<h2>Introdução ao structlog</h2>
<p>O <code>structlog</code> é uma biblioteca de terceiros que traz uma abordagem moderna e estruturada ao logging em Python. Enquanto o módulo nativo trabalha com strings, o <code>structlog</code> trabalha com dicionários e contextos, o que torna muito mais fácil gerar logs em formato JSON, estruturado, ideal para análise em sistemas de monitoramento como ELK Stack, Datadog ou Splunk.</p>
<p>A maior vantagem do <code>structlog</code> é a capacidade de manter contexto através da execução do programa. Você pode vincular informações uma única vez e elas aparecem em todos os logs subsequentes sem precisar repeti-las. Além disso, o <code>structlog</code> oferece um excelente suporte a processadores que transformam e enriquecem as mensagens de log.</p>
<h3>Instalação e configuração básica</h3>
<pre><code class="language-bash">pip install structlog</code></pre>
<pre><code class="language-python">import structlog
Configuração padrão (saída para console em formato legível)
structlog.configure(
processors=[
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
context_class=dict,
logger_factory=structlog.PrintLoggerFactory(),
cache_logger_on_first_use=False,
)
log = structlog.get_logger()
log.msg("Usuário autenticado", user_id=42, username="joao")
log.msg("Pagamento processado", valor=150.50, status="sucesso")</code></pre>
<p>A saída será clara e estruturada:</p>
<pre><code>user_id=42 username='joao' event='Usuário autenticado'
valor=150.5 status='sucesso' event='Pagamento processado'</code></pre>
<h3>Contexto e variadics</h3>
<p>O <code>structlog</code> permite vincular contexto que persiste através de chamadas de função. Isso é particularmente útil em requisições web ou processamentos que envolvem múltiplas funções.</p>
<pre><code class="language-python">import structlog
structlog.configure(
processors=[
structlog.processors.JSONRenderer()
],
context_class=dict,
logger_factory=structlog.PrintLoggerFactory(),
)
log = structlog.get_logger()
Vincular contexto
context_log = log.bind(request_id="abc-123", user_id=7)
context_log.msg("Iniciando processamento")
context_log.msg("Validando dados")
context_log.msg("Salvando resultado")
A saída será JSON com os mesmos campos em cada linha:
{"request_id": "abc-123", "user_id": 7, "event": "Iniciando processamento"}
{"request_id": "abc-123", "user_id": 7, "event": "Validando dados"}
{"request_id": "abc-123", "user_id": 7, "event": "Salvando resultado"}</code></pre>
<h3>Processadores personalizados</h3>
<p>Um dos diferenciais do <code>structlog</code> é a capacidade de criar processadores que transformam logs. Aqui criamos um processador que adiciona timestamp e filtra campos sensíveis.</p>
<pre><code class="language-python">import structlog
import json
from datetime import datetime
def adicionar_timestamp(logger, name, event_dict):
"""Adiciona timestamp ao log"""
event_dict['timestamp'] = datetime.utcnow().isoformat()
return event_dict
def remover_campos_sensiveis(logger, name, event_dict):
"""Remove senhas e tokens dos logs"""
campos_sensiveis = ['password', 'token', 'api_key', 'credit_card']
for campo in campos_sensiveis:
if campo in event_dict:
event_dict[campo] = 'REDACTED'
return event_dict
structlog.configure(
processors=[
adicionar_timestamp,
remover_campos_sensiveis,
structlog.processors.JSONRenderer()
],
context_class=dict,
logger_factory=structlog.PrintLoggerFactory(),
)
log = structlog.get_logger()
log.msg("Usuário fez login", user_email="teste@example.com",
password="minhasenha123")
Saída:
{"timestamp": "2024-01-12T14:30:45.123456", "user_email": "teste@example.com", "password": "REDACTED", "event": "Usuário fez login"}</code></pre>
<h3>Integração com logging nativo</h3>
<p>O <code>structlog</code> pode ser configurado para funcionar sobre o módulo nativo <code>logging</code> do Python, permitindo beneficiar-se de ambos os mundos: a flexibilidade do <code>structlog</code> com a robustez do sistema padrão.</p>
<pre><code class="language-python">import structlog
import logging.config
Configurar logging nativo primeiro
logging_config = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"json": {
"()": structlog.stdlib.ProcessorFormatter,
"processor": structlog.processors.JSONRenderer(),
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "json",
}
},
"root": {
"handlers": ["console"],
"level": "INFO",
}
}
logging.config.dictConfig(logging_config)
Configurar structlog para usar logging
structlog.configure(
processors=[
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
context_class=dict,
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
)
log = structlog.get_logger()
log.msg("Sistema iniciado com sucesso", versao="1.0.0")</code></pre>
<h2>Boas práticas em logging</h2>
<p>Logging é uma das atividades mais importantes em desenvolvimento profissional, mas também uma das mais negligenciadas. Um bom sistema de logging permite que você entenda o que aconteceu em produção sem precisar de um debugger, economizando tempo e reduzindo riscos.</p>
<p>A primeira regra é: <strong>use nomes descritivos para loggers</strong>. Normalmente, você obtém um logger com o nome do módulo usando <code>__name__</code>. Isso cria uma hierarquia que facilita filtrar logs por componente. Além disso, sempre prefira usar variáveis com nomes significativos nos seus logs em vez de valores "mágicos" — isso ajuda na análise posterior.</p>
<h3>Níveis apropriados para cada situação</h3>
<p>Cada nível de log tem um propósito. Usar o nível errado torna impossível filtrar os logs relevantes. Aqui está uma guia de quando usar cada um:</p>
<pre><code class="language-python">import logging
logger = logging.getLogger(__name__)
DEBUG: Informações úteis para diagnosticar problemas
Use APENAS durante desenvolvimento/depuração
logger.debug(f"Consultando BD com query: SELECT * FROM users WHERE id={user_id}")
INFO: Confirmação de que as coisas estão funcionando normalmente
Use para eventos significativos mas esperados
logger.info(f"Usuário {user_id} fez login com sucesso")
WARNING: Algo inesperado aconteceu ou pode indicar problema futuro
Use quando algo não é erro ainda, mas requer atenção
logger.warning(f"Taxa de erro acima de 5%: {error_rate}%")
ERROR: Um erro sério, uma função falhou
Use quando a operação não pode ser completada
logger.error(f"Falha ao salvar usuário: {str(e)}", exc_info=True)
CRITICAL: Erro muito sério, o programa pode não continuar
Use raramente, quando o sistema está comprometido
logger.critical("Banco de dados indisponível, impossível continuar")</code></pre>
<h3>Evite antipadrões comuns</h3>
<p>Um erro frequente é criar "logs mentirosos" — registros que não refletem o que realmente aconteceu. Também é comum logar informações sensíveis, como senhas ou tokens. E finalmente, muitos projetos sufocam o log com mensagens desnecessárias.</p>
<pre><code class="language-python"></code></pre>
<h3>Logging em aplicações web e assíncronas</h3>
<p>Em aplicações que lidam com múltiplas requisições simultâneas, é crucial correlacionar logs de uma mesma requisição. Use <code>request_id</code> ou <code>correlation_id</code> para isso.</p>
<pre><code class="language-python">import structlog
import uuid
from fastapi import FastAPI, Request
app = FastAPI()
Configurar structlog
structlog.configure(
processors=[
structlog.processors.JSONRenderer()
],
context_class=dict,
logger_factory=structlog.PrintLoggerFactory(),
)
log = structlog.get_logger()
@app.middleware("http")
async def adicionar_correlation_id(request: Request, call_next):
Gerar ou usar correlation_id existente
correlation_id = request.headers.get("X-Correlation-ID", str(uuid.uuid4()))
Vincular ao contexto structlog
contexto = structlog.contextvars.clear_contextvars()
contexto.update(correlation_id=correlation_id)
response = await call_next(request)
return response
@app.get("/usuarios/{user_id}")
async def obter_usuario(user_id: int):
bound_log = log.bind(user_id=user_id)
bound_log.msg("Buscando usuário")
... lógica ...
bound_log.msg("Usuário encontrado", nome="João")
return {"id": user_id, "nome": "João"}
Cada requisição terá o mesmo correlation_id em todos os seus logs</code></pre>
<h3>Performance: Não calcule valores antes do log</h3>
<p>Um erro comum é calcular valores antes de passá-los ao log, mesmo que o log possa ser ignorado. Use lazy evaluation quando possível.</p>
<pre><code class="language-python"></code></pre>
<h3>Estruturar mensagens para análise</h3>
<p>Quando seus logs vão para um sistema de análise (ELK, Datadog, etc.), a estrutura importa muito. Sempre use campos bem definidos e tipos consistentes.</p>
<pre><code class="language-python">import structlog
import json
from typing import Optional
from enum import Enum
class TipoEvento(str, Enum):
LOGIN = "login"
LOGOUT = "logout"
ERRO_PAGAMENTO = "erro_pagamento"
ACESSO_NEGADO = "acesso_negado"
structlog.configure(
processors=[
structlog.processors.JSONRenderer()
],
context_class=dict,
logger_factory=structlog.PrintLoggerFactory(),
)
log = structlog.get_logger()
def registrar_evento(
tipo: TipoEvento,
user_id: Optional[int] = None,
motivo: Optional[str] = None,
valor: Optional[float] = None,
):
"""
Registra eventos de forma estruturada e consistente.
Permite análise, alertas e correlação em ferramentas de observabilidade.
"""
log.msg(
"evento",
event_type=tipo.value,
user_id=user_id,
motivo=motivo,
valor=valor,
)
Uso
registrar_evento(TipoEvento.LOGIN, user_id=42)
registrar_evento(TipoEvento.ERRO_PAGAMENTO, user_id=42, valor=99.99,
motivo="Cartão recusado")</code></pre>
<h2>Conclusão</h2>
<p>Aprendemos neste artigo três lições fundamentais sobre logging profissional em Python. Primeiro, o módulo nativo <code>logging</code> oferece um sistema robusto e configurável que deve ser a base de qualquer aplicação séria — abandonar <code>print()</code> é um passo importante para a qualidade. Segundo, bibliotecas como <code>structlog</code> elevam o jogo ao oferecer contexto persistente e saída estruturada em JSON, essenciais para sistemas modernos de observabilidade. Terceiro, e mais importante, as boas práticas garantem que seus logs sejam úteis: use níveis corretos, evite dados sensíveis, mantenha correlação em requisições simultâneas, e estruture as mensagens para análise automatizada.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://docs.python.org/3/library/logging.html" target="_blank" rel="noopener noreferrer">Documentação oficial do módulo logging</a></li>
<li><a href="https://www.structlog.org/" target="_blank" rel="noopener noreferrer">Documentação do structlog</a></li>
<li><a href="https://realpython.com/python-logging/" target="_blank" rel="noopener noreferrer">Logging best practices — Real Python</a></li>
<li><a href="https://12factor.net/logs" target="_blank" rel="noopener noreferrer">The Twelve-Factor App: Logs</a></li>
<li><a href="https://martinfowler.com/articles/structured-logging.html" target="_blank" rel="noopener noreferrer">Structured Logging — Martin Fowler</a></li>
</ul>
<p><!-- FIM --></p>