Commit c663b73
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 set1 parent baf0ab7 commit c663b73
40 files changed
Lines changed: 4085 additions & 149 deletions
File tree
- docs
- src/pages/en
- (pages)/features
- examples/hello-world
- packages/react-server
- cache
- config
- lib
- build
- dev
- handlers
- http
- middlewares
- plugins
- start
- server
- telemetry
- test
- __test__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
15 | 24 | | |
16 | 25 | | |
17 | 26 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
0 commit comments