2

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_id no 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) outcome em 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)

AppFunçã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.
SerilogLogs 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

AppFunção
Grafana AlloyColetor ú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

AppFunção
PrometheusTSDB de métricas (retenção 30d, exemplars). Também raspa os exporters e roda recording rules/alertas.
LokiArmazena logs (backend + browser). Um ruler converte logs do Faro em métricas faro_*.
TempoArmazena traces. Gera span-metrics e service graph. Correlacionado aos logs por trace_id.

Exporters / probes

AppFunção
postgres-exporterMétricas do Postgres do produto (role read-only com pg_monitor).
blackbox-exporterLiveness/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

AppFunção
GrafanaDashboards (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

Carregando publicação patrocinada...