Uma equipe de DevOps acordou com 47 chamados pendentes. O dia anterior tinha sido normal: deploy às 14h, smoke test passou, ninguém notou nada. Mas durante a madrugada, uma das instâncias críticas (a do cliente que mais factura) tinha desconectado às 3h12. Reconectou sozinha às 3h45, mas as mensagens enfileiradas durante esses 33 minutos não foram entregues — porque o circuit breaker abriu, descartou os jobs com mais de 30 minutos de idade. Resultado: 1.247 mensagens perdidas, cliente furioso, gestor pedindo explicação.
O culpado não foi o bug em si. Bug em sistema distribuído acontece. O culpado foi a falta de observabilidade: ninguém soube que algo estava errado até o cliente reclamar. Sem logs estruturados, métricas e alertas, sua aplicação está cega — você só descobre o problema quando ele virou crise. Este guia mostra como montar stack completo de observabilidade para integrações WhatsApp em produção, do log básico até dashboards Grafana e alertas Slack.
Os 3 pilares
Observabilidade não é só "ter logs". É a combinação de três coisas:
- Logs: evento textual com contexto. Útil para debug pontual ("o que aconteceu com a mensagem msg_abc?")
- Métricas: números agregados por janela de tempo. Útil para tendência ("taxa de erro subiu nos últimos 15min?")
- Alertas: regras que disparam ação humana ou automática quando métrica viola limite
Faltar um dos três cega a aplicação. Logs sem métricas = você acha bug, mas não sabe se está espalhado. Métricas sem alerta = você tem dashboard bonito mas ninguém olha. Alerta sem log = você sabe que algo quebrou mas não consegue debugar.
Logs estruturados em JSON
Texto livre é inútil em escala. JSON estruturado é parseável, indexável e filtrável.
// ❌ Ruim
console.log("Mensagem enviada para 5511999999999");
// ✅ Bom
logger.info({
event: "message.sent",
instanceId: "inst_abc123",
to: "5511999999999",
messageId: "msg_xyz789",
bytes: 142,
durationMs: 87,
});
Bibliotecas: pino (Node.js, mais rápida), winston (mais features). Saída direto pra stdout — Docker captura e envia pra agregador (Loki, ELK, Datadog).
Endpoints de observabilidade da ZAP API
A ZAP API expõe 3 endpoints prontos pra você consumir:
GET /v1/instances/:id/health— health score 0-100 + flagsGET /v1/diagnostics/agent-snapshot— métricas plataforma autenticadas por X-Diagnostic-TokenGET /v1/super-admin/summary?window=24h— totais agregados com sparklines
Dashboard Node.js consumindo todos os endpoints
import axios from "axios";
import express from "express";
const ZAP = axios.create({
baseURL: "https://api.zap-api.tech/v1",
headers: {
Authorization: `Bearer ${process.env.ZAP_TOKEN}`,
"X-Diagnostic-Token": process.env.ZAP_DIAGNOSTIC_TOKEN,
},
});
const app = express();
app.get("/dashboard", async (req, res) => {
const [summary, snapshot, instances] = await Promise.all([
ZAP.get("/super-admin/summary?window=24h"),
ZAP.get("/diagnostics/agent-snapshot"),
ZAP.get("/instances"),
]);
const healths = await Promise.all(
instances.data.map((i) => ZAP.get(`/instances/${i.id}/health`).then((r) => r.data))
);
res.json({
plataforma: summary.data,
sistema: snapshot.data,
instancias: healths,
});
});
app.listen(4000);
Alertas: do silêncio ao Slack
Regras simples cobrem 80% dos casos. Cron a cada 5 minutos checa, dispara webhook Slack se algo está errado.
import cron from "node-cron";
import axios from "axios";
cron.schedule("*/5 * * * *", async () => {
const instances = await ZAP.get("/instances");
for (const inst of instances.data) {
const health = await ZAP.get(`/instances/${inst.id}/health`);
if (health.data.score < 70) {
await axios.post(process.env.SLACK_WEBHOOK_URL, {
text: `⚠️ ${inst.name} score ${health.data.score}/100 — ${health.data.flags.join(", ")}`,
});
}
if (inst.waStatus !== "CONNECTED" && inst.disconnectedSince) {
const minsOff = Math.floor((Date.now() - new Date(inst.disconnectedSince)) / 60000);
if (minsOff > 10) {
await axios.post(process.env.SLACK_WEBHOOK_URL, {
text: `🔴 ${inst.name} desconectada há ${minsOff}min`,
});
}
}
}
});
Custom exporter para Prometheus / Grafana
Quando você tem stack Prometheus rodando, exporter custom pluga métricas direto no scrape.
import express from "express";
import client from "prom-client";
const register = new client.Registry();
client.collectDefaultMetrics({ register });
const messagesSent = new client.Counter({
name: "zap_messages_sent_total",
help: "Total messages sent",
labelNames: ["instance"],
registers: [register],
});
const instanceScore = new client.Gauge({
name: "zap_instance_health_score",
help: "Health score 0-100",
labelNames: ["instance"],
registers: [register],
});
// Atualiza métricas a cada 30s
setInterval(async () => {
const instances = await ZAP.get("/instances");
for (const inst of instances.data) {
const health = await ZAP.get(`/instances/${inst.id}/health`);
instanceScore.set({ instance: inst.name }, health.data.score);
}
}, 30_000);
// Webhook que recebe message.sent e incrementa
app.post("/webhook/zap", express.json(), (req, res) => {
if (req.body.type === "message.sent") {
messagesSent.inc({ instance: req.body.instanceName });
}
res.status(200).send("ok");
});
app.get("/metrics", async (req, res) => {
res.set("Content-Type", register.contentType);
res.end(await register.metrics());
});
app.listen(9090);
Configure Prometheus para fazer scrape em http://exporter:9090/metrics. Grafana puxa do Prometheus e renderiza dashboards.
SLI / SLO / SLA: a diferença
- SLI (Service Level Indicator): métrica observada — ex: "% mensagens entregues em até 30s"
- SLO (Service Level Objective): meta interna — ex: "99,5% das mensagens entregues em 30s"
- SLA (Service Level Agreement): contrato com cliente — ex: "99,0% ou crédito de 10%"
SLO é interno e mais rigoroso que SLA — se você fica em SLO, está dentro do SLA com folga. Defina SLO por SLI crítico: latência de envio, taxa de entrega, uptime de instância.
Anti-padrões de observabilidade que te deixam cego
Ter logs não significa ter observabilidade. Esses erros são comuns mesmo em times experientes:
- Log sem correlação: cada serviço loga com IDs próprios, mas não existe
traceIdcomum que amarre a jornada. Você sabe que a mensagem falhou, mas não consegue rastrear em qual hop. Solução: geretraceIdno ponto de entrada e propague em todos os serviços downstream via headerX-Trace-Id. - Alerta que ninguém lê: canal Slack com 200 alertas por dia que todo mundo silenciou. Alerta sem confiança não vale nada — pior que alerta falso é alerta ignorado. Revise regras mensalmente, prune alertas que nunca geraram ação real.
- Dashboard sem contexto de negócio: gráfico de "mensagens enviadas" sem comparação com meta do dia não diz nada. Adicione linha de target: se você precisa enviar 10k mensagens por dia, dashboard mostra progresso vs meta. Métrica sem alvo é decoração.
- Log de sucesso sem log de falha estruturado: logar só o que funciona. Quando algo falha, o log é um erro genérico do Express. Defina um schema fixo para erros:
{event: "message.send_failed", instanceId, to, errorCode, errorMessage, attempt}. Erro estruturado é pesquisável; stack trace cru não é. - Alerta só em produção: staging sem alerta significa que problemas chegam em produção sem aviso prévio. Mantenha o mesmo stack de observabilidade em staging — alertas com threshold mais permissivo, mas existentes.
Runbook: o que fazer quando alerta dispara
Alerta sem runbook é pânico documentado. Para cada alerta recorrente, documente 5 linhas:
// Exemplo de runbook para "instância desconectada > 10min"
const RUNBOOK_DISCONNECTED = {
alerta: "Instância desconectada há > 10 minutos",
causas: [
"Celular do cliente foi para airplane mode ou desligou",
"WhatsApp no celular foi forçado a fechar pelo SO",
"Sessão expirou por inatividade",
"QR code scaneado por outro dispositivo (sessão roubada)",
],
passos: [
"1. Checar GET /v1/instances/:id/health — olhar flags",
"2. Se waStatus=QR_REQUIRED: enviar mensagem WhatsApp pro cliente com link de reconexão",
"3. Se waStatus=CONNECTING há > 2 min: POST /v1/instances/:id/disconnect + connect",
"4. Se reconnect falhou 3x: escalar para cliente via email com ticket",
"5. Registrar incidente se mais de 3 instâncias afetadas simultaneamente",
],
escalacao: "Se não resolver em 30min → PagerDuty on-call",
};
Com runbook, on-call de plantão às 3h da manhã segue lista e resolve. Sem runbook, cada incidente é reinventar a roda sob pressão.
FAQ
Qual o stack mínimo viável para uma equipe pequena?
Pino para logs (stdout) + Better Stack ou Logtail para agregação ($0-29/mês) + cron Node.js consumindo ZAP API endpoints + webhook Slack para alerta. Custa entre $0 e $50/mês e cobre 80% dos casos. Não precisa Prometheus/Grafana até atingir 10+ instâncias. A ordem de prioridade é: (1) logs estruturados — sem custo além de stdout; (2) alerta básico para instância desconectada — 2h de dev; (3) dashboard de métricas — só depois que os dois anteriores estiverem estáveis. Equipes que inverteram essa ordem ficaram com dashboards bonitos e instâncias caindo sem ninguém saber.
Quanto custa observabilidade em produção real?
Para 10-50 instâncias, $50-200/mês cobre logs (Loki/Logtail), métricas (Grafana Cloud free tier ou Prometheus self-hosted), alerta (Slack grátis). Para 100+ instâncias, Datadog/New Relic ($150-500/mês) escala melhor mas custa mais. Self-hosted (Prometheus + Grafana + Loki) é gratuito mas exige 2-4h/semana de manutenção de infraestrutura — considere o custo de oportunidade. Regra prática: até $200/mês de plataforma de observabilidade é justificado para qualquer negócio que tenha cliente pagando.
Como evitar alertas falsos positivos?
Três regras: (a) persistência — alerta só dispara se condição persistir por X minutos (ex: score < 70 por 10min, não 1 ponto isolado); (b) cooldown — não realertar pelo mesmo problema em 1h; (c) severidade — separar warning (Slack canal-monitor) de critical (Slack canal-incident + PagerDuty). Além disso, faça "alerta review" mensal: liste todos os alertas que dispararam no mês, qual % gerou ação real, qual % foi falso positivo. Se algum alerta tem > 50% de falso positivo, ajuste threshold ou elimine.
Quanto tempo manter logs?
Logs operacionais: 30-90 dias. Logs de auditoria (quem fez o quê): 1-7 anos (depende da regulação — LGPD recomenda mínimo necessário; alguns setores têm obrigação de 5 anos). Logs de payload de webhook: 7-30 dias (LGPD — não acumular dado de mensagem de cliente sem necessidade). Configure rotação automática. Dica: comprimido, log JSON de 1 dia em produção média ocupa 5-50 MB — custo de storage é irrisório comparado ao valor de ter o log disponível quando necessário.
Como organizar on-call?
Para equipe pequena (2-4 dev), rotação semanal: 1 pessoa de plantão, 1 backup. Alerta vai para PagerDuty/OpsGenie ($10-30/usuário). Critical = liga (qualquer hora), warning = notifica em horário comercial. Documente runbook por tipo de alerta — sem runbook, on-call vira pesquisa no Confluence às 3h da manhã. Faça "game day" trimestral: simule falha em staging durante hora comercial, equipe segue runbook, meça tempo de resolução e pontos de fricção. Prática em ambiente controlado evita pânico em ambiente real.
Nunca mais fique cego em produção. A ZAP API expõe endpoints prontos de health, diagnostics e summary com X-Diagnostic-Token. Prometheus exporter, dashboards Grafana e webhooks pra Slack — tudo documentado. Criar conta grátis e plugue seu stack.