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.
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ável | Padrão | O que faz |
|---|---|---|
| UPWATCH_LISTEN | :8080 | Endereço de escuta |
| UPWATCH_DB_DRIVER | sqlite | sqlite ou postgres |
| UPWATCH_DB_DSN | /data/upwatch.db | Caminho do arquivo ou string de conexão |
| UPWATCH_RETENTION_RAW | 168h | Quanto tempo as batidas cruas duram |
| UPWATCH_RETENTION_HOURLY | 2160h | Retenção do agregado horário |
| UPWATCH_RETENTION_DAILY | 17520h | Retenção do agregado diário |
| UPWATCH_WORKERS | 50 | Teto de verificações simultâneas |
| UPWATCH_SECURE_COOKIES | false | Ligue ao servir por HTTPS |
| UPWATCH_PUBLIC_URL | vazio | Endereç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.