Quando desenvolvemos aplicações Python, especialmente em ambiente de produção, a capacidade de registrar eventos, erros e informações de debug é fundamental. O módulo logging do Python é a ferramenta padrão e mais versátil para essa finalidade, oferecendo muito mais do que simples prints no console.

Neste tutorial completo, você aprenderá desde os conceitos básicos até técnicas avançadas de logging em Python, incluindo configuração de handlers, formatters personalizados, filtros e integração com sistemas externos de monitoramento.

O Que é Python Logging?

O logging é o processo de registrar informações sobre a execução de um programa. Diferente do print(), o módulo logging oferece uma estrutura hierárquica, múltiplos níveis de severidade, configuração centralizada e a possibilidade de enviar logs para diferentes destinos simultaneamente.

Com o logging adequado, você pode:

  • Monitorar o comportamento da aplicação em produção
  • Debugar problemas em ambiente de desenvolvimento
  • Auditar ações de usuários eシステム eventos
  • Integrar com ferramentas de monitoramento como ELK, Datadog, New Relic
  • Gerar métricas de performance e disponibilidade

Fonte: Python Documentation - Logging

Os 5 Níveis de Logging

O Python define cinco níveis de logging, cada um com uma finalidade específica:

import logging

# DEBUG - Informações detalhadas para diagnóstico
logging.debug("Variável x = %d", x)

# INFO - Confirmação de que tudo está funcionando
logging.info("Usuário %s fez login", username)

# WARNING - Algo inesperado aconteceu, mas a aplicação continua
logging.warning("Memória acima de 80%%")

# ERROR - Problema sério que afectó uma função
logging.error("Falha ao conectar com banco de dados")

# CRITICAL - Erro crítico que pode parar a aplicação
logging.critical("Sistema de autenticação falhou!")

Cada nível tem um valor numérico associated: DEBUG=10, INFO=20, WARNING=30, ERROR=40, CRITICAL=50. Por padrão, apenas WARNING e superiores são exibidos.

Fonte: Real Python - Python Logging

Configuração Básica de Logging

A forma mais simples de começar com logging é usando a função basicConfig():

import logging

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

logging.info("Logging configurado com sucesso!")

Este código cria um logger raiz com output para o console. O formato inclui timestamp, nome do logger, nível e mensagem.

Configuração com dicionário (versão moderna)

A partir do Python 3.21, você pode usar dictionaries para configuração:

import logging
import logging.config

config = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "standard": {
            "format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
        },
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "DEBUG",
            "formatter": "standard",
            "stream": "ext://sys.stdout"
        },
        "file": {
            "class": "logging.FileHandler",
            "level": "INFO",
            "formatter": "standard",
            "filename": "app.log",
            "mode": "a"
        }
    },
    "root": {
        "level": "INFO",
        "handlers": ["console", "file"]
    }
}

logging.config.dictConfig(config)

Esta abordagem é muito mais flexível e permite configuração sem alterar código, sendo ideal para ambientes de produção.

Criando Loggers Personalizados

Para aplicações maiores, é recommend criar loggers específicos para cada módulo:

import logging

# Criar logger para um módulo específico
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# Adicionar handler se não existir
if not logger.handlers:
    handler = logging.StreamHandler()
    formatter = logging.Formatter(
        '%(name)s - %(levelname)s - %(message)s'
    )
    handler.setFormatter(formatter)
    logger.addHandler(handler)

# Usar o logger
logger.info("Processando dados do usuário")
logger.error("Falha na validação")

Usar __name__ como argumento para getLogger() cria um logger com o nome do módulo atual, o que facilita a identificação da origem dos logs.

Handlers: Destinos dos Logs

Handlers definem para onde os logs serão enviados. O Python oferece vários tipos:

StreamHandler - Console e Arquivos

handler = logging.StreamHandler()  # stdout (padrão)
handler = logging.FileHandler('app.log', encoding='utf-8')  # arquivo
handler = logging.FileHandler('error.log', mode='a')  # append mode

RotatingFileHandler - Logs com Rotação

Para evitar arquivos de log enorms, use rotação:

from logging.handlers import RotatingFileHandler

handler = RotatingFileHandler(
    'app.log',
    maxBytes=10 * 1024 * 1024,  # 10 MB
    backupCount=5  # manter 5 arquivos de backup
)

Quando o arquivo atinge 10 MB, ele é renomeado e um novo é criado. O parâmetro backupCount define quantos arquivos antigos são mantidos.

Fonte: Python Logging Handlers

TimedRotatingFileHandler - Rotação por Tempo

from logging.handlers import TimedRotatingFileHandler

handler = TimedRotatingFileHandler(
    'app.log',
    when='midnight',  # rotates at midnight
    interval=1,
    backupCount=30  # keep 30 days of logs
)

SysLogHandler - Enviar para Sistema

Para enviar logs para servidores syslog:

from logging.handlers import SysLogHandler

handler = SysLogHandler(address=('localhost', 514))

HTTPHandler - Enviar para API

Para enviar logs para serviços externos:

from logging.handlers import HTTPHandler

handler = HTTPHandler(
    'api.monitoring.com',
    '/logs',
    method='POST'
)

Formatters: Formatando a Saída

Formatters definem o layout das mensagens de log. O Python oferece vários atributos:

formatter = logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

Atributos disponíveis:

  • %(asctime)s - Timestamp formatado
  • %(name)s - Nome do logger
  • %(levelname)s - Nível do log (DEBUG, INFO, etc)
  • %(levelno)s - Número do nível
  • %(message)s - A mensagem
  • %(filename)s - Nome do arquivo
  • %(funcName)s - Nome da função
  • %(lineno)d - Número da linha
  • %(process)d - ID do processo
  • %(thread)d - ID da thread
  • %(pathname)s - Caminho completo do arquivo

Criando um Custom Formatter

class ColoredFormatter(logging.Formatter):
    """Formatter com cores para console"""

    grey = "\x1b[38;21m"
    blue = "\x1b[38;5;39m"
    yellow = "\x1b[38;5;226m"
    red = "\x1b[38;5;196m"
    bold_red = "\x1b[31;1m"
    reset = "\x1b[0m"

    FORMATS = {
        logging.DEBUG: grey + "%(message)s" + reset,
        logging.INFO: blue + "%(message)s" + reset,
        logging.WARNING: yellow + "%(message)s" + reset,
        logging.ERROR: red + "%(message)s" + reset,
        logging.CRITICAL: bold_red + "%(message)s" + reset
    }

    def format(self, record):
        log_fmt = self.FORMATS.get(record.levelno)
        formatter = logging.Formatter(log_fmt)
        return formatter.format(record)

Este formatter adiciona cores aos logs no console, facilitando a identificação visual do nível de cada mensagem.

Filtros: Controlando o Fluxo de Logs

Filtros permitem um controle mais granular sobre quais mensagens são registradas:

class SensitiveDataFilter(logging.Filter):
    """Filtro que removes dados sensíveis dos logs"""

    SENSITIVE_PATTERNS = [
        r'\b\d{3}-\d{2}-\d{4}\b',  # CPF
        r'\bpassword[=:]\S+',       # Password
        r'\bapi_key[=:]\S+',        # API Key
    ]

    def filter(self, record):
        import re
        message = record.getMessage()
        for pattern in self.SENSITIVE_PATTERNS:
            message = re.sub(pattern, '***REDACTED***', message)
        record.msg = message
        return True

# Adicionar filtro ao handler
handler.addFilter(SensitiveDataFilter())

Este filtro é essencial para compliance com LGPD/GDPR, removendo informações pessoais dos logs.

Filtro por Módulo

class ModuleFilter(logging.Filter):
    """Filtro que permite apenas módulos específicos"""

    def __init__(self, allowed_modules):
        super().__init__()
        self.allowed_modules = allowed_modules

    def filter(self, record):
        return record.name in self.allowed_modules or record.levelno >= logging.ERROR

Logging em Aplicações Web (Django/Flask)

Configuração para Django

# settings.py

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {message}',
            'style': '{',
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'verbose',
        },
        'file': {
            'class': 'logging.FileHandler',
            'filename': 'django.log',
            'formatter': 'verbose',
        },
    },
    'root': {
        'handlers': ['console', 'file'],
        'level': 'INFO',
    },
    'loggers': {
        'django': {
            'handlers': ['file'],
            'level': 'WARNING',
            'propagate': False,
        },
        'myapp': {
            'handlers': ['console', 'file'],
            'level': 'DEBUG',
            'propagate': False,
        },
    },
}

Configuração para Flask

import logging
from logging.handlers import RotatingFileHandler

app = Flask(__name__)

if not app.debug:
    file_handler = RotatingFileHandler(
        'flask.log',
        maxBytes=10240000,
        backupCount=10
    )
    file_handler.setFormatter(logging.Formatter(
        '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
    ))
    file_handler.setLevel(logging.INFO)
    app.logger.addHandler(file_handler)
    app.logger.setLevel(logging.INFO)
    app.logger.info('Aplicação Flask iniciada')

Fonte: DigitalOcean - Python Logging

Logging Assíncrono

Para aplicações de alta performance, considere logging assíncrono:

import logging
from logging.handlers import QueueHandler
import queue
import threading

# Criar queue para logs
log_queue = queue.Queue(-1)

# Handler que envia para a queue
queue_handler = QueueHandler(log_queue)

# Thread que processa a queue
def log_writer():
    while True:
        record = log_queue.get()
        if record is None:
            break
        logger = logging.getLogger(record.name)
        logger.handle(record)

# Iniciar thread de escrita
writer_thread = threading.Thread(target=log_writer, daemon=True)
writer_thread.start()

# Configurar logger raiz com QueueHandler
root_logger = logging.getLogger()
root_logger.addHandler(queue_handler)
root_logger.setLevel(logging.DEBUG)

# Agora todo logging é assíncrono
logging.info("Log assíncrono!")

Esta abordagem é especialmente útil em aplicações web de alto tráfego onde o I/O de logging pode se tornar um gargalo.

Boas Práticas de Logging

  • Use níveis corretamente: Não use DEBUG em produção, nem ERROR para mensagens informativas.
  • Inclua contexto: Adicione IDs de requisição, nomes de usuário, dados relevantes.
  • Evite dados sensíveis: Nunca logue senhas, tokens, dados pessoais sem criptografia.
  • Seja consistente: Use o mesmo formato em toda a aplicação.
  • Configure rotação: Evite logs que consumam todo o espaço em disco.
  • Monitore seus logs: Use ferramentas como ELK, Datadog, Sentry.
  • Documente sua configuração: altri configuração deve estar documentada.

Exemplo de Logger Estruturado

import logging
import json
from datetime import datetime

class JSONFormatter(logging.Formatter):
    """Formatter que outputa JSON"""

    def format(self, record):
        log_data = {
            "timestamp": datetime.utcnow().isoformat(),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
            "module": record.module,
            "function": record.funcName,
            "line": record.lineno
        }

        if record.exc_info:
            log_data["exception"] = self.formatException(record.exc_info)

        return json.dumps(log_data)

Logs em JSON são ideais para processamento por sistemas de monitoring e log aggregation.

Fonte: Loggly - Ultimate Guide to Python Logging

Logging e Exception Handling

Uma prática essencial é usar logging com tratamento de exceções:

import logging
import traceback

logger = logging.getLogger(__name__)

try:
    result = process_data(data)
except Exception as e:
    logger.error(
        "Falha ao processar dados: %s\n%s",
        str(e),
        traceback.format_exc()
    )
    raise

# Maneira mais limpa com logging.exception()
try:
    result = process_data(data)
except Exception:
    logger.exception("Falha ao processar dados")
    raise

O método logging.exception() automaticamente inclui o traceback completo.

Integração com Sistemas de Monitoramento

Sentry

import sentry_sdk
from sentry_sdk.integrations.logging import SentryHandler

sentry_sdk.init(
    dsn="YOUR_SENTRY_DSN",
    integrations=[LoggingIntegration()]
)

handler = SentryHandler()
logging.root.addHandler(handler)

Datadog

from datadog import DogStatsd

statsd = DogStatsd(host="localhost", port=8125)

class DatadogHandler(logging.Handler):
    def emit(self, record):
        if record.levelno >= logging.ERROR:
            statsd.increment("log.error", tags=[
                f"logger:{record.name}",
                f"level:{record.levelname}"
            ])

logging.root.addHandler(DatadogHandler())

Conclusão

O módulo logging do Python é uma ferramenta extremamente poderosa que vai muito além do simples print(). Com a configuração correta de handlers, formatters e filtros, você pode criar um sistema de logging profissional adequado para aplicações de qualquer tamanho.

Lembre-se das melhores práticas: use níveis adequados, inclua contexto relevante, evite dados sensíveis e configure rotação de logs. Com um bom sistema de logging, debugging e monitoramento tornam-se muito mais eficientes.

Continue aprendendo com os guias gratuitos do Universo Python: explore tratamento de exceções, testes automatizados com pytest, e criar APIs com FastAPI para elevar suas habilidades de desenvolvimento!