Teu progresso
0 / 83 módulos0%
Estágio 03 · 03-07
BloqueadoA maioria dos sistemas em produção não é "observable", é "monitored". Existe diferença real. Monitoring responde "X está vermelho?". Observability responde "por que X virou vermelho de jeitos que você não previu?". Logs verbose sem contexto, métricas sem cardinalidade decente, traces ausentes, alertas paginando madrugadas em problemas inúteis. Tudo isso é norma.
Este módulo é observability moderna: três pilares (logs, metrics, traces), OpenTelemetry como padrão, RED/USE methods, SLO/SLI/SLA, alerting baseado em error budget, profiling, e o stack realista (Prometheus/Grafana, Loki, Tempo, ou alternativas SaaS). Você sai sabendo instrumentar um sistema novo from scratch, definir SLOs, e debugar incidente sem refletor.
Cada um tem custo e finalidade. Bons sistemas usam todos, integrados.
Logs não-estruturados ("user 123 paid 50") são query-hostis. Logs estruturados (JSON com fields) são queryáveis:
{"ts":"2026-04-28T10:00:00Z","level":"info","msg":"order paid","orderId":"abc","userId":"123","amount":50,"requestId":"r1","tenantId":"t1"}
Práticas:
Tipos (Prometheus):
Cardinalidade explode quando você adiciona labels com muitos valores distintos (userId, tenantId com 100k tenants). Memory blowup em Prometheus. Limite labels a baixa cardinalidade (status code, route, region).
RED (Rate, Errors, Duration), pra serviços que respondem requests:
USE (Utilization, Saturation, Errors), pra recursos:
Pra cada serviço, dashboards RED. Pra cada recurso (CPU, disk, queue), USE.
Distributed tracing: request percorre 5 serviços; cada um cria spans com timing e attributes; tudo amarrado por trace ID.
OpenTelemetry (OTel) virou padrão de fato em 2024-2026:
traceparent).Padrão de adoção:
Tracing alta volume = caro. Sampling reduz:
Default: 100% em dev/staging, 1-10% em prod com tail sampling pra preservar errors e p99.
Burn rate alerting: alarm quando você está consumindo budget muito rápido pra restar 30 dias. Ex: budget de 30 dias inteiramente consumido em 1h = burn rate 30*24 = 720x.
Vantagem: alerta acionável. "Latência subiu" é vago; "burn rate alto, vai estourar SLO em 4h" é acionável.
Esse stack ("LGTM", Loki/Grafana/Tempo/Mimir) compete com SaaS modernamente.
Para projetos pequenos, free/cheap tier de Grafana Cloud, Honeycomb, Axiom resolve.
{job="api"} |~ "error" | json | tenantId="t1".Skill básica: 5-6 queries que você usa pra incidente. Se você não consegue rapidamente "quantos erros 5xx em /orders nas últimas 5 min, agrupado por tenant", instrumentação está fraca.
Princípios:
Sampling profiler runs em produção pra mostrar "onde CPU vai":
Use quando p99 alto sem causa óbvia em metrics/traces.
Front-end:
Bots simulando user em rotas críticas, periodicamente:
Detecta downtime quando ninguém está usando. Catch regressões rapidamente.
eBPF (extended Berkeley Packet Filter) deixou de ser nicho de kernel hackers e virou categoria de observability mainstream entre 2023-2025. Vale entender por que e quando usar.
O que é eBPF tecnicamente:
Por que substitui agentes tradicionais:
Tools em produção (2026):
| Tool | Foco | Onde brilha |
|---|---|---|
| Pixie (CNCF) | K8s "instant observability" | Auto-detect HTTP/gRPC/DB queries. Latency/error breakdown sem instrumentação. |
| Cilium Tetragon | Security + observability | Detect process exec, file access, network. Audit + run-time policy. |
| Parca | Continuous profiling | Pprof-format flamegraphs do cluster inteiro. Achar funções caras em prod. |
| Cilium | CNI + service mesh | Substitui kube-proxy + Istio sidecar. L7 policy via eBPF, sem sidecar. |
| Coroot | Full-stack observability | Mapa de serviços auto-gerado, RED/USE method baseados em eBPF. |
| Inspektor Gadget | K8s troubleshooting | Coleção de gadgets eBPF (trace exec, DNS, net policies). |
| bpftrace | Ad-hoc tracing | DTrace-like one-liner pra investigar prod sem instalar nada permanente. |
| Beyla (Grafana) | Auto-instrumentation HTTP/gRPC | Span generation OTel-compat sem SDK. |
Quando vale eBPF observability vs OTel manual:
| Caso | Tradicional (OTel SDK) | eBPF |
|---|---|---|
| App que você desenvolve, ownership total | Melhor, span semantics ricos | Complementar |
| Mix de apps de fornecedores (Postgres, Redis, custom) | Custoso instrumentar tudo | Vence, coverage automático |
| Profile de função em prod | Impossível sem code changes | Parca/eBPF cover |
| Network L4/L7 visibility (mTLS broken, who's calling who) | Service mesh ou tcpdump | Cilium / Pixie |
| Forensics de incident (que processo escreveu nesse arquivo?) | Logs, audit | Tetragon |
| Latency breakdown de Postgres query interno | Lib instrumentation se exists | bpftrace cover |
Pegadinhas:
Estratégia pragmática 2026:
Argumento de Charity Majors: agregação cedo (Prometheus) joga fora dimensions importantes. Eventos com alta cardinalidade preservados (1 evento por request, com 50 attributes) permitem perguntas que você ainda não sabia.
Honeycomb foi pioneira; Datadog, NewRelic seguiram. Trace-based observability é a evolução.
Observability pode dominar bill em projetos médios:
Pratique:
Aplicação com LLM core (chat, agent, RAG) tem observability shape diferente de microservice tradicional. Métricas, custos e modos de falha próprios. Em 2026, virou frente própria.
O que rastrear (mínimo viável):
Failure modes únicos:
OpenTelemetry GenAI semantic conventions (2024+): atributos padrão gen_ai.system, gen_ai.request.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, gen_ai.response.finish_reasons. Use isso desde dia 1; libs upstream estão adotando.
Tools especializadas (2026):
| Tool | Foco | Quando |
|---|---|---|
| LangSmith | Tracing + eval em LangChain/LangGraph apps | Default se stack já é LangChain |
| Langfuse (open-source) | Tracing, eval, prompt management | Self-host, multi-framework |
| Helicone | Proxy-based tracing (zero code) | Prototyping, multi-LLM apps |
| Phoenix (Arize, open-source) | LLM eval + RAG-specific debugging | Foco em RAG quality |
| Weights & Biases (W&B) / Weave | ML experiment tracking + LLM | Stack já em W&B |
Padrão de tracing recomendado:
// span pai por conversa, child por turn, grandchild por tool call
span("conversation", {conversation_id})
span("turn", {turn_index, model})
span("llm.completion", {prompt_tokens, completion_tokens, cost})
span("tool.call", {tool_name, args, result_summary})
span("rag.retrieve", {query, num_docs, similarity_top})
Eval automation (não opcional em 2026):
Cost trap a evitar: Tracing TODO call LLM com prompt + response inteiros = span 10-100x maior que normal. Sample agressivamente:
Cruza com 04-10 (LLM systems) e 04-09 (observability cost discipline ao escalar).
OpenTelemetry virou o padrão de instrumentação vendor-neutral em 2026. Stack canônico: SDK no app exporta OTLP pro Collector, Collector faz processing (sampling, masking, batch) e fan-out pra backends (Tempo/Mimir/Loki, Datadog, New Relic). Versões de referência: OTel JS SDK 1.x, OTLP/HTTP 1.0, semantic-conventions 1.27+.
1. SDK setup Node (production-ready). Auto-instrumentation patcha módulos em load time, então tracing.ts precisa ser carregado ANTES do app code via node --require ./tracing.js dist/index.js (ou --import em ESM).
// tracing.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
const sdk = new NodeSDK({
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'logistics-api',
[ATTR_SERVICE_VERSION]: process.env.GIT_SHA ?? 'dev',
'deployment.environment': process.env.NODE_ENV ?? 'dev',
'service.instance.id': process.env.HOSTNAME ?? require('os').hostname(),
}),
traceExporter: new OTLPTraceExporter({ url: 'http://otel-collector:4317' }),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({ url: 'http://otel-collector:4317' }),
exportIntervalMillis: 15000,
}),
logRecordProcessors: [],
instrumentations: [getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-fs': { enabled: false }, // ruidoso
})],
});
sdk.start();
process.on('SIGTERM', () => sdk.shutdown());
auto-instrumentations-node cobre HTTP, pg, redis, fetch, kafkajs, amqplib, ioredis, fastify. service.name ausente ou genérico (api, backend) inviabiliza queries em Tempo — sempre nome único.
2. Collector como pipeline central. Por que não exportar direto pro backend: Collector centraliza config (1 lugar pra mudar sampling/masking), faz retry com buffer, masking de PII antes de sair do perímetro, fan-out pra múltiplos backends, e tail-sampling (decisão depois do trace completar). Modos: Agent (DaemonSet/sidecar, low overhead, recebe do app local) → Gateway (deployment central, faz tail-sampling e masking pesado).
# collector-gateway.yaml
receivers:
otlp:
protocols:
grpc: { endpoint: 0.0.0.0:4317 }
http: { endpoint: 0.0.0.0:4318 }
processors:
memory_limiter: { check_interval: 1s, limit_percentage: 80, spike_limit_percentage: 25 }
batch: { timeout: 5s, send_batch_size: 1024 }
attributes/redact:
actions:
- { key: http.request.body, action: delete }
- { key: user.email, action: hash }
- { key: user.cpf, action: delete }
tail_sampling:
decision_wait: 30s
policies:
- { name: errors, type: status_code, status_code: { status_codes: [ERROR] } }
- { name: slow, type: latency, latency: { threshold_ms: 500 } }
- { name: probabilistic, type: probabilistic, probabilistic: { sampling_percentage: 5 } }
exporters:
otlphttp/tempo: { endpoint: http://tempo:4318 }
prometheusremotewrite: { endpoint: http://mimir:9009/api/v1/push }
otlphttp/loki: { endpoint: http://loki:3100/otlp }
service:
pipelines:
traces: { receivers: [otlp], processors: [memory_limiter, attributes/redact, tail_sampling, batch], exporters: [otlphttp/tempo] }
metrics: { receivers: [otlp], processors: [memory_limiter, batch], exporters: [prometheusremotewrite] }
logs: { receivers: [otlp], processors: [memory_limiter, attributes/redact, batch], exporters: [otlphttp/loki] }
memory_limiter SEMPRE primeiro — sem ele, traffic spike vira OOM e Collector cai. batch SEMPRE no fim — sem batch, RPC overhead destrói throughput.
3. Trace propagation cross-service. Padrão W3C traceparent: 00-<trace-id>-<span-id>-<flags> automático em HTTP/gRPC. Pegadinha em messaging: kafkajs/amqplib não propagam por default — @opentelemetry/instrumentation-kafkajs injeta no header da message no producer e extrai no consumer. Sem isso, trace fragmenta em produce vs consume e debug fica impossível.
4. Structured logging com pino + trace correlation. pino é o padrão prod 2026 (JSON nativo, latência sub-microssegundo, vence winston em throughput). Mixin injeta trace_id/span_id em todo log:
import pino from 'pino';
import { trace } from '@opentelemetry/api';
export const log = pino({
level: process.env.LOG_LEVEL ?? 'info',
formatters: { level: (l) => ({ level: l }) },
mixin: () => {
const ctx = trace.getActiveSpan()?.spanContext();
return ctx ? { trace_id: ctx.traceId, span_id: ctx.spanId } : {};
},
});
Em Grafana, clicar no log line abre o trace em Tempo via trace_id; clicar no span abre logs do span via Loki query {service_name="logistics-api"} | json | trace_id="...". NUNCA pino-pretty em prod — quebra parser downstream.
5. Sampling strategies. Head-based (parentbased(traceidratio(0.1)) no SDK) decide no início, simples e cheap, mas não pode focar em traces interessantes (ainda não sabe se haverá erro). Tail-based (no Gateway Collector) decide ao fim, captura 100% errors + 100% slow + 5% normais — signal/noise ordens de magnitude melhor, custa buffer de 30s no Collector. Pattern recomendado: head 100% + tail no Gateway. Numbers reais 2026: 10k req/s sem sampling ≈ 30M spans/dia ≈ $3-5k/mês em SaaS observability; com tail 5% + 100% errors ≈ $300/mês mantendo signal.
6. Cardinality control em metrics. HTTP auto-instrumentation cria http.server.duration com label http.route (path template /orders/:id, baixa cardinalidade) — NÃO http.target (path com IDs /orders/abc-123, cardinality explosion). Usar http.target em métrica vira $1k/mês em $20k. Budget: < 100k series ativos por serviço. Drop attributes problemáticos via OTel Views ou processor attributes/drop no Collector.
7. Stack completo na Logística. Apps Node Fastify rodam com --require ./tracing.js, exportam OTLP pro DaemonSet Collector (Agent mode, um por node, low overhead). DaemonSet forward pro Gateway Collector central (Deployment, 3 réplicas) que faz tail-sampling, masking de CPF/email e fan-out: traces → Tempo (S3 backend), metrics → Mimir (S3 backend), logs → Loki (S3 backend). Grafana é single pane of glass com Explore linkando logs↔traces↔metrics via service.name + trace_id. Custo target: < 2% do compute spend.
Anti-patterns observados:
--require/--import.service.name ausente ou genérico (api, backend) — impossível distinguir serviços em Tempo.memory_limiter — OOM em traffic spike, Collector cai e perde dados.http.target em vez de http.route — cardinality explosion ($1k → $20k).trace_id injetado — correlação manual via timestamps frágil.pino-pretty em prod — JSON vira texto, parser downstream quebra.batch processor — RPC overhead destrói throughput.Cruza com 02-07 (Node, ordem de --require importa pra patches), 02-08 (Fastify auto-instrumentation), 03-05 (AWS X-Ray vs OTel + Tempo), 03-15 (incident response, traces correlacionados aceleram MTTR), 04-09 (scaling, OTel ingestion cost @ scale).
OpenTelemetry virou de fato o padrão de instrumentação multi-vendor. Spec 1.40 (Q1 2026) consolida o que ficou anos em "experimental": HTTP semantic conventions estáveis (http.request.method, http.response.status_code, url.full), messaging semconv estável (Kafka/RabbitMQ/SQS attributes padronizados), e Collector contrib v0.110+ com processors maduros pra tail sampling, k8s enrichment, transform. Backends (Tempo, Jaeger 2.x, Datadog, Honeycomb, New Relic) consomem OTLP nativo. Vendor lock-in de instrumentação morreu — a SDK fica, o backend troca.
Esta seção é production deep: como montar Collector em pipeline robusto (receivers → processors → exporters), tail sampling sério (não "amostra 1%", mas "guarda 100% dos erros + P99 lentos + 1% do resto"), exemplars ligando metric spike a trace exato, continuous profiling 2026 (Pyroscope/Parca/Grafana Profiles) correlacionado a span_id via eBPF stack collection.
Antes da 1.40 cada vendor mapeava do jeito dele. Agora HTTP server/client têm attributes congelados:
http.request.method = "GET" | "POST" | ...
http.response.status_code = 200 | 500 | ...
url.full = "https://api.x.com/v2/orders?status=paid"
url.path = "/v2/orders"
url.scheme = "https"
server.address = "api.x.com"
server.port = 443
network.protocol.version = "1.1" | "2" | "3"
http.route = "/v2/orders/:id" # template, não path raw (cardinality)
http.route é o killer pra metrics — usar url.path raw em label de Prometheus = explosão de cardinalidade (/users/1, /users/2, ... = N séries). Sempre coletar http.route (template) pra metrics, url.full só em traces (alta card aceitável lá).
Messaging semconv estável: messaging.system (kafka, rabbitmq, aws_sqs), messaging.destination.name (topic/queue), messaging.operation.type (publish, receive, process), messaging.kafka.consumer.group. Span links ligam o producer span ao consumer span através de OTel context propagation no header traceparent.
Collector é proxy + ETL pra telemetria. Roda como sidecar, agent (DaemonSet) ou gateway (Deployment com HA). Pipeline = receivers → processors → exporters, declarado em YAML. Config production-ready pra fleet Node em K8s:
# otel-collector-contrib v0.110+ (2026)
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
max_recv_msg_size_mib: 16
http:
endpoint: 0.0.0.0:4318
processors:
# 1. Detecta resource (cloud, host, k8s)
resourcedetection:
detectors: [env, system, ec2, eks]
timeout: 2s
override: false
# 2. Enrich com k8s metadata (pod, namespace, deployment)
k8sattributes:
auth_type: serviceAccount
passthrough: false
extract:
metadata:
- k8s.namespace.name
- k8s.pod.name
- k8s.deployment.name
- k8s.node.name
labels:
- tag_name: app.version
key: version
from: pod
# 3. Filtra noise (health checks)
filter/healthchecks:
error_mode: ignore
traces:
span:
- 'attributes["http.route"] == "/healthz"'
- 'attributes["http.route"] == "/readyz"'
# 4. Tail sampling — decisão APÓS trace completo
tail_sampling:
decision_wait: 30s # buffer 30s pra trace montar
num_traces: 100000 # max in-memory traces (OOM guard)
expected_new_traces_per_sec: 5000
policies:
- name: errors-always
type: status_code
status_code: { status_codes: [ERROR] }
- name: slow-traces
type: latency
latency: { threshold_ms: 1000 }
- name: critical-tenants
type: string_attribute
string_attribute:
key: tenant.tier
values: [enterprise, premium]
- name: probabilistic-rest
type: probabilistic
probabilistic: { sampling_percentage: 1.0 }
# 5. Span metrics — gera RED metrics (rate/errors/duration) DOS traces
spanmetrics:
metrics_exporter: prometheusremotewrite
dimensions:
- name: http.route
- name: http.response.status_code
- name: service.name
histogram:
explicit:
buckets: [10ms, 50ms, 100ms, 250ms, 500ms, 1s, 2s, 5s]
# 6. Batch (sempre por último antes do export)
batch:
timeout: 5s
send_batch_size: 10000
send_batch_max_size: 11000
exporters:
otlphttp/tempo:
endpoint: https://tempo.obs.svc:4318
compression: gzip
sending_queue:
enabled: true
num_consumers: 10
queue_size: 5000
retry_on_failure:
enabled: true
initial_interval: 5s
max_interval: 30s
max_elapsed_time: 5m
prometheusremotewrite:
endpoint: https://prometheus.obs.svc/api/v1/write
resource_to_telemetry_conversion: { enabled: true }
service:
extensions: [health_check, pprof]
pipelines:
traces:
receivers: [otlp]
processors: [resourcedetection, k8sattributes, filter/healthchecks, tail_sampling, spanmetrics, batch]
exporters: [otlphttp/tempo]
metrics:
receivers: [otlp]
processors: [resourcedetection, k8sattributes, batch]
exporters: [prometheusremotewrite]
telemetry:
metrics:
level: detailed
address: 0.0.0.0:8888
Ordem dos processors importa. k8sattributes antes do tail_sampling (precisa do attribute pra policy decidir). batch SEMPRE por último (agrupa pré-export, reduz round-trips). spanmetrics ANTES de batch (gera metrics derivadas dos spans pré-export).
Head sampling (decisão no client antes do span existir) é cego — perde 99% das traces antes de saber se deu erro. Tail sampling (decisão no Collector após trace completo) é caro mas inteligente. Trade-off:
Policies compõem (OR lógico): se QUALQUER policy diz "keep", o trace fica. Padrão saudável: errors-always + latency > P99 + 1% probabilistic baseline. Em multi-tenant, adicionar policy por tier (enterprise = 100%, free = 0.1%).
Custo: tail sampling Collector dimensionado pra ~50k spans/s por instance com 8GB RAM. Acima disso, sharding por trace_id (load balancer com routing_key=traceID no loadbalancing exporter, garante todos spans de um trace caem na mesma instance).
Exemplar é attribute opcional num metric point apontando trace_id+span_id de UMA execução exemplar daquele bucket. Histogram http.server.duration no bucket [1s, 2.5s] carrega exemplar trace_id=abc123 — clica no spike P99 do Grafana, abre o trace exato no Tempo. Um clique entre "métrica está ruim" e "este request específico foi ruim por isso".
Emit em Node com @opentelemetry/sdk-metrics:
import { metrics, trace } from '@opentelemetry/api'
const histogram = metrics.getMeter('http').createHistogram('http.server.duration', {
unit: 'ms',
advice: { explicitBucketBoundaries: [10, 50, 100, 250, 500, 1000, 2500, 5000] },
})
// No middleware HTTP:
const start = performance.now()
res.on('finish', () => {
const ctx = trace.getActiveSpan()?.spanContext()
histogram.record(performance.now() - start, {
'http.route': req.route?.path ?? 'unknown',
'http.response.status_code': res.statusCode,
// exemplar é injetado AUTOMATICAMENTE se há span ativo no contexto
})
})
Backend storage: Prometheus 3.x suporta exemplars nativamente (--enable-feature=exemplar-storage, ring buffer separado, default 100k exemplars). Grafana renderiza diamond marks no histogram panel ligando ao Tempo data source.
Cuidado: exemplars em metrics de altíssima cardinalidade (label user_id) explodem custo. Manter em metrics agregadas (route + status), não em metrics dimensionadas por entidade.
Profiling era ferramenta de "pegar momentaneamente quando há problema". Continuous profiling = sempre on, sample rate baixo (~100Hz), eBPF coleta stack traces sem instrumentação no app. Ferramentas mainstream:
pyroscope ebpf) ou SDK push. Storage flame graphs queryáveis por label.Trace-to-profile correlation 2026: span injeta pyroscope.profile_id baggage; Pyroscope agent captura stack traces durante execução do span e linka via span_id. Grafana Tempo panel mostra "View profile for this span" — flame graph do tempo CPU gasto entre span start e end.
Setup Node SDK push:
import Pyroscope from '@pyroscope/nodejs'
Pyroscope.init({
serverAddress: 'http://pyroscope.obs.svc:4040',
appName: 'orders-api',
tags: {
env: process.env.NODE_ENV,
version: process.env.APP_VERSION,
},
sampleRate: 100, // Hz; padrão = 100, overhead ~1-2% CPU
wall: { collectCpuTime: true },
})
Pyroscope.start()
Overhead real: SDK profiling ~1-2% CPU @ 100Hz. eBPF profiling (kernel-level, perf_event) ~0.5% sem precisar tocar no app — vantagem clara em fleet poliglota. Custo storage: ~1KB/s por process em flame graphs comprimidos delta-encoded.
resourcedetectionprocessor enriquece spans com cloud/host/k8s metadata sem o app saber. Em EKS: detecta cloud.provider=aws, cloud.region=us-east-1, host.id, k8s.cluster.name. Combinado com k8sattributesprocessor (precisa de RBAC pra ler API server), adiciona k8s.pod.name, k8s.deployment.name, k8s.namespace.name. Resultado: query Tempo "show traces from deployment=orders-api in namespace=prod" funciona sem o app ter logado nada disso.
Default historicamente foi gRPC (4317). 2026 trend é OTLP/HTTP (4318) por: load balancers L7 entendem (gRPC precisa L7 com HTTP/2 awareness), proxies/CDNs corporate friendly, debug com curl. Performance: gRPC ~15-20% menor overhead em fleet grande (binary framing, multiplexing). HTTP é fine pra <10k spans/s por client. Acima disso, gRPC.
Fleet de ~40 services Node em EKS. Antes: Datadog APM SDK em todos, vendor lock, custo $18k/mês. Migração 2026: OTel SDK em todos, Collector DaemonSet (agent) + Deployment (gateway com tail sampling), Tempo pra traces, Mimir pra metrics, Loki pra logs, Pyroscope pra profiling. Tail sampling: 100% errors + P99>1s + 2% baseline. Exemplars on em todos histograms HTTP/DB. Span-to-profile via Pyroscope eBPF DaemonSet. Custo final: ~$3k/mês (S3 backend Tempo + EBS Mimir). MTTR p50 caiu 60% — clique no spike → trace → profile, sem kubectl exec em produção.
tail_sampling sem num_traces cap — OOM em traffic spike. Sem cap, buffer cresce sem limite, pod morre, telemetria zerada.user_id label) — cost explosion no Prometheus exemplar storage. Manter em metrics agregadas.request.method colide com http.request.method upstream. Sempre app.<dominio>.<attr> (ex: app.orders.tenant_tier).batch processor primeiro no pipeline — agrupa antes de filtrar/sampling = trabalho desperdiçado. Batch SEMPRE último.k8sattributes sem RBAC adequado — Collector sem permission lê API server retorna spans sem enrichment, debug vira pesadelo. Aplicar ClusterRole pods, namespaces get/list/watch.decision_wait muito curto (5s) em fleet com long-running traces (background jobs) — decisão tomada antes do trace completar, perde spans tardios. Calibrar com P99 trace duration real.spanmetrics sem dimension cap — incluir url.full como dimension explode séries Prometheus. Whitelist dimensions: service.name, http.route, http.response.status_code. Nunca path raw.Cruza com §2.5 (traces foundation, span/context propagation), §2.13 (profiling intro, motivação), §2.16 (eBPF, base do continuous profiling kernel-side), §2.18 (cost — tail sampling é alavanca #1 de redução), §2.19 (LLM observability — OTel GenAI semconv 2026 estável), 02-07 (Node --require order pra @opentelemetry/auto-instrumentations-node), 03-15 (MTTR via traces correlacionados a profiles), 04-09 (scaling — Collector horizontal sharding por trace_id), 02-17 §2.20 (mobile observability 2026 — Sentry SDK 8 + MetricKit + Macrobenchmark + Android Vitals), 03-09 §2.21 (RUM + LoAF integration pra INP attribution), 01-02 §2.11 (Linux observability stack — eBPF + journald + PSI).
Você precisa, sem consultar:
Instrumentar Logística v1 com observability profissional. Stack open-source rodando em Docker Compose ou K8s.
orders_created_total{tenant,status}, courier_assignment_latency_seconds, ws_connections_active.traceId injetado a partir do OTel context.GET /orders < 300ms.POST /orders succeed (não 5xx).console.log de info em prod. Logs estruturados sempre.Threshold de Maestria
Acerte todas as 5 pra marcar o módulo como concluído. Sem pressa, sem timer. Tudo fica salvo no teu navegador.
Q1Qual a diferença essencial entre 'monitoring' e 'observability'?
Q2Por que adicionar labels como `userId` ou `tenantId` (com 100k tenants) em métricas Prometheus é antipadrão?
Q3Qual a vantagem de tail-based sampling em traces sobre head-based?
Q4Em SLO/burn rate alerting, por que 'burn rate alto' é mais acionável que 'latência subiu'?
Q5Por que mockar instrumentation manual com OTel SDK exige que `tracing.ts` carregue ANTES do app code via `--require`?
Destrava
03-07 é prereq dos seguintes módulos: