Monitoramento que mostra a latência junto com o estado.

Quase todo incidente começa com o serviço ficando lento, não caindo. O UpWatch codifica as duas coisas no mesmo traço — a cor diz se está no ar, a altura diz quanto demorou — para você ver a degradação antes da queda.

api-de-producao https://api.exemplo.com 100%34 ms
busca-interna https://busca.exemplo.com 92,4%1,2 s
gateway-pagamento tcp://gateway.interno:8443 18,2%

A altura de cada traço é a latência daquela verificação. Repare na segunda linha: ela sobe por vários ciclos antes de virar queda.

O que ele faz

Verifica de seis jeitos

HTTP, TCP, ICMP, DNS, certificado TLS e sinal do próprio serviço — este último para tarefas agendadas e processos sem porta exposta. Intervalo a partir de cinco segundos, por monitor.

Guarda meses sem guardar meses de dado cru

As batidas duram uma semana; depois viram agregado horário e diário. Cinco alvos verificando a cada minuto ocuparam 2,6 MB depois de trinta dias.

Confirma antes de alarmar

Uma queda só vira incidente depois de N falhas seguidas, configurável por monitor. Oscilação de rede não acorda ninguém de madrugada.

Sabe quando o problema é você

Se as verificações começam a falhar, uma sonda independente confere se a sua rede ainda responde. Se não responder, os resultados viram “sem medição” em vez de “fora do ar”.

Avisa e deixa você testar

Webhook, Discord e Slack, com um botão de teste em cada canal. Descobrir que o alerta não chega durante a queda é descobrir tarde demais.

O corpo do webhook é o seu

O destino que você já tem espera os campos com os nomes dele. Declare a forma, use os marcadores, e mande cabeçalho próprio se ele pedir chave.

Publica uma página de estado

No formato que Anthropic, Cloudflare e Google consolidaram — e que nunca revela o endereço do alvo nem a causa detectada.

Agrupa por etiqueta

Homolog e produção lado a lado sem virar uma lista única de quarenta linhas. As etiquetas são normalizadas: “Produção” e “produção ” são o mesmo grupo, não dois.

Dois papéis de acesso

Administrador altera; observador só lê — para plantão, gerência e time vizinho. A barreira está no servidor, não em botão escondido.

Instalar

Um binário, um contêiner, um volume. A interface vem embarcada no executável — não há nginx ao lado nem pasta de arquivos estáticos que possa ficar defasada em relação ao servidor.

Docker

docker run -d --name upwatch \
  -p 8080:8080 -v upwatch:/data \
  ghcr.io/jbnado/upwatch:latest

Abra http://localhost:8080 e crie a conta de administração. É a única conta criada sem autenticação; depois dela, o cadastro fecha.

Docker Compose

curl -O https://raw.githubusercontent.com/Jbnado/upwatch/main/compose.yaml
docker compose up -d

Com PostgreSQL

O SQLite dá conta de uma instalação inteira num arquivo, mas não de duas instâncias do UpWatch escrevendo ao mesmo tempo — que é o que se quer quando a disponibilidade do próprio monitorador importa.

curl -O https://raw.githubusercontent.com/Jbnado/upwatch/main/compose.postgres.yaml
POSTGRES_PASSWORD=$(openssl rand -base64 24) \
  docker compose -f compose.postgres.yaml up -d

Binário

Baixe da página de releases. É estático e não depende de nada instalado.

UPWATCH_DB_DSN=./upwatch.db ./upwatch

Configurar

Por variável de ambiente ou arquivo YAML. A variável ganha do arquivo, e o arquivo ganha do padrão — assim uma imagem sobe sem configuração nenhuma, e uma instalação séria versiona o YAML.

VariávelPadrãoO que faz
UPWATCH_LISTEN:8080Endereço de escuta
UPWATCH_DB_DRIVERsqlitesqlite ou postgres
UPWATCH_DB_DSN/data/upwatch.dbCaminho do arquivo ou string de conexão
UPWATCH_RETENTION_RAW168hQuanto tempo as batidas cruas duram
UPWATCH_RETENTION_HOURLY2160hRetenção do agregado horário
UPWATCH_RETENTION_DAILY17520hRetenção do agregado diário
UPWATCH_WORKERS50Teto de verificações simultâneas
UPWATCH_SECURE_COOKIESfalseLigue ao servir por HTTPS
UPWATCH_PUBLIC_URLvazioEndereço externo, para o feed usar URLs absolutas

Antes de expor na internet: ligue UPWATCH_SECURE_COOKIES e defina UPWATCH_PUBLIC_URL. O primeiro fica desligado por padrão porque muita instalação caseira serve em HTTP na rede local, e um cookie Secure ali simplesmente não é enviado — o login pareceria quebrado sem explicação.

Página pública de estado

Um endereço que qualquer pessoa abre sem entrar, para compartilhar disponibilidade com clientes e com o time. Você escolhe quais alvos aparecem — nada é publicado sem ser marcado.

As barras são automáticas; o relato, não

A causa que a sonda detecta é literal e interna, do tipo dial tcp 10.0.3.7:5432: connect: connection refused. Publicá-la entregaria endereço, porta e tecnologia de um serviço que ninguém de fora deveria enxergar — então ela nunca sai.

O que aparece em “incidentes anteriores” é o texto que você escreve, na linha do tempo de sempre: investigando → identificado → monitorando → resolvido. Uma instalação recém-subida mostra as barras e “nenhum incidente relatado”, que é o comportamento correto.

Cada componente tem um rótulo público

O monitor pode se chamar api-prod-us-east-1 na operação e aparecer como “API” para quem lê — sem obrigar você a renomeá-lo nem a entregar sua convenção de nomes.

Há também um feed Atom, para acompanhar sem cadastrar e-mail e para ligar num canal de chat sem que o UpWatch precise saber falar com aquele canal.

Webhook

Sem configuração extra, o corpo é um envelope JSON com todos os campos do evento: monitor, target, status, previous_status, message, at e duration_seconds.

Quando o destino já existe, a forma é dele

Adaptar quem recebe nem sempre é possível. Declare a forma em body_template e os marcadores são substituídos:

{
  "url": "https://automacao.exemplo/alertas",
  "headers": { "X-Chave": "…" },
  "body_template": {
    "event": "$status",
    "service": { "name": "$monitor", "id": "$monitor_id" },
    "outage_seconds": "$duration_seconds",
    "summary": "[$status] $monitor"
  }
}

Sozinho, o marcador entrega o valor com o tipo dele"$duration_seconds" chega como 300, não "300". Dentro de um texto, compõe a frase. A diferença importa para quem valida esquema do outro lado.

A substituição acontece sobre a estrutura JSON já decodificada, e o resultado é serializado de volta — nunca por concatenação. É o que garante que uma aspa no nome do monitor não produza um corpo que o destino recusa, perdendo o aviso da queda por causa da própria queda. Marcador desconhecido é recusado no cadastro do canal, não na entrega.

API e Prometheus

A API é de primeira classe, não um acessório da interface: a tela consome exatamente os mesmos endpoints que qualquer script. A especificação OpenAPI é servida pela própria instalação, e um teste garante que ela não diverge das rotas de verdade — nos dois sentidos.

curl -H "Authorization: Bearer upw_..." \
  http://localhost:8080/api/v1/monitors

Para quem já tem Prometheus, /metrics responde sem credencial:

scrape_configs:
  - job_name: upwatch
    static_configs:
      - targets: ['upwatch:8080']

Em upwatch_monitor_status, 1 é no ar, 0 fora do ar, 2 degradado e -1 sem medição. “Sem medição” não é zero de propósito: confundir os dois faria o alerta disparar para todo monitor recém-criado.

Como este projeto é feito

Escrito em TDD, e a suíte é o principal artefato de desenho — quase toda decisão estrutural apareceu porque um teste ficou difícil de escrever. Duas partes merecem destaque.

A conformidade do armazenamento roda contra os dois bancos

A mesma bateria, 124 casos idênticos no SQLite e no PostgreSQL, zero pulados. É o que impede “banco plugável” de virar fachada: um backend que não passe integralmente não entra.

A página pública é atacada de propósito

Travessia de caminho em nove formas, injeção de SQL no slug, enumeração de páginas, texto hostil, cabeçalho Host forjado. Um desses testes encontrou um defeito real durante o desenvolvimento — e é por isso que eles existem.