Skip to content

Commit c663b73

Browse files
authored
feat: opentelemetry observability support (#347)
## Summary Adds built-in [OpenTelemetry](https://opentelemetry.io/) 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 Lookup` → `Cache 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`): ```js 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
1 parent baf0ab7 commit c663b73

40 files changed

Lines changed: 4085 additions & 149 deletions

docs/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@
1212
"dependencies": {
1313
"@docsearch/react": "3",
1414
"@lazarv/react-server": "workspace:^",
15+
"@opentelemetry/api": "^1.9.0",
16+
"@opentelemetry/core": "^2.0.0",
17+
"@opentelemetry/exporter-metrics-otlp-http": "^0.200.0",
18+
"@opentelemetry/exporter-trace-otlp-http": "^0.200.0",
19+
"@opentelemetry/resources": "^2.0.0",
20+
"@opentelemetry/sdk-metrics": "^2.0.0",
21+
"@opentelemetry/sdk-node": "^0.200.0",
22+
"@opentelemetry/sdk-trace-base": "^2.0.0",
23+
"@opentelemetry/semantic-conventions": "^1.28.0",
1524
"@uidotdev/usehooks": "^2.4.1",
1625
"algoliasearch": "^4.24.0",
1726
"highlight.js": "^11.9.0",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"telemetry": {
3+
"enabled": true
4+
}
5+
}

0 commit comments

Comments
 (0)