Transforme Seus Projetos Python com Técnicas Robustas de Logging - Parte I

A geração de logs é uma habilidade crucial para qualquer desenvolvedor Python, ajudando na depuração, monitoramento de desempenho e diagnóstico de falhas. Aqui, vamos nos aprofundar no logging em Python, cobrindo do nível básico ao avançado, e incluindo exemplos práticos para solidificar seu entendimento.

🕒 Tempo estimado de leitura: 11 minutos

O logging é a prática de registrar informações sobre a execução de um programa. Isso pode incluir detalhes como erros, mensagens informativas, avisos e informações de depuração. Python fornece um módulo de logging embutido que os desenvolvedores podem usar para implementar o logging em suas aplicações.

Por que o Logging é Importante?

Depuração: O logging permite que você entenda o fluxo de sua aplicação e ajuda a identificar onde as coisas podem estar dando errado. É muito mais fácil rastrear problemas quando você tem um registro do que a aplicação estava fazendo em vários pontos no tempo.

Monitoramento: Logs podem ser usados para monitorar a saúde de uma aplicação em tempo real ou revisando os logs posteriormente. Eles podem fornecer insights sobre gargalos de desempenho, uso de recursos e outros aspectos operacionais.

Auditoria e Conformidade: Muitas indústrias têm requisitos regulatórios que exigem manter logs para fins de conformidade. Logging é uma parte essencial da manutenção de um trilho de auditoria, que pode ser crítico para auditorias de segurança e revisões de conformidade.

Suporte ao Usuário: Logs detalhados podem fornecer informações inestimáveis para equipes de suporte ao cliente quando os usuários relatam problemas. Saber o que o sistema estava fazendo quando ocorreu um erro pode ajudar a identificar e resolver rapidamente os problemas.

Persistência: Diferente das instruções print, que são transitórias e só visíveis no console, os logs podem ser salvos em arquivos, bancos de dados ou outros meios de armazenamento para análise e inspeção a longo prazo.

Agora, vamos mergulhar no mundo dos logs em Python com alguns exemplos práticos que também estão disponíveis no Google Colab aqui 👨‍🔬.

Configurando o Logging Básico

A biblioteca embutida de logging do Python facilita a configuração do logging. Veja como começar:

import logging

# Configura o sistema de logging
# Define o limiar para o logger no nível DEBUG
# Isso significa que todas as mensagens de logging a partir deste nível serão mostradas
logging.basicConfig(level=logging.DEBUG)

# Abaixo estão diferentes níveis de mensagens de logging em ordem crescente de severidade.

# DEBUG: Informação detalhada, tipicamente de interesse apenas ao diagnosticar problemas.
logging.debug("Esta é uma mensagem de depuração")

# INFO: Confirmação de que as coisas estão funcionando como esperado.
logging.info("Esta é uma mensagem de informação")

# WARNING: Uma indicação de que algo inesperado aconteceu, ou indicativo de algum problema no futuro próximo
# (por exemplo, ‘espaço em disco baixo’). O software ainda está funcionando como esperado.
logging.warning("Esta é uma mensagem de aviso")

# ERROR: Devido a um problema mais sério, o software não conseguiu executar alguma função.
logging.error("Esta é uma mensagem de erro")

# CRITICAL: Um erro muito sério, indicando que o programa pode não conseguir continuar a execução.
logging.critical("Esta é uma mensagem crítica")

Exemplo Prático: Logging de Script Simples

Vamos escrever um script básico que demonstra o logging:

import logging  # Importa o módulo de logging para possibilitar o logging de eventos, erros e mensagens informativas.

def dividir(a, b):
    """Função para dividir dois números.

    Args:
    a (float): O numerador.
    b (float): O denominador.

    Returns:
    float: O resultado da divisão se bem-sucedida, caso contrário None.
    """
    logging.info(f"Dividindo {a} por {b}")  # Loga uma mensagem de info indicando a operação sendo realizada.
    
    if b == 0:  # Verifica se o denominador é zero para evitar erro de divisão por zero.
        logging.error("Tentativa de divisão por zero")  # Loga uma mensagem de erro se uma divisão por zero foi tentada.
        return None  # Retorna None para indicar que a operação falhou.
    
    return a / b  # Realiza a divisão e retorna o resultado.

if __name__ == "__main__":
    # Configura o logging para exibir mensagens do nível DEBUG e acima
    logging.basicConfig(level=logging.DEBUG)
    
    # Testa a função dividir com entrada válida
    resultado = dividir(10, 2)
    print(f"Resultado: {resultado}")  # Exibe o resultado da divisão

    # Testa a função dividir com divisão por zero
    resultado = dividir(10, 0)
    print(f"Resultado: {resultado}")  # Exibe o resultado, que deve ser None por causa da divisão por zero

Configurando Loggers, Handlers e Formatters

O logging em Python tem vários níveis (DEBUG, INFO, WARNING, ERROR, CRITICAL). Por padrão, o logging captura níveis WARNING e superiores, a menos que configurado de outra forma.

  • Um logger é um ponto de entrada para o logging;

  • handlers enviam mensagens de log para destinos configurados como arquivos ou consoles;

  • formatters definem o layout das mensagens de log.

import logging

# Cria um objeto logger com o nome 'exemplo_logger'
logger = logging.getLogger('exemplo_logger')
# Define o nível de logging para DEBUG. Isso significa que todas as mensagens com nível DEBUG e acima serão registradas
logger.setLevel(logging.DEBUG)

# Cria um handler que imprimirá mensagens de log no console (saída padrão)
console_handler = logging.StreamHandler()
# Cria um handler que escreverá mensagens de log em um arquivo chamado 'example.log'
file_handler = logging.FileHandler('exemplo.log')

# Define um formatter que especifica o formato das mensagens de log
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# Define o formatter para o console handler
console_handler.setFormatter(formatter)
# Define o formatter para o file handler
file_handler.setFormatter(formatter)

# Adiciona o console handler ao logger. Isso significa que mensagens de log serão exibidas no console
logger.addHandler(console_handler)
# Adiciona o file handler ao logger. Isso significa que mensagens de log serão escritas no arquivo 'example.log'
logger.addHandler(file_handler)

# Loga uma mensagem informativa com o logger
logger.info('Esta é uma mensagem informativa')

A execução do script acima produzirá os seguintes logs, tanto no console quanto no arquivo example.log.

2024-06-06 18:38:33,511 - exemplo_logger - INFO - Esta é uma mensagem informativa
INFO:exemplo_logger:Esta é uma mensagem informativa

⚠️ No Google Colab, se você quiser verificar o conteúdo do arquivo, selecione o ícone da pasta chamada 'Arquivos' na barra lateral esquerda.

Rotacionamento de Arquivos de Log

O rotacionamento de arquivos de log é essencial para manter um sistema de logging eficiente e gerenciável em qualquer aplicação. À medida que uma aplicação executa, seus arquivos de log podem crescer indefinidamente, levando ao aumento do uso do disco e potencialmente causando problemas de desempenho ou falta de espaço em disco.

Ao rotacionar arquivos de log, você limita o tamanho e o número de arquivos de log retidos, garantindo que você só mantenha os logs mais recentes e relevantes para fins de resolução e monitoramento de problemas. A rotação de logs também melhora a legibilidade dos logs, impedindo que qualquer arquivo de log se torne excessivamente grande e difícil de manusear. Além disso, melhora a segurança, garantindo que informações sensíveis não permaneçam acessíveis indefinidamente.

import logging  # Importa o módulo de logging para lidar com logs
from logging.handlers import RotatingFileHandler  # Importa o RotatingFileHandler para criar um sistema de log rotativo

# Cria um objeto de logger chamado 'rotating_logger'
logger = logging.getLogger('rotating_logger')
# Define o nível de logging para DEBUG para que todos os níveis (DEBUG, INFO, WARNING, ERROR e CRITICAL) sejam registrados
logger.setLevel(logging.DEBUG)

# Cria um handler de arquivo rotativo que escreverá no 'rotating_log.log'
# maxBytes=2000 significa que cada arquivo de log pode conter até 2000 bytes
# backupCount=5 significa que o handler manterá até 5 arquivos de log de backup antes de sobrescrever o mais antigo
rotating_handler = RotatingFileHandler('rotating_log.log', maxBytes=2000, backupCount=5)

# Define um formato para as mensagens de log
# O formato inclui o timestamp, nome do logger, nível do log e a mensagem
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# Aplica o formatter ao rotating handler
rotating_handler.setFormatter(formatter)

# Adiciona o rotating handler ao logger para que ele saiba como lidar com o logging
logger.addHandler(rotating_handler)

# Gera 100 mensagens de log de depuração
for i in range(100):
    # Cada mensagem de depuração conterá o índice do loop
    logger.debug(f"Mensagem de depuração {i}")

Projeto em Ação: Da Teoria à Prática

Para consolidar seu entendimento, vamos criar um projeto onde combinamos os conceitos. Vamos supor que estamos construindo um scraper web simples que extrai dados de um site. Queremos registrar vários eventos, como o início do scraper, erros encontrados e extração de dados bem-sucedida. Aqui está um exemplo passo a passo:

Passo 1: Configuração da Configuração de Logging

import logging

# Cria um objeto logger com o nome 'web_scraper_logger'
web_scraper_logger = logging.getLogger('web_scraper')

# Define o nível de logging para DEBUG. Isso significa que todas as mensagens com o nível DEBUG e superior serão registradas
web_scraper_logger.setLevel(logging.DEBUG)

# Cria handlers que irão imprimir mensagens de log no console (saída padrão) e escrever mensagens de log em um arquivo chamado 'web_scraper.log'
console_handler = logging.StreamHandler()
file_handler = logging.FileHandler('web_scraper.log')

# Define um formatter que especifica o formato das mensagens de log
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# Define o formatter para o console handler e para o file handler
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

# Adiciona o console handler ao logger. Isso significa que as mensagens de log serão exibidas no console
web_scraper_logger.addHandler(console_handler)
# Adiciona o file handler ao logger. Isso significa que as mensagens de log serão escritas no arquivo 'web_scraper.log'
web_scraper_logger.addHandler(file_handler)

Passo 2: Criando uma Função de Web Scraping

Para simplificar, vamos criar um scraper web básico que usa requests e BeautifulSoup para buscar o título de uma página web.

import requests # Importa a biblioteca requests para fazer requisições HTTP
from bs4 import BeautifulSoup # Importa a classe BeautifulSoup da biblioteca bs4 para analisar conteúdo HTML 

def buscar_titulo(url):
    """
    Função para buscar o título de uma página da web dado o seu URL.

    Args:
        url (str): O URL da página da web a ser raspada.

    Retorna:
        str ou None: O título da página da web, ou None se ocorrer um erro.
    """

    # Registra o início do processo de raspagem da web
    web_scraper_logger.info(f"Iniciando a raspagem do URL: {url}")

    try:
        # Envia uma requisição GET para o URL especificado
        resposta = requests.get(url)

        # Levanta uma exceção HTTPError para códigos de resposta 4xx ou 5xx
        resposta.raise_for_status()
    except requests.RequestException as e:
        # Registra o erro se a requisição HTTP falhar
        web_scraper_logger.error(f"Falha na requisição HTTP: {e}")
        return None

    try:
        # Analisa o conteúdo HTML da resposta usando BeautifulSoup
        sopa = BeautifulSoup(resposta.content, 'html.parser')

        # Extrai o conteúdo da tag <title>
        titulo = sopa.title.string

        # Registra uma mensagem indicando a extração bem-sucedida do título
        web_scraper_logger.info(f"Título obtido com sucesso: {titulo}")
        return titulo
    except Exception as e:
        # Registra quaisquer erros que ocorram durante a análise de HTML ou extração do título
        web_scraper_logger.error(f"Falha ao analisar HTML ou extrair título: {e}")
        return None

Passo 3: Usando o Logging Dentro da Função

Agora vamos testar nosso scraper web e ver o logging em ação.

if __name__ == '__main__':
    urls = [
        'https://www.python.org',
        'https://www.example.com',
        'https://urlinexistente.com.br'  # Uma URL que causará uma solicitação falhada
    ]

    for url in urls:
        titulo = buscar_titulo(url)
        if titulo:
            web_scraper_logger.debug(f"Título raspado para {url}: {titulo}")
        else:
            web_scraper_logger.warning(f"Nenhum título encontrado para {url}")

A execução do script acima produzirá os seguintes logs, tanto no console quanto no arquivo web_scraper.log.

2024-06-06 18:44:22,732 - web_scraper - INFO - Iniciando a raspagem do URL: https://www.python.org
INFO:web_scraper:Iniciando a raspagem do URL: https://www.python.org
2024-06-06 18:44:22,907 - web_scraper - INFO - Título obtido com sucesso: Welcome to Python.org
INFO:web_scraper:Título obtido com sucesso: Welcome to Python.org
2024-06-06 18:44:22,916 - web_scraper - DEBUG - Título raspado para https://www.python.org: Welcome to Python.org
DEBUG:web_scraper:Título raspado para https://www.python.org: Welcome to Python.org
2024-06-06 18:44:22,924 - web_scraper - INFO - Iniciando a raspagem do URL: https://www.example.com
INFO:web_scraper:Iniciando a raspagem do URL: https://www.example.com
2024-06-06 18:44:23,061 - web_scraper - INFO - Título obtido com sucesso: Example Domain
INFO:web_scraper:Título obtido com sucesso: Example Domain
2024-06-06 18:44:23,069 - web_scraper - DEBUG - Título raspado para https://www.example.com: Example Domain
DEBUG:web_scraper:Título raspado para https://www.example.com: Example Domain
2024-06-06 18:44:23,075 - web_scraper - INFO - Iniciando a raspagem do URL: https://urlinexistente.com.br
INFO:web_scraper:Iniciando a raspagem do URL: https://urlinexistente.com.br
2024-06-06 18:44:23,242 - web_scraper - ERROR - Falha na requisição HTTP: HTTPSConnectionPool(host='urlinexistente.com.br', port=443): Max retries exceeded with url: / (Caused by NameResolutionError("<urllib3.connection.HTTPSConnection object at 0x7fbb8a714220>: Failed to resolve 'urlinexistente.com.br' ([Errno -2] Name or service not known)"))
ERROR:web_scraper:Falha na requisição HTTP: HTTPSConnectionPool(host='urlinexistente.com.br', port=443): Max retries exceeded with url: / (Caused by NameResolutionError("<urllib3.connection.HTTPSConnection object at 0x7fbb8a714220>: Failed to resolve 'urlinexistente.com.br' ([Errno -2] Name or service not known)"))
2024-06-06 18:44:23,245 - web_scraper - WARNING - Nenhum título encontrado para https://urlinexistente.com.br
WARNING:web_scraper:Nenhum título encontrado para https://urlinexistente.com.br

Conclusão

Cobrimos o espectro do logging em Python — desde o básico de configurar logging até conceitos mais avançados como rotacionamento de arquivos de log. Com exemplos práticos e um projeto abrangente, você agora tem tudo o que precisa para incorporar um logging eficaz em suas aplicações Python, garantindo que seu código seja mantível, depurável e pronto para produção.

Sinta-se à vontade para responder a este boletim informativo com quaisquer perguntas ou tópicos que você gostaria que abordássemos no futuro.

Se você gostou deste boletim informativo, não se esqueça de se inscrever para receber atualizações regulares. Compartilhe com seus amigos e colegas interessados em Python e vamos crescer juntos em nossa comunidade de programadores!

Lembre-se, a chave para a maestria é a prática e a persistência. Feliz codificação! Até a próxima edição, continue programando! 👨‍💻

InfinitePy Newsletter - Sua fonte de aprendizado e inspiração em Python.