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
GETouHEAD) 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ódulorequestsque 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 / Canal | Vantagens | Desvantagens | Cenário Recomendado |
| Discord / Slack Webhook | Configuração instantânea, notificações push em equipes, sem custo de SMS | Requer conectividade de saída e dependência da API da plataforma | Alertas de incidentes operacionais para equipes de TI e DevOps |
| E-mail SMTP (SSL/TLS) | Padrão universal, suporte a mensagens detalhadas com logs anexados | Maior risco de cair em filtros de spam sem configurações SPF/DKIM | Relató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 SO | Mede apenas o tempo do cliente, não isola latência DNS de rede | Benchmarking interno de resposta HTTP e diagnóstico de SLAs |
Recomendações para Implantação em Produção
- Execução como Serviço de Sistema (Systemd): No Linux, configure o script como um serviço
systemdcom reinicialização automática em caso de falha (Restart=always). - 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.
- Configuração de Métodos HTTP: Utilize requisições
HEADem vez deGETquando o objetivo for apenas validar a existência e status da URL sem transferir o corpo da página, economizando largura de banda. - 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.