Logging em Python — 2025 | Python Brasil

Domine o módulo logging em Python. Níveis, formatação, handlers e boas práticas profissionais. Aprenda agora!

6 min de leitura Equipe Python Brasil

O módulo logging é uma das ferramentas mais subutilizadas por desenvolvedores Python iniciantes e intermediários. Enquanto print() pode parecer suficiente durante o desenvolvimento, em produção ele é completamente inadequado. O logging profissional permite categorizar mensagens por severidade, direcionar logs para diferentes destinos, formatar saídas de maneira padronizada e desativar ou ativar mensagens sem alterar o código.

Neste artigo, vamos explorar o módulo logging do Python em profundidade, desde o uso básico até configurações avançadas para aplicações profissionais.

Por que não usar print()?

Antes de mergulhar no logging, vale entender por que print() é insuficiente em projetos reais:

# O problema com print()
print("Iniciando processamento...")          # Sem contexto temporal
print("ERRO: arquivo não encontrado")        # Sem severidade formal
print(f"Processando item {item}")            # Não pode ser desligado facilmente
print("DEBUG: valor da variável x =", x)     # Vai para produção por acidente

O print() mistura todas as mensagens em um único fluxo sem distinção de importância, sem timestamp, sem informação de origem e sem possibilidade de controle granular. O módulo logging resolve todos esses problemas.

Configuração básica

O jeito mais rápido de começar com logging:

import logging

# Configuração básica
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)

logger = logging.getLogger(__name__)

# Usando diferentes níveis
logger.debug("Variável x = 42")          # Não aparece (nível INFO configurado)
logger.info("Processamento iniciado")     # Aparece
logger.warning("Disco com 85% de uso")   # Aparece
logger.error("Falha na conexão com DB")  # Aparece
logger.critical("Sistema fora do ar")    # Aparece

Os cinco níveis de severidade, em ordem crescente, são: DEBUG, INFO, WARNING, ERROR e CRITICAL. Ao configurar o nível como INFO, todas as mensagens de nível INFO ou superior são exibidas, enquanto DEBUG é ignorado.

Níveis de log e quando usá-los

Cada nível tem um propósito específico:

import logging

logger = logging.getLogger("minha_app")

# DEBUG: informações detalhadas para diagnóstico
# Use para rastrear o fluxo do programa durante desenvolvimento
logger.debug("Consultando banco: SELECT * FROM users WHERE id = %s", user_id)

# INFO: confirmação de que as coisas estão funcionando
# Use para eventos normais e esperados
logger.info("Servidor iniciado na porta 8000")
logger.info("Usuário %s realizou login", username)

# WARNING: algo inesperado aconteceu, mas o programa continua
# Use para situações que merecem atenção
logger.warning("Tentativa de login com senha incorreta para %s", username)
logger.warning("API externa respondeu em %dms (limite: 500ms)", tempo)

# ERROR: o programa não conseguiu realizar uma operação
# Use quando algo falhou mas o sistema continua rodando
logger.error("Falha ao enviar e-mail para %s: %s", email, erro)

# CRITICAL: erro grave que pode impedir o programa de continuar
# Use para falhas que comprometem o sistema
logger.critical("Banco de dados principal inacessível")

Handlers: direcionando logs

Handlers definem para onde as mensagens de log são enviadas. Você pode ter múltiplos handlers, cada um com seu próprio nível e formato:

import logging
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler

def configurar_logging():
    """Configura logging com múltiplos handlers."""
    logger = logging.getLogger("minha_app")
    logger.setLevel(logging.DEBUG)

    # Formato detalhado para arquivo
    formato_arquivo = logging.Formatter(
        "%(asctime)s | %(name)s | %(levelname)-8s | %(filename)s:%(lineno)d | %(message)s"
    )

    # Formato simplificado para console
    formato_console = logging.Formatter(
        "%(asctime)s - %(levelname)-8s - %(message)s",
        datefmt="%H:%M:%S"
    )

    # Handler de console (INFO e acima)
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    console_handler.setFormatter(formato_console)

    # Handler de arquivo com rotação por tamanho
    arquivo_handler = RotatingFileHandler(
        "app.log",
        maxBytes=5_000_000,      # 5 MB por arquivo
        backupCount=5,           # Mantém 5 arquivos de backup
        encoding="utf-8"
    )
    arquivo_handler.setLevel(logging.DEBUG)
    arquivo_handler.setFormatter(formato_arquivo)

    # Handler separado para erros
    erro_handler = logging.FileHandler(
        "erros.log",
        encoding="utf-8"
    )
    erro_handler.setLevel(logging.ERROR)
    erro_handler.setFormatter(formato_arquivo)

    # Adicionando handlers ao logger
    logger.addHandler(console_handler)
    logger.addHandler(arquivo_handler)
    logger.addHandler(erro_handler)

    return logger

logger = configurar_logging()
logger.info("Sistema iniciado com sucesso")
logger.debug("Modo debug ativo")
logger.error("Exemplo de erro registrado")

Com essa configuração, mensagens DEBUG vão apenas para o arquivo app.log, mensagens INFO e acima aparecem no console e no arquivo, e erros são adicionalmente registrados em erros.log.

Logging com contexto usando extras

Adicionar contexto às mensagens de log facilita a depuração:

import logging

logger = logging.getLogger("api")

def processar_requisicao(request_id, usuario, endpoint):
    """Processa uma requisição com contexto nos logs."""
    extra = {"request_id": request_id, "usuario": usuario}

    logger.info(
        "Requisição recebida: %s %s",
        "GET", endpoint,
        extra=extra
    )

    try:
        # Simulação de processamento
        resultado = {"status": "ok", "dados": [1, 2, 3]}
        logger.info(
            "Requisição processada com sucesso: %d registros",
            len(resultado["dados"]),
            extra=extra
        )
        return resultado
    except Exception as e:
        logger.exception(
            "Erro ao processar requisição para %s",
            endpoint,
            extra=extra
        )
        return None

O método logger.exception() registra automaticamente o traceback completo da exceção, sendo muito mais informativo que logger.error() dentro de um bloco except.

Configuração via dicionário

Para projetos maiores, a configuração via dicionário é mais organizada e flexível:

import logging
import logging.config

CONFIG_LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "padrao": {
            "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
            "datefmt": "%Y-%m-%d %H:%M:%S"
        },
        "detalhado": {
            "format": "%(asctime)s | %(name)s | %(levelname)-8s | %(module)s:%(funcName)s:%(lineno)d | %(message)s"
        }
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "INFO",
            "formatter": "padrao",
            "stream": "ext://sys.stdout"
        },
        "arquivo": {
            "class": "logging.handlers.RotatingFileHandler",
            "level": "DEBUG",
            "formatter": "detalhado",
            "filename": "aplicacao.log",
            "maxBytes": 10485760,
            "backupCount": 3,
            "encoding": "utf-8"
        }
    },
    "loggers": {
        "minha_app": {
            "level": "DEBUG",
            "handlers": ["console", "arquivo"],
            "propagate": False
        },
        "minha_app.banco": {
            "level": "WARNING",
            "handlers": ["console", "arquivo"],
            "propagate": False
        }
    },
    "root": {
        "level": "WARNING",
        "handlers": ["console"]
    }
}

logging.config.dictConfig(CONFIG_LOGGING)

# Usando loggers específicos
logger_app = logging.getLogger("minha_app")
logger_banco = logging.getLogger("minha_app.banco")

logger_app.info("Aplicação iniciada")
logger_banco.debug("Query executada")  # Não aparece (nível WARNING)
logger_banco.warning("Conexão lenta com o banco")

Logging em módulos separados

Em projetos com múltiplos módulos, cada módulo deve criar seu próprio logger:

# arquivo: servicos/usuario.py
import logging

logger = logging.getLogger(__name__)

class ServicoUsuario:
    def criar(self, nome, email):
        logger.info("Criando usuário: %s (%s)", nome, email)
        try:
            # Lógica de criação
            logger.info("Usuário %s criado com sucesso", nome)
            return True
        except Exception:
            logger.exception("Falha ao criar usuário %s", nome)
            return False

# arquivo: servicos/pedido.py
import logging

logger = logging.getLogger(__name__)

class ServicoPedido:
    def processar(self, pedido_id):
        logger.info("Processando pedido %s", pedido_id)
        # Lógica de processamento

Usar __name__ como nome do logger cria automaticamente uma hierarquia baseada na estrutura de pacotes, facilitando a configuração de níveis diferentes para cada módulo.

Boas práticas de logging

Para um sistema de logging profissional:

  • Use __name__ como nome do logger: cria hierarquia automática e facilita o controle.
  • Nunca use print() em produção: substitua por logging com o nível adequado.
  • Use formatação lazy: prefira logger.info("Valor: %s", x) em vez de logger.info(f"Valor: {x}"). A formatação lazy só processa a string se o log for realmente emitido.
  • Registre exceções com exception(): dentro de blocos except, use logger.exception() para incluir o traceback.
  • Não logue dados sensíveis: senhas, tokens e dados pessoais nunca devem aparecer em logs.
  • Configure rotação de arquivos: use RotatingFileHandler ou TimedRotatingFileHandler para evitar que logs consumam todo o disco.
  • Estruture os logs: em sistemas distribuídos, considere usar logging em formato JSON para facilitar a análise com ferramentas como ELK Stack.

Conclusão

O módulo logging é uma ferramenta fundamental para qualquer aplicação Python profissional. Ele oferece flexibilidade para direcionar mensagens para diferentes destinos, controlar a verbosidade por módulo e formatar saídas de maneira padronizada. Investir tempo para configurar o logging adequadamente no início do projeto economiza muitas horas de depuração no futuro.

Como próximos passos, explore bibliotecas como structlog para logging estruturado, aprenda a integrar logs com ferramentas de monitoramento como Grafana e Prometheus, e estude como configurar alertas automáticos baseados em padrões de erro nos seus logs.

E

Equipe Python Brasil

Contribuidor do Python Brasil — Aprenda Python em Português