Pular para o conteúdo
~/.primo-academy.sh
☰ Aulas · Observabilidade Frontend: Sentry, RUM, OpenTelemetry, feature flags e PII · 0/7
Recomendado: essencial

OpenTelemetry Web: traces distribuidos no browser, OTLP, contexto

7 min de leitura

fonte

Voce tem error tracking (Sentry) e RUM (Web Vitals). Mas e' a pergunta mais importante em sistemas distribuidos: "por que essa request inteira (frontend → backend → DB → payment gateway) demorou 8s?". Error tracking da o erro local, RUM da o Web Vital. So' traces distribuidos mostram a jornada completa. Esse no cobre OpenTelemetry (OTel) Web: o padrao CNCF vendor-neutral pra emitir traces, com W3C Trace Context propagado via headers HTTP, e exportacao pra qualquer backend (Jaeger, Tempo, Honeycomb, Sentry, Datadog).

OpenTelemetry e' o spec; as implementacoes sao libs pra cada linguagem (no JS, e' o pacote @opentelemetry/sdk-web). Em 2026, OTel e' o padrao de fato - Sentry, Datadog, Honeycomb todos aceitam OTel como input. Sem vendor lock-in.

Voce sai de "acho que o backend demorou" pra "request 1234: 50ms frontend, 200ms backend, 100ms DB, 1.5s payment gateway - ah, e' o payment".

O essencial 🟢

O que e' OpenTelemetry (OTel). Um projeto CNCF (mesma fundacao do Kubernetes) que define spec, API, e SDK pra emitir logs, metrics, e traces de forma vendor-neutral. Antes do OTel, cada vendor tinha proprio formato (Datadog, New Relic, Jaeger, Zipkin), e voce ficava preso ao vendor. Com OTel, 1 SDK no client, N backends possiveis.

Conceitos fundamentais.

  • Trace: jornada completa de uma request. Ex: "user clica em checkout" → 1 trace.
  • Span: unidade atomica de trabalho dentro de um trace. Ex: "fetch /api/checkout" = 1 span de 200ms.
  • Span context: ID unico (traceId, spanId), atributos, parent-child relationship.
  • Propagation: passar trace context entre servicos (frontend → backend → DB) via W3C Trace Context (headers HTTP).

Estrutura de um trace.

Trace (traceId: abc-123)
  Span: page.click_checkout (50ms) [frontend]
    Span: fetch /api/checkout (200ms) [frontend]
      Span: POST /api/checkout (180ms) [backend]
        Span: db.query (100ms) [backend]
        Span: payment_gateway (50ms) [backend]

Cada span tem: nome, duracao, atributos, status (OK/ERROR), e parent_span_id (criando arvore).

Setup basico de OTel no browser.

pnpm add @opentelemetry/sdk-web
pnpm add @opentelemetry/api
pnpm add @opentelemetry/exporter-trace-otlp-http
// instrumentation.ts
import { WebTracerProvider } from "@opentelemetry/sdk-web";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { Resource } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-web";
import { trace, context } from "@opentelemetry/api";

const provider = new WebTracerProvider({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: "my-app-web",
    [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: "production",
  }),
});

const exporter = new OTLPTraceExporter({
  url: "https://api.honeycomb.io/v1/traces",
  headers: { "x-honeycomb-team": "your-api-key" },
});

provider.addSpanProcessor(new BatchSpanProcessor(exporter));
provider.register();

// Agora pode usar em qualquer lugar:
const tracer = trace.getTracer("my-app");
const span = tracer.startSpan("user.click_checkout");
span.setAttribute("user.id", "u-456");
span.end();

Auto-instrumentation. OTel detecta automaticamente fetch, XHR, e outras operacoes. Sem instrumentar manualmente cada chamada:

import { getWebAutoInstrumentations } from "@opentelemetry/auto-instrumentations-web";

provider.addSpanProcessor(new BatchSpanProcessor(exporter));
// Auto-instrumentation:
const instrumentations = [
  new DocumentLoadInstrumentation(),
  new UserInteractionInstrumentation(),
  new FetchInstrumentation(),
  // ...
];

Auto-instrumentation captura: clicks, navigations, fetch, XHR, sem codigo no app.

W3C Trace Context - propagacao via headers HTTP. O padrao W3C define como passar trace context entre servicos:

traceparent: 00-{traceId}-{parentSpanId}-{flags}
tracestate: vendor-specific-data

traceparent e' gerado pelo frontend ao fazer fetch. Backend le o header, continua o trace com mesmo traceId, gera novo spanId (span filho), e propaga pros proximos servicos. Sem isso, traces ficam quebrados entre client e server.

Exemplo de fetch instrumentado.

// Auto-instrumentation adiciona traceparent automaticamente
fetch("/api/checkout", { method: "POST" });

// Manual span (se quiser adicionar contexto)
import { trace } from "@opentelemetry/api";

const tracer = trace.getTracer("my-app");
const span = tracer.startSpan("checkout.process");
span.setAttribute("cart.size", 5);
span.setAttribute("cart.value", 1234.56);

try {
  const response = await fetch("/api/checkout", { method: "POST" });
  span.setAttribute("http.status_code", response.status);
  if (!response.ok) {
    span.setStatus({ code: 2, message: "HTTP error" });  // ERROR
  }
} catch (err) {
  span.recordException(err);
  span.setStatus({ code: 2, message: err.message });
} finally {
  span.end();
}

OTLP - OpenTelemetry Protocol. O protocolo de exportacao. OTLP sobre HTTP ou gRPC, padrao que todo backend OTel aceita.

import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";

const exporter = new OTLPTraceExporter({
  url: "https://api.honeycomb.io/v1/traces",
  headers: { "x-honeycomb-team": "your-api-key" },
});

Vendor-specific extensions. Cada backend tem extensoes proprias (como Datadog adiciona metric), mas core OTLP funciona em todos.

Span attributes - "contexto rico". Attributes sao key-value pairs que dizem o que o span representa:

span.setAttribute("user.id", "u-456");
span.setAttribute("user.country", "BR");
span.setAttribute("cart.size", 5);
span.setAttribute("cart.value", 1234.56);
span.setAttribute("payment.method", "credit_card");
span.setAttribute("payment.gateway", "stripe");

Boa pratica: attributes sao buscados no backend. Use nomes consistentes (lowercase, dot.separated).

Semantic conventions. OTel define nomes padrao pra atributos comuns (http.method, db.system, messaging.system, etc). Use-os - backend ja' sabe como indexar.

span.setAttribute("http.method", "POST");
span.setAttribute("http.url", "/api/checkout");
span.setAttribute("http.status_code", 200);
span.setAttribute("db.system", "postgresql");
span.setAttribute("db.statement", "SELECT * FROM users WHERE id = $1");

Context propagation - o conceito mais importante. OTel usa Context API pra propagar trace info sem voce gerenciar:

import { trace, context } from "@opentelemetry/api";

const span = tracer.startSpan("outer");

// Dentro do span, fetch e' automaticamente filho
context.with(trace.setSpan(context.active(), span), () => {
  fetch("/api/checkout");  // child span do "outer"
});

OTel + Sentry. Sentry aceita OTel como input:

import { trace } from "@opentelemetry/api";
import * as Sentry from "@sentry/react";

Sentry.init({
  tracesSampleRate: 0.1,
  integrations: [Sentry.browserTracingIntegration()],
});

// Sentry ve spans de OTel SDK
const span = trace.getTracer("my-app").startSpan("checkout");
span.end();

Use OTel SDK no client, envie pra Sentry via OTLP. **Vendor-neutral

  • RUM benefits**.

OTel backend options.

BackendOpen sourceSelf-hostFree tierNotes
Jaegersimsimyestraces, CNCF, Grafana UI
Temposimsimyestraces, Grafana native, mais novo que Jaeger
Honeycombnaonaosim (limitado)traces, query poderosa, $$
Sentrysimsimsim (5K/mes)errors + traces + RUM
Datadognaonaonao (free trial)tudo (logs/metrics/traces/RUM)
Grafana Cloudnaonaosim (10K series)Grafana stack as service

Em 2026, Grafana Tempo e' a opcao open source mais usada pra traces (substituindo Jaeger).

Aprofundamento 🟡

Sampling strategies. Enviar 100% dos traces e' caro. OTel suporta sampling via Sampler:

import { ParentBasedSampler, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-web";

const provider = new WebTracerProvider({
  sampler: new ParentBasedSampler({
    root: new TraceIdRatioBasedSampler(0.1),  // 10% dos traces
  }),
});
  • ParentBased: se trace ja' tem sampling decision (do backend), respeita.
  • TraceIdRatioBased: 10% dos traces aleatoriamente.
  • AlwaysOn: 100% (debug).
  • AlwaysOff: 0% (testes).

Head vs tail sampling. OTel SDK faz head sampling (decide no inicio). Tail sampling (decide no fim, baseado em erro/latencia) requer collector (proxy).

Span limits. OTel tem limits pra evitar OOM:

new WebTracerProvider({
  spanLimits: {
    maxAttributeValueLength: 4096,
    maxAttributeCount: 128,
    maxEventCount: 256,
  },
});

Context API - bagagem do trace. Context carrega trace state atraves de callbacks, promises, e async/await:

import { context, trace } from "@opentelemetry/api";

const outerSpan = tracer.startSpan("outer");

// Tudo dentro deste callback e' child de outerSpan
context.with(trace.setSpan(context.active(), outerSpan), () => {
  setTimeout(() => {
    // Ainda e' child de outerSpan
    const innerSpan = tracer.startSpan("inner");
    innerSpan.end();
  }, 1000);
});

Auto-instrumentation details. OTel auto-detecta:

  • DocumentLoad - navigation, resource loading.
  • UserInteraction - clicks, key presses.
  • Fetch / XHR - HTTP requests.
  • WebSocket - send/receive.
  • Long task - tasks > 50ms.
  • Console - log, warn, error.

Custom instrumentation. Pra operacoes que auto-instrumentation nao cobre:

const span = tracer.startSpan("custom.operation");
span.setAttribute("custom.key", "value");

try {
  await customOp();
  span.setStatus({ code: 1 });  // OK
} catch (err) {
  span.recordException(err);
  span.setStatus({ code: 2, message: "Error" });
} finally {
  span.end();
}

Span events - "log dentro do span". Use events pra adicionar contexto a um span sem criar sub-span:

const span = tracer.startSpan("checkout.process");
span.addEvent("payment.started", { method: "credit_card" });
span.addEvent("payment.completed", { duration_ms: 200 });
span.end();

Events aparecem no timeline do trace, nao como spans separados.

Span links - "causal relationship sem hierarchy". Use quando um span e' causado por outro sem ser filho:

const parent = tracer.startSpan("user.request");
const link = { context: parent.spanContext() };

const child = tracer.startSpan("background.process", { links: [link] });
// child e' child mas tambem linkado a parent

Pra quem quer ir mais alem 🔴

OTel Collector. Em prod enterprise, OTel Collector e' um proxy que recebe traces/metrics/logs, processa, e envia pra multiplos backends. Self-hosted (Go) ou managed (Grafana Cloud, Honeycomb).

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
      http:

processors:
  batch:
    timeout: 5s
  memory_limiter:
    limit_mib: 512

exporters:
  otlp:
    endpoint: api.honeycomb.io:443
  debug:
    verbosity: detailed

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp, debug]

Span metrics - "metrics de trace data". OTel pode gerar metrics a partir de traces (latencia por rota, error rate, etc). Combina traces + metrics num so' sistema.

Tail-based sampling no Collector. Collector pode decidir no fim se manda trace (baseado em erro, latencia, custom rules). So' envia traces interessantes - economiza bandwidth e storage.

OTel + eBPF (2024+). Novas versoes de OTel Collector usam eBPF pra auto-instrumentar kernel-level - capture traces sem codigo em qualquer linguagem. Futuro da observability.

OpenTelemetry Logs (ainda em desenvolvimento). OTel tem spec pra logs mas a implementacao JS ainda esta' em alpha. Em 2026, use Sentry ou Pino/Winston pra logs, OTel pra traces e metrics.

Custom propagators. Em casos especiais (legacy systems, vendor proprietary), OTel suporta custom propagators que interpretam outros formatos:

import { W3CTraceContextPropagator } from "@opentelemetry/core";
import { B3Propagator } from "@opentelemetry/propagator-b3";

propagation.setGlobalPropagator(new W3CTraceContextPropagator());
// ou B3 (Zipkin format):
propagation.setGlobalPropagator(new B3Propagator());

Leitura recomendada:

Dica: o erro mais comum em OTel Web e' esquecer de configurar o propagator. Sem W3C Trace Context, traces ficam quebrados entre client e server - cada um gera seu proprio traceId, sem continuity. Solucao: garantir que propagation.setGlobalPropagator(new W3CTraceContextPropagator()) e' chamado antes de qualquer fetch. Default no OTel SDK e' W3C, mas verifique se nenhum codigo custom substituiu.

No proximo no, vamos feature flags: como fazer rollout progressivo de uma feature (10% → 50% → 100%), kill switch rapido, e A/B testing com LaunchDarkly, Flagsmith, ou Cloudflare Flagship.

// Quiz

Por que OpenTelemetry (OTel) e' considerado o padrao vendor-neutral de observabilidade e quando usar OTel vs Sentry direto?

Escolha uma alternativa

// recursos

// avaliação da trilha

—
ainda sem avaliações