Logging: configurando e usando o módulo logging

1. Introdução ao módulo logging

Em aplicações Python, a tentação de usar print() para depuração é grande, mas essa abordagem se mostra limitada em projetos reais. O módulo logging oferece uma solução profissional e flexível para registrar informações durante a execução do código.

Por que usar logging em vez de print()?
- Controle granular de níveis de severidade
- Saída direcionável para múltiplos destinos (console, arquivo, rede)
- Formatação personalizada das mensagens
- Suporte a rotação de arquivos e gerenciamento de tamanho
- Capacidade de desativar logs sem modificar o código

Níveis de log (do menos ao mais severo):
- DEBUG: Informações detalhadas para diagnóstico
- INFO: Confirmação de que tudo funciona como esperado
- WARNING: Indicação de algo inesperado ou problema futuro
- ERROR: Erro grave que impede uma função específica
- CRITICAL: Erro crítico que pode impedir o programa de continuar

Para começar, basta importar o módulo:

import logging

2. Configuração básica do logger

A função basicConfig() é a maneira mais rápida de configurar o logging. Ela permite definir nível, formato e destino das mensagens.

import logging

# Configuração básica: nível INFO, formato com timestamp e mensagem
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

# Exemplos de uso
logging.debug("Isso não aparecerá (nível DEBUG abaixo do configurado)")
logging.info("Iniciando processamento de dados")
logging.warning("Arquivo de configuração não encontrado, usando padrões")
logging.error("Falha ao conectar ao banco de dados")
logging.critical("Sistema sem memória disponível")

Para salvar em arquivo, adicione o parâmetro filename:

logging.basicConfig(
    filename='app.log',
    filemode='a',  # 'a' para append, 'w' para sobrescrever
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

Boas práticas: Defina o nível mínimo como INFO em produção e DEBUG em desenvolvimento. Use sempre um formato consistente para facilitar a análise posterior.

3. Hierarquia de loggers e propagação

O módulo logging organiza os loggers em uma hierarquia baseada em nomes com separação por ponto (.). A função getLogger(__name__) é a maneira recomendada de criar loggers em módulos.

# modulo_a.py
import logging
logger = logging.getLogger(__name__)  # Cria logger 'modulo_a'

# modulo_b.py
import logging
logger = logging.getLogger(__name__)  # Cria logger 'modulo_b'

# main.py
import logging
import modulo_a
import modulo_b

logging.basicConfig(level=logging.INFO)
modulo_a.logger.info("Mensagem do módulo A")  # Propaga para o logger raiz

Propagação: Por padrão, mensagens de loggers filhos propagam para loggers pais. Para interromper essa propagação:

logger = logging.getLogger('modulo_a.submodulo')
logger.propagate = False  # Mensagens não sobem para 'modulo_a' nem para a raiz

4. Handlers: direcionando a saída do log

Handlers definem para onde as mensagens de log vão. Você pode ter múltiplos handlers em um mesmo logger.

import logging

logger = logging.getLogger('meu_app')
logger.setLevel(logging.DEBUG)

# Handler para console
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.WARNING)  # Só warnings+ no console
logger.addHandler(console_handler)

# Handler para arquivo com rotação por tamanho
from logging.handlers import RotatingFileHandler
file_handler = RotatingFileHandler(
    'app.log', 
    maxBytes=1024*1024,  # 1 MB
    backupCount=5
)
file_handler.setLevel(logging.DEBUG)
logger.addHandler(file_handler)

# Handler com rotação por tempo
from logging.handlers import TimedRotatingFileHandler
time_handler = TimedRotatingFileHandler(
    'app_diario.log',
    when='midnight',
    interval=1,
    backupCount=30
)
logger.addHandler(time_handler)

logger.info("Mensagem vai para o arquivo, mas não para o console")
logger.warning("Esta mensagem aparece em ambos os destinos")

5. Formatação de mensagens de log

O Formatter permite personalizar completamente a aparência das mensagens usando placeholders.

import logging

logger = logging.getLogger('exemplo')

# Formato detalhado para desenvolvimento
dev_formatter = logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

# Formato simplificado para produção
prod_formatter = logging.Formatter(
    '%(asctime)s | %(levelname)-8s | %(message)s',
    datefmt='%H:%M:%S'
)

console_handler = logging.StreamHandler()
console_handler.setFormatter(dev_formatter)
logger.addHandler(console_handler)
logger.setLevel(logging.DEBUG)

logger.info("Processo concluído com sucesso")
# Saída exemplo: 2024-01-15 14:30:22 - exemplo - INFO - test.py:18 - Processo concluído com sucesso

Placeholders úteis: %(name)s, %(levelname)s, %(message)s, %(asctime)s, %(module)s, %(lineno)d, %(funcName)s, %(threadName)s.

6. Filtros e handlers customizados

Filtros permitem controlar quais mensagens são processadas:

import logging

class SensitiveDataFilter(logging.Filter):
    def filter(self, record):
        # Filtra mensagens contendo senhas
        return 'senha' not in record.getMessage().lower()

logger = logging.getLogger('app_filtrado')
logger.addFilter(SensitiveDataFilter())

# Handler customizado para enviar logs para uma API
import requests
from logging import Handler

class APIHandler(Handler):
    def __init__(self, url):
        super().__init__()
        self.url = url

    def emit(self, record):
        log_entry = self.format(record)
        try:
            requests.post(self.url, json={'log': log_entry})
        except requests.RequestException:
            pass  # Falha silenciosa para não causar erros em cascata

api_handler = APIHandler('https://api.exemplo.com/logs')
api_handler.setLevel(logging.ERROR)
logger.addHandler(api_handler)

7. Boas práticas e padrões de uso

Configuração via dicionário é a abordagem mais flexível e recomendada para aplicações complexas:

import logging.config

LOGGING_CONFIG = {
    'version': 1,
    'formatters': {
        'default': {
            'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        },
        'detailed': {
            'format': '%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s',
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'level': 'DEBUG',
            'formatter': 'detailed',
        },
        'file': {
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': 'app.log',
            'maxBytes': 1048576,
            'backupCount': 5,
            'formatter': 'default',
        },
    },
    'loggers': {
        '': {  # Root logger
            'handlers': ['console', 'file'],
            'level': 'INFO',
        },
        'minha_biblioteca': {
            'handlers': ['console'],
            'level': 'WARNING',
            'propagate': False,
        },
    },
}

logging.config.dictConfig(LOGGING_CONFIG)

Tratamento de exceções com logger.exception():

try:
    resultado = 10 / 0
except ZeroDivisionError:
    logger.exception("Erro ao realizar divisão")  # Inclui stack trace automaticamente

Logging em bibliotecas: Crie um logger com __name__ e não configure handlers. Deixe a aplicação principal configurar o logging.

8. Exemplos práticos e integração com outros módulos

Logging em aplicação Flask:

from flask import Flask
import logging

app = Flask(__name__)

# Configurar logging para Flask
if not app.debug:
    file_handler = logging.FileHandler('flask_app.log')
    file_handler.setLevel(logging.WARNING)
    app.logger.addHandler(file_handler)
    app.logger.setLevel(logging.WARNING)

@app.route('/')
def index():
    app.logger.info('Página inicial acessada')
    return 'Olá, mundo!'

Logging assíncrono com QueueHandler para alta performance:

import logging
from logging.handlers import QueueHandler, QueueListener
from queue import Queue
import threading

def worker():
    while True:
        # Processa logs da fila
        record = log_queue.get()
        if record is None:
            break
        # Simula processamento
        print(f"Processado: {record.getMessage()}")

log_queue = Queue()
queue_handler = QueueHandler(log_queue)

logger = logging.getLogger('async_logger')
logger.addHandler(queue_handler)
logger.setLevel(logging.DEBUG)

# Inicia thread consumidora
threading.Thread(target=worker, daemon=True).start()

logger.info("Mensagem enviada para fila assíncrona")

Referências

  • Documentação oficial do módulo logging — Referência completa com todos os recursos do módulo logging, incluindo handlers, formatters e configuração avançada.
  • Logging Cookbook — Guia prático com exemplos reais de configuração, múltiplos handlers, logging em bibliotecas e integração com outros módulos.
  • Real Python: Logging in Python — Tutorial abrangente cobrindo desde conceitos básicos até técnicas avançadas como logging assíncrono e configuração via dicionário.
  • Python Logging: A Complete Guide — Guia completo da Loggly com exemplos práticos, boas práticas e integração com serviços externos de logging.
  • Flask Logging Documentation — Documentação oficial sobre como configurar logging em aplicações Flask, incluindo tratamento de exceções e configuração por ambiente.