Como Criar um Verificador de Disponibilidade de URLs (HTTP Uptime Monitor) em Python

Como Criar um Verificador de Disponibilidade de URLs (HTTP Uptime Monitor) em Python

Garantir que aplicações web, APIs e sites institucionais permaneçam acessíveis e responsivos é um requisito fundamental na engenharia de software e na gestão de infraestrutura. A indisponibilidade de um serviço (downtime) gera prejuízos financeiros, degrada a experiência do usuário e compromete a reputação da organização. Um monitor de disponibilidade sintético (HTTP Uptime Monitor) atua como uma linha de defesa preventiva, realizando requisições periódicas para validar a integridade dos endpoints e disparar alertas imediatos quando falhas são detectadas.

Este artigo apresenta um guia completo para construir um verificador de disponibilidade robusto e escalável em Python. Serão abordados desde os conceitos fundamentais de checagem HTTP e medição de latência com alta precisão até o tratamento de exceções de rede e a integração com sistemas automáticos de notificação via Webhook e E-mail.

1. Arquitetura de um HTTP Uptime Monitor em Python

Um monitor sintético de disponibilidade funciona simulando o comportamento de um cliente HTTP que acessa a aplicação em intervalos de tempo pré-determinados. A arquitetura de um sistema resiliente baseia-se em quatro pilares principais:

  • Coletor de Métricas HTTP: Realiza requisições HTTP (geralmente do tipo GET ou HEAD) para uma lista de URLs configuradas, definindo limites estritos de tempo de espera (timeout).
  • Mecanismo de Validação: Avalia a resposta recebida examinando o código de status HTTP (onde faixas entre 200 e 299 indicam operação normal, enquanto faixas 4xx e 5xx sinalizam erros) e a latência da conexão.
  • Motor de Resiliência e Tratamento de Erros: Captura falhas de resolução de DNS, recusas de conexão e estouro de tempo limite sem interromper a execução do serviço.
  • Gerenciador de Alertas e Logging: Dispara notificações em tempo real para canais de comunicação (como Discord, Slack ou E-mail) assim que o estado do endpoint transiciona de operacional (UP) para indisponível (DOWN), registrando o histórico de eventos.

A escolha de Python para essa tarefa justifica-se pela rica biblioteca padrão e pelo ecossistema de módulos como requests, que simplifica a execução de chamadas HTTP e a manipulação de payloads de rede.

2. Checagem de Status HTTP e Medição de Latência de Alta Precisão

A verificação do estado de uma URL exige não apenas confirmar a resposta do servidor, mas também medir o tempo decorrido até a conclusão da requisição. Conforme documentado em tutoriais de benchmarking em Python, a medição precisa de duração de processos deve utilizar a função time.perf_counter() do módulo nativo time. Diferente de time.time(), que pode sofrer variações bruscas devido a sincronizações do relógio do sistema ou ajustes de fuso horário, time.perf_counter() utiliza um contador de desempenho de alta resolução imune a alterações do sistema operacional.

O script a seguir demonstra como realizar uma verificação básica de URL capturando o código de status HTTP e calculando a latência em milissegundos:

import time
import requests

def verificar_url(url: str, timeout: float = 5.0) -> dict:
    """
    Realiza uma requisição HTTP GET e mede o tempo de resposta do servidor.
    """
    inicio = time.perf_counter()
    
    try:
        resposta = requests.get(url, timeout=timeout)
        fim = time.perf_counter()
        latencia_ms = (fim - inicio) * 1000
        
        status_ok = 200 <= resposta.status_code < 300
        
        return {
            "url": url,
            "status_code": resposta.status_code,
            "latencia_ms": round(latencia_ms, 2),
            "online": status_ok,
            "erro": None
        }
    except requests.exceptions.RequestException as e:
        fim = time.perf_counter()
        latencia_ms = (fim - inicio) * 1000
        
        return {
            "url": url,
            "status_code": None,
            "latencia_ms": round(latencia_ms, 2),
            "online": False,
            "erro": str(e)
        }

if __name__ == "__main__":
    resultado = verificar_url("https://example.com")
    print(f"URL: {resultado['url']} | Status: {resultado['status_code']} | Latência: {resultado['latencia_ms']}ms")

3. Resiliência e Tratamento de Falhas de Rede e Timeouts

Em ambientes de produção, falhas de rede, estouro de tempo limite e servidores indisponíveis são inevitáveis. Uma falha crítica em monitores ingênuos é omitir o parâmetro timeout na chamada da biblioteca requests. Sem um limite explícito, o Python pode aguardar indefinidamente por uma resposta do socket TCP, travando o script de monitoramento.

Como destacado nas diretrizes de resiliência em chamadas HTTP e captura de eventos em sistemas de arquivos, o tratamento de exceções deve distinguir os diferentes tipos de erro para um diagnóstico preciso do incidente:

  • requests.exceptions.Timeout: O servidor demorou mais do que o tempo limite especificado para responder.
  • requests.exceptions.ConnectionError: Falha ao resolver o nome de domínio (DNS), conexão recusada pelo servidor ou queda na interface de rede.
  • requests.exceptions.HTTPError: O servidor retornou uma resposta HTTP indicando erro de cliente ou servidor (faixas 4xx e 5xx).
  • requests.exceptions.RequestException: Exceção base do módulo requests que captura qualquer outra anomalia de comunicação.

Abaixo está a implementação aprimorada da função de checagem com captura granular de erros e mapeamento de diagnósticos:

import requests

def checar_disponibilidade_avancada(url: str, timeout_segundos: float = 3.0) -> dict:
    try:
        resposta = requests.get(url, timeout=timeout_segundos)
        resposta.raise_for_status()
        
        return {
            "url": url,
            "status": "UP",
            "codigo_http": resposta.status_code,
            "mensagem": "Serviço operacional"
        }
    except requests.exceptions.Timeout:
        return {
            "url": url,
            "status": "DOWN",
            "codigo_http": None,
            "mensagem": f"Tempo limite excedido ({timeout_segundos}s)"
        }
    except requests.exceptions.ConnectionError:
        return {
            "url": url,
            "status": "DOWN",
            "codigo_http": None,
            "mensagem": "Falha na conexão TCP / DNS recusado"
        }
    except requests.exceptions.HTTPError as e:
        return {
            "url": url,
            "status": "DOWN",
            "codigo_http": e.response.status_code if e.response else None,
            "mensagem": f"Erro HTTP retornado: {e}"
        }
    except requests.exceptions.RequestException as e:
        return {
            "url": url,
            "status": "DOWN",
            "codigo_http": None,
            "mensagem": f"Erro genérico de rede: {e}"
        }

4. Sistema de Alertas Automáticos: Webhooks e E-mail

Detectar uma queda só é útil se a equipe responsável for notificada imediatamente. Um monitor de disponibilidade completo deve incluir múltiplos canais de saída para distribuição dos alertas.

Notificação via Webhook (Discord/Slack)

Webhooks HTTP consistem em URLs expostas por plataformas de mensagens que aceitam requisições POST com payloads formatados em JSON. Conforme detalhado nos guias de integração com Discord, o envio do alerta exige a inclusão do cabeçalho Content-Type: application/json e uma estrutura contendo a mensagem ou componentes visuais formatados.

Notificação via E-mail Seguro (SMTP)

Para alertas formais ou integrações corporativas, o protocolo SMTP garante a entrega direta na caixa de entrada. Conforme demonstrado nos manuais da biblioteca padrão de e-mail do Python, a construção da mensagem utiliza a classe EmailMessage do módulo email.message, e a conexão com o provedor é protegida por TLS/SSL usando smtplib.SMTP_SSL em conjunto com um contexto de segurança ssl.create_default_context().

O código a seguir consolida as duas estratégias de notificação em um módulo reutilizável:

import ssl
import smtplib
from email.message import EmailMessage
import requests

def enviar_alerta_webhook(webhook_url: str, mensagem: str) -> bool:
    payload = {"content": f"ALERTA DE INDISPONIBILIDADE: {mensagem}"}
    try:
        res = requests.post(webhook_url, json=payload, timeout=5.0)
        return res.status_code in (200, 204)
    except requests.exceptions.RequestException as e:
        print(f"Erro ao enviar Webhook: {e}")
        return False

def enviar_alerta_email(servidor_smtp: str, porta: int, remetente: str, chave_autenticacao: str, destinatario: str, assunto: str, corpo: str) -> bool:
    msg = EmailMessage()
    msg["From"] = remetente
    msg["To"] = destinatario
    msg["Subject"] = assunto
    msg.set_content(corpo)

    contexto_ssl = ssl.create_default_context()
    try:
        with smtplib.SMTP_SSL(servidor_smtp, porta, context=contexto_ssl) as server:
            server.login(remetente, chave_autenticacao)
            server.send_message(msg)
        return True
    except Exception as e:
        print(f"Erro ao enviar e-mail: {e}")
        return False

5. Loop de Monitoramento Contínuo e Logging Estruturado

Para transformar a checagem em um serviço contínuo, a execução é estruturada em um laço periódico com intervalos definidos por time.sleep(). Além disso, para evitar disparos repetitivos durante indisponibilidades prolongadas, a aplicação mantém o histórico do estado de cada endpoint, emitindo notificações apenas durante mudanças de estado (transição entre normal e com falha).

A gestão de configurações e endereços de destino deve utilizar variáveis de ambiente acessadas via os.getenv(), garantindo a separação entre o código-fonte e as parametrizações do ambiente.

Abaixo está o script completo do monitor integrado com o módulo nativo logging:

import os
import time
import logging
import requests

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s"
)

ENDPOINTS = ["https://example.com"]

def checar_status_servicos():
    historico = {url: True for url in ENDPOINTS}
    
    for url in ENDPOINTS:
        t_inicio = time.perf_counter()
        try:
            res = requests.get(url, timeout=5.0)
            duracao_ms = round((time.perf_counter() - t_inicio) * 1000, 2)
            is_online = (200 <= res.status_code < 300)
            
            if is_online != historico[url]:
                historico[url] = is_online
                logging.warning(f"Mudanca de estado em {url}: Online={is_online}")
            else:
                logging.info(f"Status {url}: HTTP {res.status_code} em {duracao_ms}ms")
        except requests.exceptions.RequestException as err:
            if historico[url]:
                historico[url] = False
                logging.error(f"Falha de conexao em {url}: {err}")

if __name__ == "__main__":
    checar_status_servicos()

6. Guia Prático de Implementação e Boas Práticas em Produção

A tabela a seguir resume as principais características técnicas das estratégias de alerta e métricas envolvidas no monitoramento de URLs:

A escolha dos canais e das métricas depende do nível de criticidade dos serviços monitorados e da infraestrutura disponível na organização.

Componente / CanalVantagensDesvantagensCenário Recomendado
Discord / Slack WebhookConfiguração instantânea, notificações push em equipes, sem custo de SMSRequer conectividade de saída e dependência da API da plataformaAlertas de incidentes operacionais para equipes de TI e DevOps
E-mail SMTP (SSL/TLS)Padrão universal, suporte a mensagens detalhadas com logs anexadosMaior risco de cair em filtros de spam sem configurações SPF/DKIMRelatórios diários e notificações para gestores e clientes
Medição por time.perf_counter()Resolução em microssemgundos, imune a saltos no relógio do SOMede apenas o tempo do cliente, não isola latência DNS de redeBenchmarking interno de resposta HTTP e diagnóstico de SLAs

Recomendações para Implantação em Produção

  1. Execução como Serviço de Sistema (Systemd): No Linux, configure o script como um serviço systemd com reinicialização automática em caso de falha (Restart=always).
  2. Execução Distribuída: Para evitar falsos positivos causados por problemas pontuais na rede do servidor de monitoramento, execute instâncias em diferentes regiões geográficas ou provedores de nuvem.
  3. Configuração de Métodos HTTP: Utilize requisições HEAD em vez de GET quando o objetivo for apenas validar a existência e status da URL sem transferir o corpo da página, economizando largura de banda.
  4. Gerenciamento do Limite de Taxa (Rate Limit): Ao enviar requisições frequentes para o mesmo servidor ou disparar Webhooks em lote, trate respostas HTTP 429 respeitando o cabeçalho Retry-After.

Thales de Oliveira Gomes

Thales de Oliveira Gomes é gestor de Tecnologia da Informação, especialista em infraestrutura de redes, computação em nuvem e operações de TI hospitalar, com mais de 18 anos de experiência na área. Graduado em Gestão de TI e pós-graduado em Engenharia de Redes e Telecomunicações, Cloud Computing, Redes de Computadores e Gestão Hospitalar, construiu uma trajetória sólida liderando projetos de alta complexidade, como a implementação e migração para nuvem de sistemas ERP de grande porte. Sua atuação no setor o levou a ser indicado ao prêmio IT Leaders Brasil na categoria Rising Leaders. Além da carreira corporativa e da paixão por tecnologia, Thales é autor independente com mais de 70 obras publicadas sobre redes, segurança da informação, Linux e física, além de criador de conteúdo no blog Vetor Tech e na plataforma CiberDicas. No universo das artes e do entretenimento, atua como produtor de música eletrônica sob o projeto Yknex, cria ilustrações digitais e compartilha walkthroughs de games no YouTube.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *