Skip to content

feat: opentelemetry observability support#347

Merged
lazarv merged 7 commits intomainfrom
feat/otel
Mar 8, 2026
Merged

feat: opentelemetry observability support#347
lazarv merged 7 commits intomainfrom
feat/otel

Conversation

@lazarv
Copy link
Owner

@lazarv lazarv commented Mar 8, 2026

Summary

Adds built-in OpenTelemetry integration to @lazarv/react-server, providing distributed tracing and metrics for both development and production environments. When enabled, the runtime automatically instruments HTTP requests, SSR rendering, server functions, middleware, and cache operations — with zero runtime overhead when disabled.

Motivation

Production React Server applications need observability to debug performance issues, trace requests across services, and monitor runtime behavior. Rather than requiring users to manually instrument their code, this PR bakes OpenTelemetry support directly into the framework — covering the full request lifecycle out of the box.

What's Included

Core Telemetry Engine (server/telemetry.mjs — 609 lines)

  • SDK initialization with two codepaths:
    • OTLP: Full NodeSDK with batch span processing, OTLP/HTTP trace & metric exporters, W3C trace context propagation
    • Console / Dev-Console: Lightweight BasicTracerProvider with SimpleSpanProcessor for immediate span output (no collector needed)
  • No-op fallbacks: When telemetry is disabled, all APIs (getTracer(), getMeter(), withSpan(), etc.) resolve to no-op objects — zero allocations, zero overhead
  • Lazy loading: All @opentelemetry/* packages are imported dynamically on first use
  • Graceful shutdown: shutdownTelemetry() flushes pending spans/metrics on server close
  • W3C Trace Context: Automatic extraction from incoming request headers and injection into response headers

Built-in Instrumentation

Trace Spans:

Span Description
HTTP Request Root span per incoming request with method, URL, status, client IP
Middleware: {name} Per-middleware span in the compose chain
Render (RSC / SSR) Nested render spans for the RSC→SSR pipeline
Server Function Server function invocation with function ID and error tracking
Cache LookupCache Hit / Cache Miss → Recompute Cache operation spans with provider, TTL, hit/miss status
Server Startup Server initialization span (dev + production)
Vite Dev Server Init Vite server creation span (dev only)
Vite plugin [{name}].{hook} Every Vite plugin hook instrumented (dev only)

Metrics (7 instruments):

Metric Type
http.server.request.duration Histogram
http.server.active_requests UpDownCounter
react_server.server_function.duration Histogram
react_server.rsc.render.duration Histogram
react_server.dom.render.duration Histogram
react_server.cache.hits Counter
react_server.cache.misses Counter

Dev Console Exporter (server/dev-trace-exporter.mjs — 419 lines)

A pretty-printed terminal exporter that renders compact trace trees with:

  • Color-coded durations (green < 20ms, yellow 20–100ms, red > 100ms)
  • Proportional timing bars
  • Grouped Vite plugin spans with counts
  • Collapsed micro-spans (< 1ms summarized in one line)

Configuration & Validation

Config schema (config/schema.mjs, config/schema.json, config/schema.d.ts):

export default {
  telemetry: {
    enabled: true,                    // default: false
    serviceName: "my-app",            // default: package name
    endpoint: "http://localhost:4318", // default: localhost:4318
    exporter: "otlp",                 // "otlp" | "console" | "dev-console" (auto-detected)
    sampleRate: 1.0,                  // 0.0–1.0
    metrics: {
      enabled: true,                  // default: true when telemetry enabled
      interval: 30000,                // export interval in ms (>= 1000)
    },
  },
};

Environment variables:

Variable Effect
REACT_SERVER_TELEMETRY=true Enables telemetry
OTEL_EXPORTER_OTLP_ENDPOINT Enables telemetry + sets endpoint
OTEL_SERVICE_NAME Overrides service name

Validation (config/validate.mjs) — runtime validation with descriptive error messages for all telemetry config fields.

Public API (@lazarv/react-server/telemetry)

New package export with full TypeScript types (telemetry/index.d.ts — 156 lines):

  • withSpan(name, attributes?, fn) — execute code within a child span
  • getSpan() — get the current request span
  • getTracer() — get the OpenTelemetry tracer
  • getMeter() — get the OpenTelemetry meter
  • getMetrics() — get built-in metric instruments
  • getOtelContext() — get the OTel context for the current request
  • injectTraceContext(headers) — inject W3C trace context into outgoing headers

Build-time Tree Shaking

When telemetry is disabled at build time, all @opentelemetry/* imports are force-resolved to empty modules via a new optional-deps Vite plugin, ensuring zero bundle impact.

Edge Runtime Support

Lightweight telemetry for edge runtimes (Cloudflare Workers, Netlify Edge Functions) using BasicTracerProvider + SimpleSpanProcessor. Traces only — metrics are not supported due to platform constraints.

Documentation

Comprehensive documentation page (docs/src/pages/en/(pages)/features/observability.mdx — 417 lines) covering:

  • Getting started & installation
  • Full configuration reference
  • All built-in spans and metrics with attribute tables
  • Telemetry API usage examples
  • Trace propagation
  • Dev console exporter
  • Production setup guides (Jaeger, Grafana/Tempo, Honeycomb/Datadog/New Relic)
  • Edge runtime notes
  • Zero-overhead explanation

Tests

  • config-schema.spec.mjs — 62 lines testing schema generation
  • config-validate.spec.mjs — 178 lines testing telemetry config validation (valid configs, invalid types, boundary values)

Key Design Decisions

  1. All OTel packages are optional peer dependencies — users only install what they need; the framework never forces OTel into the dependency tree
  2. No-op by default — telemetry is opt-in; disabled apps have zero overhead
  3. NodeSDK serviceName option over custom Resource — avoids cross-version @opentelemetry/resources class incompatibilities under pnpm's strict hoisting (the resource.getRawAttributes is not a function fix)
  4. Dev-console as default exporter in development — no collector needed for local debugging; OTLP used in production or when OTEL_EXPORTER_OTLP_ENDPOINT is set

@cloudflare-workers-and-pages
Copy link

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
react-server-docs 111cfe8 Mar 08 2026, 04:35 PM

@lazarv lazarv merged commit c663b73 into main Mar 8, 2026
57 checks passed
@lazarv lazarv deleted the feat/otel branch March 8, 2026 16:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant