Respsta gerada pelo claude com o contexto do meu projeto:
Cara, você descreveu exatamente o ponto cego que quase todo mundo tem. Infra é o problema fácil (binário: subiu/caiu, tem ferramenta boa pra caramba). O difícil é o que você listou: health check verde, container de pé — e mesmo assim o fluxo que importa não terminou.
Toco um SaaS de e-commerce de joia 3D (upload de STL, render, pagamento, nota, frete) — mesma família de dor: fila, worker, mídia, job assíncrono. O que virou a chave aqui foi parar de instrumentar infra e instrumentar desfecho de domínio. Dois sinais, e o segundo é o que pega o silêncio:
1. Contador de fluxo com outcome. Cada etapa emite evento_total{outcome="success|failed"}. Render, thumbnail, notificação, PDF — todos com taxa de falha visível. Pega "worker falhou num item específico".
2. Gauge de estado preso — o pulo do gato. Em vez de "o job rodou", eu meço "quantas coisas estão num estado não-terminal além do SLA" (ex.: presos_em_pending há +5 min). Alerta quando fica > 0. É exatamente o "job rodou com sucesso mas deixou o usuário sem resultado" — sucesso do job ≠ desfecho entregue. E o mesmo job de reconciliação que mede o preso também reprocessa, fechando o loop antes do usuário abrir ticket.
Os outros silêncios que você citou:
- Dead-letter durável: mensagem envenenada não some — vai pro Postgres (outbox/inbox do Wolverine), com contador + alerta e reprocessável.
- Fila sem consumer: alerta em
queue_messages > 0 AND consumers == 0. - Trace correlacionado (
trace_idno log): do alerta pulo direto pro trace daquele item. Sem grep.
O mapa do que roda:
graph LR
API[API dotnet]
WORKER[Worker dotnet]
UI[UI browser Faro]
ALLOY[Alloy coletor unico]
PROM[Prometheus metricas]
LOKI[Loki logs]
TEMPO[Tempo traces]
PGX[postgres-exporter]
BB[blackbox-exporter]
RMQ[RabbitMQ metricas]
GRAF[Grafana dashboards e alertas]
API -->|OTLP metricas e traces| ALLOY
API -->|logs Serilog| LOKI
WORKER -->|OTLP metricas e traces| ALLOY
WORKER -->|logs Serilog| LOKI
UI -->|Faro web vitals e erros| ALLOY
ALLOY -->|metricas| PROM
ALLOY -->|traces| TEMPO
ALLOY -->|logs Faro| LOKI
PGX --> PROM
BB --> PROM
RMQ --> PROM
PROM --> GRAF
LOKI --> GRAF
TEMPO --> GRAF
Direto ao que você perguntou:
- Sentry vs OTel? Não é ou-ou. Sozinho, começa com Sentry pra exceção (10 min, já mata "falha silenciosa"). A parte de pipeline (fila, estado preso, backlog) ele não pega bem — aí entra métrica+alerta.
- Valeu o OTel/Tempo/Grafana? Valeu — mas só porque instrumentei desfecho de negócio. Se subir a stack e só olhar latência HTTP e CPU, vira a aba esquecida que você citou. O ROI tá nos 4-5 alertas de fluxo, não no LGTM completo.
- A fricção real: não é subir o Grafana, é manter a instrumentação viva dentro do código de domínio. Isso morre num sidecar longe do código. Regra: mexeu numa transição de estado, mexe na métrica junto, faz parte do PR.
- Ponto de equilíbrio pra time pequeno (80% dos silêncios com 20% do trabalho): (1)
outcomeem cada etapa; (2) gauge de "presos > X min" + job que reprocessa; (3) dead-letter que persiste; (4) alerta em fila sem consumer. Roda num Prometheus + Alertmanager modestos. Tracing e RUM são o passo 2.
Numa frase: pare de alertar em "o job rodou" e comece a alertar em "o resultado que o usuário esperava existe". O primeiro fica verde enquanto a experiência de alguém está quebrada.
A stack de observabilidade é o LGTM da Grafana + exporters, num perfil Docker separado. O que cada peça faz:
Instrumentação (dentro dos apps)
| App | Função |
|---|---|
| OpenTelemetry SDK (.NET) | Gera métricas + traces da API e do Worker (AspNetCore, HttpClient, EF Core, runtime, Wolverine, Quartz, RateLimiting + um meter próprio Ourivus). Exporta via OTLP pro Alloy. |
| Serilog | Logs estruturados; escreve direto no Loki (não passa pelo Alloy — a redação de dado sensível é feita no app). |
| Grafana Faro (browser/UI) | RUM: web-vitals, erros JS, fetch/navegação do cliente. Manda pro Alloy no endpoint Faro. |
Coleta
| App | Função |
|---|---|
| Grafana Alloy | Coletor único. Recebe OTLP do backend (:4317/:4318) e Faro do browser (:9123), redige dados sensíveis (chaves, CPF, query strings, tokens) e roteia: traces → Tempo, métricas → Prometheus (remote-write), logs Faro → Loki. |
Armazenamento
| App | Função |
|---|---|
| Prometheus | TSDB de métricas (retenção 30d, exemplars). Também raspa os exporters e roda recording rules/alertas. |
| Loki | Armazena logs (backend + browser). Um ruler converte logs do Faro em métricas faro_*. |
| Tempo | Armazena traces. Gera span-metrics e service graph. Correlacionado aos logs por trace_id. |
Exporters / probes
| App | Função |
|---|---|
| postgres-exporter | Métricas do Postgres do produto (role read-only com pg_monitor). |
| blackbox-exporter | Liveness/TLS dos apps via probe HTTP em /health/live (os apps não são raspados — métrica chega por push OTLP). |
| RabbitMQ (plugin nativo) | Expõe métricas de fila (rabbitmq_queue_*) em :15692; o Prometheus raspa. |
Visualização
| App | Função |
|---|---|
| Grafana | Dashboards (Overview/SLO, API-RED, Worker/Filas, Negócio, RUM, Postgres/RabbitMQ/PDF, Host) + alertas de fluxo (5xx, p99, backlog, dead-letter, NFe presa, render falhando…), com datasources provisionados. |
node-exporter / cAdvisor são infra de host compartilhada — as métricas node_*/container_* vêm de um Prometheus HOST, adicionado ao Grafana como datasource Prometheus HOST. Os dashboards de infra apontam pra ele.
OBS: toda essa stack roda num único docker-compose no mesmo servidor da aplicação