Python

Logging em Python: logging module, structlog e Boas Práticas: Do Básico ao Avançado

17 min de leitura

Logging em Python: logging module, structlog e Boas Práticas: Do Básico ao Avançado

O módulo logging nativo do Python O módulo é 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 , 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. O funciona através de uma hierarquia de componentes: loggers criam os registros, handlers determinam para onde as mensagens vão, formatadores definem como as mensagens aparecem, e filtros controlam quais mensagens são processadas. Essa separação de responsabilidades torna o sistema robusto e reutilizável em projetos de qualquer tamanho. Configuração básica A forma mais simples de usar logging é chamar antes de qualquer log ser registrado. Esse método configura um logger padrão com um handler de console. Quando executado, você verá algo assim: Loggers, Handlers e Formatadores Para aplicações reais, você precisa entender como compor esses elementos manualmente.

<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=&#039;%(asctime)s - %(name)s - %(levelname)s - %(message)s&#039;,

datefmt=&#039;%d/%m/%Y %H:%M:%S&#039;

)

logger = logging.getLogger(__name__)

logger.debug(&quot;Mensagem de debug&quot;)

logger.info(&quot;Informação geral&quot;)

logger.warning(&quot;Aviso importante&quot;)

logger.error(&quot;Erro que ocorreu&quot;)

logger.critical(&quot;Erro crítico do sistema&quot;)</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(&#039;nome&#039;)</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(&#039;minha_aplicacao&#039;)

logger.setLevel(logging.DEBUG)

Handler para arquivo

file_handler = logging.FileHandler(&#039;app.log&#039;)

file_handler.setLevel(logging.ERROR)

Handler para console

console_handler = logging.StreamHandler()

console_handler.setLevel(logging.INFO)

Formatador

formatter = logging.Formatter(

&#039;%(asctime)s | %(name)s | %(levelname)s | %(message)s&#039;,

datefmt=&#039;%Y-%m-%d %H:%M:%S&#039;

)

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(&quot;Este debug só aparece no console&quot;)

logger.error(&quot;Este erro aparece em ambos&quot;)</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 = {

&quot;version&quot;: 1,

&quot;disable_existing_loggers&quot;: False,

&quot;formatters&quot;: {

&quot;standard&quot;: {

&quot;format&quot;: &quot;%(asctime)s [%(levelname)s] %(name)s: %(message)s&quot;

},

&quot;detailed&quot;: {

&quot;format&quot;: &quot;%(asctime)s [%(levelname)s] %(name)s.%(funcName)s:%(lineno)d - %(message)s&quot;

}

},

&quot;handlers&quot;: {

&quot;console&quot;: {

&quot;class&quot;: &quot;logging.StreamHandler&quot;,

&quot;level&quot;: &quot;DEBUG&quot;,

&quot;formatter&quot;: &quot;standard&quot;,

&quot;stream&quot;: &quot;ext://sys.stdout&quot;

},

&quot;file&quot;: {

&quot;class&quot;: &quot;logging.handlers.RotatingFileHandler&quot;,

&quot;level&quot;: &quot;WARNING&quot;,

&quot;formatter&quot;: &quot;detailed&quot;,

&quot;filename&quot;: &quot;app.log&quot;,

&quot;maxBytes&quot;: 10485760,

&quot;backupCount&quot;: 5

}

},

&quot;loggers&quot;: {

&quot;&quot;: {

&quot;handlers&quot;: [&quot;console&quot;, &quot;file&quot;],

&quot;level&quot;: &quot;DEBUG&quot;,

&quot;propagate&quot;: True

}

}

}

logging.config.dictConfig(config)

logger = logging.getLogger(__name__)

logger.info(&quot;Aplicação iniciada&quot;)

logger.warning(&quot;Atenção: memória baixa&quot;)</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(&quot;Usuário autenticado&quot;, user_id=42, username=&quot;joao&quot;)

log.msg(&quot;Pagamento processado&quot;, valor=150.50, status=&quot;sucesso&quot;)</code></pre>

<p>A saída será clara e estruturada:</p>

<pre><code>user_id=42 username=&#039;joao&#039; event=&#039;Usuário autenticado&#039;

valor=150.5 status=&#039;sucesso&#039; event=&#039;Pagamento processado&#039;</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=&quot;abc-123&quot;, user_id=7)

context_log.msg(&quot;Iniciando processamento&quot;)

context_log.msg(&quot;Validando dados&quot;)

context_log.msg(&quot;Salvando resultado&quot;)

A saída será JSON com os mesmos campos em cada linha:

{&quot;request_id&quot;: &quot;abc-123&quot;, &quot;user_id&quot;: 7, &quot;event&quot;: &quot;Iniciando processamento&quot;}

{&quot;request_id&quot;: &quot;abc-123&quot;, &quot;user_id&quot;: 7, &quot;event&quot;: &quot;Validando dados&quot;}

{&quot;request_id&quot;: &quot;abc-123&quot;, &quot;user_id&quot;: 7, &quot;event&quot;: &quot;Salvando resultado&quot;}</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):

&quot;&quot;&quot;Adiciona timestamp ao log&quot;&quot;&quot;

event_dict[&#039;timestamp&#039;] = datetime.utcnow().isoformat()

return event_dict

def remover_campos_sensiveis(logger, name, event_dict):

&quot;&quot;&quot;Remove senhas e tokens dos logs&quot;&quot;&quot;

campos_sensiveis = [&#039;password&#039;, &#039;token&#039;, &#039;api_key&#039;, &#039;credit_card&#039;]

for campo in campos_sensiveis:

if campo in event_dict:

event_dict[campo] = &#039;REDACTED&#039;

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(&quot;Usuário fez login&quot;, user_email=&quot;teste@example.com&quot;,

password=&quot;minhasenha123&quot;)

Saída:

{&quot;timestamp&quot;: &quot;2024-01-12T14:30:45.123456&quot;, &quot;user_email&quot;: &quot;teste@example.com&quot;, &quot;password&quot;: &quot;REDACTED&quot;, &quot;event&quot;: &quot;Usuário fez login&quot;}</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 = {

&quot;version&quot;: 1,

&quot;disable_existing_loggers&quot;: False,

&quot;formatters&quot;: {

&quot;json&quot;: {

&quot;()&quot;: structlog.stdlib.ProcessorFormatter,

&quot;processor&quot;: structlog.processors.JSONRenderer(),

}

},

&quot;handlers&quot;: {

&quot;console&quot;: {

&quot;class&quot;: &quot;logging.StreamHandler&quot;,

&quot;formatter&quot;: &quot;json&quot;,

}

},

&quot;root&quot;: {

&quot;handlers&quot;: [&quot;console&quot;],

&quot;level&quot;: &quot;INFO&quot;,

}

}

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(&quot;Sistema iniciado com sucesso&quot;, versao=&quot;1.0.0&quot;)</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 &quot;mágicos&quot; — 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&quot;Consultando BD com query: SELECT * FROM users WHERE id={user_id}&quot;)

INFO: Confirmação de que as coisas estão funcionando normalmente

Use para eventos significativos mas esperados

logger.info(f&quot;Usuário {user_id} fez login com sucesso&quot;)

WARNING: Algo inesperado aconteceu ou pode indicar problema futuro

Use quando algo não é erro ainda, mas requer atenção

logger.warning(f&quot;Taxa de erro acima de 5%: {error_rate}%&quot;)

ERROR: Um erro sério, uma função falhou

Use quando a operação não pode ser completada

logger.error(f&quot;Falha ao salvar usuário: {str(e)}&quot;, exc_info=True)

CRITICAL: Erro muito sério, o programa pode não continuar

Use raramente, quando o sistema está comprometido

logger.critical(&quot;Banco de dados indisponível, impossível continuar&quot;)</code></pre>

<h3>Evite antipadrões comuns</h3>

<p>Um erro frequente é criar &quot;logs mentirosos&quot; — 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(&quot;http&quot;)

async def adicionar_correlation_id(request: Request, call_next):

Gerar ou usar correlation_id existente

correlation_id = request.headers.get(&quot;X-Correlation-ID&quot;, 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(&quot;/usuarios/{user_id}&quot;)

async def obter_usuario(user_id: int):

bound_log = log.bind(user_id=user_id)

bound_log.msg(&quot;Buscando usuário&quot;)

... lógica ...

bound_log.msg(&quot;Usuário encontrado&quot;, nome=&quot;João&quot;)

return {&quot;id&quot;: user_id, &quot;nome&quot;: &quot;João&quot;}

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 = &quot;login&quot;

LOGOUT = &quot;logout&quot;

ERRO_PAGAMENTO = &quot;erro_pagamento&quot;

ACESSO_NEGADO = &quot;acesso_negado&quot;

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,

):

&quot;&quot;&quot;

Registra eventos de forma estruturada e consistente.

Permite análise, alertas e correlação em ferramentas de observabilidade.

&quot;&quot;&quot;

log.msg(

&quot;evento&quot;,

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=&quot;Cartão recusado&quot;)</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>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Python

Tipos Avançados em Python: Generic, Protocol, TypeVar e ParamSpec na Prática
Tipos Avançados em Python: Generic, Protocol, TypeVar e ParamSpec na Prática

Introdução aos Tipos Avançados em Python Python é uma linguagem dinamicamente...

Guia Completo de Autenticação em FastAPI: JWT, OAuth2 e Segurança de Endpoints
Guia Completo de Autenticação em FastAPI: JWT, OAuth2 e Segurança de Endpoints

Por que Autenticação é Crítica em APIs Modernas Quando você constrói uma API,...

Closures e Funções de Primeira Classe em Python: Do Básico ao Avançado
Closures e Funções de Primeira Classe em Python: Do Básico ao Avançado

Funções como Objetos de Primeira Classe Em Python, tudo é um objeto — incluin...