Os três pilares da observabilidade (traces, métricas e logs) montados manualmente com OpenTelemetry e correlacionados no Grafana, com SLO, error budget, alerta de burn-rate e um runbook. Sem Aspire: o objetivo aqui é entender a mecânica que as ferramentas costumam automatizar.
A aplicação exporta um único protocolo (OTLP) para o Collector, que faz o fan-out para os backends. Assim, trocar ou adicionar um backend é configuração do Collector, e não redeploy da aplicação (ADR-001).
flowchart LR
subgraph App[API .NET]
SDK[OTel SDK<br/>traces + metrics + logs]
end
App -->|OTLP gRPC| COL[OTel Collector]
COL -->|OTLP| TEMPO[(Tempo · traces)]
COL -->|OTLP| LOKI[(Loki · logs)]
COL -->|Prometheus exporter| PROM[(Prometheus · métricas)]
TEMPO --> GRAF[Grafana]
LOKI --> GRAF
PROM --> GRAF
App --- PG[(PostgreSQL)]
k6[k6 load] -->|HTTP| App
| Pilar | Como | Backend |
|---|---|---|
| Traces | auto-instrumentação (ASP.NET Core, Npgsql) + ActivitySource manual; span HTTP com o span de DB aninhado |
Tempo |
| Métricas (RED) | histograma http.server.request.duration, virando Rate / Errors / Duration por rota |
Prometheus |
| Logs | ILogger → OTLP, carregando trace_id e span_id |
Loki |
No Grafana os três se ligam: clicar no trace_id de um log de erro abre o trace; a partir de um span, dá para pular para os logs daquele request (ADR-002).
- Disponibilidade: 99,5% de requests não-5xx, ou seja, error budget de 0,5%.
- Latência: 99% das requests abaixo de 300ms (a app usa uma View com bucket em 300ms).
- Alerta de burn-rate multi-janela (modelo do Google SRE):
fast(1h e 5m acima de 14,4× o budget, dispara page) eslow(6h e 30m acima de 6×, abre ticket). O alerta protege o SLO, não o gráfico.
Quando o alerta dispara, existe um plano: docs/runbooks/AvailabilityBurn.md, com sintoma, queries PromQL/LogQL prontas, correlação trace↔log, causas prováveis, mitigação e escalonamento.
Tudo é versionado: os datasources e dashboards do Grafana são provisionados (ADR-005), e as configs do Collector, Prometheus, Tempo e Loki ficam em deploy/.
Pré-requisitos: Docker.
# sobe tudo: app + postgres + collector + tempo + loki + prometheus + grafana
docker compose up --build -d
# gera carga por ~3 min, populando os três pilares e os painéis de SLO
docker compose --profile load up k6
# (ou manualmente: curl -X POST localhost:8080/orders -H 'Content-Type: application/json' -d '{"sku":"Keyboard","quantity":1}')
# abra o Grafana em http://localhost:3000 → dashboards "RED — obs-api" e "SLO & Error Budget — obs-api"| Serviço | URL | O quê |
|---|---|---|
| API | http://localhost:8080 | /products, /orders, /health |
| Grafana | http://localhost:3000 | dashboards + Explore (Tempo/Loki/Prometheus) |
| Prometheus | http://localhost:9090 | métricas, rules, alertas |
| Tempo | http://localhost:3200 | API de traces |
| Loki | http://localhost:3100 | API de logs |
| Postgres | localhost:5433 | banco da app |
- RED — obs-api: taxa, fração de 5xx e p95/p99 por rota (o
POST /ordersinjeta cerca de 5% de erro e uma cauda de latência). - SLO & Error Budget: SLI de disponibilidade e latência contra o SLO, o burn rate com os limiares (1×/6×/14,4×) e o p95/p99 contra a linha de 300ms.
- Explore → Loki: logs com
trace_id; clicando nele, o trace abre no Tempo mostrando o span HTTP com a query do Postgres aninhada.
Para provocar sinais: POST /orders?fail=1 força um 500 e ?delayMs=600 força latência.
src/Api/ Minimal API .NET 10 instrumentada com o OTel SDK
deploy/
otel-collector/ collector-config.yaml (receivers, processors, exporters)
prometheus/ prometheus.yml + rules (recording e burn-rate)
tempo/ · loki/ configs dos backends
grafana/ provisioning (datasources + dashboards) + dashboards/*.json
load/ k6 (tráfego com fração de erro e latência)
docs/adr/ · docs/runbooks/
docker-compose.yml
- ADR-001 — Collector no meio
- ADR-002 — Stack Grafana LGTM
- ADR-003 — Instrumentação à mão (sem Aspire)
- ADR-004 — RED + SLO via error budget
- ADR-005 — Observabilidade como código
Em produção, .NET Aspire ou um Collector gerenciado seriam escolhas legítimas. Fazer manualmente aqui foi proposital, para entender o que essas ferramentas resolvem por baixo.