This FRD ships @dmwd-io/tracing as an optional package that re-exports @opentelemetry/api and @opentelemetry/sdk-node as the platform standard for distributed tracing via OpenTelemetry, layering only platform-specific conventions on top: span naming, OTLP export defaults, and standard env var names. The value is standards alignment and drift prevention, not a custom abstraction over the library.
Introduction
Overview
ADR-023 mentions distributed tracing and trace_id correlation but no productized tracing solution exists. Each app that wants tracing manually configures OpenTelemetry SDK packages, chooses exporters, and invents its own span naming conventions. Trace context propagation across service boundaries is inconsistent.
Per ADR-014 (Open Source First), this FRD designates @opentelemetry/api and @opentelemetry/sdk-node as the community-standard library for distributed tracing and ships them through @dmwd-io/tracing — a thin re-export package. Platform conventions added on top are limited to: the {domain}.{action} span naming standard from ADR-023, the OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_SERVICE_NAME env var names, and default sampling rates. No custom provider interface is built — the OTel library’s own API is the interface.
Goals
Designate @opentelemetry/api and @opentelemetry/sdk-node as the platform standard for distributed tracing.
Ship @dmwd-io/tracing as a thin re-export of those libraries so teams import from a single canonical package.
Document OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_SERVICE_NAME as the platform-standard env var names.
Define span naming conventions matching the {domain}.{action} pattern from ADR-023.
Correlate traces with wide-event log entries via shared trace_id and span_id fields.
Include documentation covering frontend and backend setup.
Non-Goals
Building a custom provider interface or adapter layer — the library’s own API is the interface (per ADR-014).
Building a custom tracing backend or collector (use Grafana Tempo, Jaeger, or a vendor).
Implementing metrics collection (counters, histograms) — tracing only for v1.
Building a tracing UI or trace viewer.
Auto-instrumenting database queries (app-level concern using OTel ecosystem plugins).
React component-level tracing (render spans).
Scope
In Scope
Area
Description
Bootstrap function
createTracingProvider(config) initializing OTel SDK with exporter, service name, and environment
W3C TraceContext propagation via traceparent header
Fetch instrumentation
instrumentFetch() wrapping the global fetch to create spans and propagate context
HTTP instrumentation
instrumentHttp() wrapping Node.js HTTP server to create incoming request spans
Log correlation
Helper to extract trace_id and span_id from the current span for wide-event logging
Exporters
OTLP (gRPC/HTTP) and console exporter support
Shutdown
shutdown() method for graceful flush on process exit
Test adapter
createTestTracingProvider() capturing spans in-memory for assertions
Out of Scope
Area
Reason
Metrics (counters, histograms, gauges)
Separate concern; tracing-only for v1
Custom tracing backend
Use standard OTLP-compatible backends
Database instrumentation
App-level; use existing OTel ecosystem plugins
React render tracing
Complex and high-overhead; not in initial scope
Tracing UI / visualization
Vendor-hosted (Grafana, Jaeger)
Users and Pain Points
User Groups
User
Description
Needs
Backend developers
Engineers building services and API routes
Consistent tracing initialization with propagation across service boundaries
Frontend developers
Engineers building client-side apps
Fetch instrumentation with trace context propagation to backend
SRE / on-call engineers
Responders investigating latency and failures
Correlated traces and logs for end-to-end request visibility
Pain Points
User
Pain Point
Impact
Backend developers
Each service initializes OTel differently — different span names, different exporters, inconsistent propagation
Traces break at service boundaries; latency attribution is unreliable
Frontend developers
No frontend tracing — client-side requests are invisible in traces
Backend engineers cannot see the full request lifecycle
SRE engineers
Logs and traces are not correlated — trace_id is missing from log entries
Cross-referencing logs and traces requires manual guesswork
Backend developers
Span names vary across services — handleRequest vs api.users.list vs GET /users
Trace viewers are hard to navigate; filtering is unreliable
Definitions
Term
Definition
Span
A named, timed operation within a trace representing a unit of work
Trace
A tree of spans representing a distributed operation across services
Trace context
Metadata (trace_id, span_id, trace_flags) propagated between services
W3C TraceContext
The standard HTTP header format (traceparent) for propagating trace context
OTLP
OpenTelemetry Protocol — the standard wire format for exporting spans to collectors
Instrumentation
Code that automatically creates spans for common operations (fetch, http)
Exporter
A component that sends collected spans to a tracing backend
Wide event
A single structured log entry per ADR-023 containing trace_id for correlation
Current State
Existing Behavior
ADR-023 defines wide-event logging with a trace_id field, but no tracing infrastructure populates that field. There are no files under src/lib/observability/. Some apps have experimented with @opentelemetry/sdk-node in their entry points but none share configuration, span naming, or propagation patterns. Frontend services have no tracing at all.
Current Limitations
No shared tracing bootstrap — each app configures OTel from scratch.
No span naming convention — span names are inconsistent across services.
No trace_id in log entries — ADR-023 defines the field but nothing populates it.
No frontend tracing — client-side fetch requests do not create spans or propagate context.
No test adapter — tracing code is not tested because there is no in-memory span capture.
No graceful shutdown — some apps lose final spans on process exit.
Existing Workarounds
Backend apps copy-paste OTel setup from a wiki article.
Log correlation is done manually by searching timestamps across tools.
Frontend observability relies entirely on error tracking and analytics.
Proposed Solution
Summary
Per ADR-014, @dmwd-io/tracing is a thin re-export package. It re-exports @opentelemetry/api and @opentelemetry/sdk-node as peer dependencies and adds only the platform-level conventions listed below. No custom interface is built — the library’s API is the interface.
// All OTel API and SDK exports are available from the canonical platform import
Span naming: {domain}.{action} format matching ADR-023 (e.g. http.incoming_request, billing.charge).
Env vars: OTEL_EXPORTER_OTLP_ENDPOINT for the collector URL, OTEL_SERVICE_NAME for the service identifier.
Default sample rates: 0.05 for frontend, 1.0 for backend (configurable via OTEL_TRACES_SAMPLER_ARG).
Log correlation: guidance for extracting trace_id and span_id from the active span to populate ADR-023 wide-event log entries.
User Experience
End users are not directly affected. Tracing is a developer and operations concern. Users benefit indirectly from faster incident resolution.
Developer Experience
Teams import from @dmwd-io/tracing and get the full OTel API. The package enforces no wrapper layer — developers use @opentelemetry/api’s trace.getTracer(), tracer.startActiveSpan(), and trace.getActiveSpan() directly. The platform conventions (span naming, env vars, sample rate defaults) are documented rather than enforced programmatically.
Requirements
ID
Requirement
Priority
Notes
FR-001
Export createTracingProvider(config) initializing OTel with configurable exporter, service name, and sample rate
Must
-
FR-002
Export instrumentFetch() for client-side fetch instrumentation with context propagation
Must
-
FR-003
Export instrumentHttp(server) for Node.js HTTP server instrumentation
Must
-
FR-004
Export getTraceContext() returning { traceId, spanId } for log correlation
Must
-
FR-005
Export withSpan(name, fn) for wrapping arbitrary operations in a span
Must
-
FR-006
Export shutdown() for graceful span flush on process exit
Patching global fetch may conflict with other libraries that also patch fetch
Unpredictable behavior if multiple patches stack
Document the patching approach; provide a non-patching tracedFetch() wrapper alternative
OTel SDK API changes could break the wrapper
Maintenance burden
Pin to a stable OTel API version; abstract the OTel API behind the TracingProvider interface
Sampling rate 0.0 still initializes OTel infrastructure
Wasted resources when tracing is effectively disabled
Document that omitting createTracingProvider() entirely is the zero-overhead path
Frontend tracing at high sample rates generates many spans
Collector capacity may be overwhelmed
Default sample rate is 0.05 (5%) for frontend; document tuning guidance
Open Questions
ID
Question
Owner
Status
Resolution
Q-001
Should the wrapper support OTLP gRPC in addition to HTTP?
David Holmes
Open
—
Q-002
Should instrumentFetch support filtering which URLs are traced (e.g. exclude analytics calls)?
David Holmes
Open
—
Q-003
Should the wrapper include a baggage helper for propagating custom key-value pairs across services?
David Holmes
Open
—
Q-004
Should the frontend wrapper support PerformanceObserver integration for Web Vitals correlation?
David Holmes
Open
—
Acceptance Criteria
ID
Criteria
Related Requirement
AC-001
createTracingProvider({ serviceName: "test", exporter: "console" }) initializes without error
FR-001
AC-002
instrumentFetch() adds a traceparent header to outgoing fetch requests
FR-002, FUNC-003
AC-003
getTraceContext() returns a valid traceId and spanId within an active span
FR-004
AC-004
withSpan("test.op", fn) creates a span visible in the test adapter’s .spans array
FR-005, FUNC-008
AC-005
Span names follow {domain}.{action} convention
FUNC-007
AC-006
shutdown() flushes pending spans without error
FR-006
AC-007
Test adapter captures spans with names, attributes, and durations
FR-007
AC-008
Tracing failures do not throw — they are silently handled
NFR-002
AC-009
Unit tests cover provider creation, withSpan, fetch instrumentation, log correlation, and shutdown
FR-001 through FR-008
AC-010
OTel SDK packages are listed as peer dependencies, not direct dependencies
NFR-005
LLM Handoff Instructions
Expected LLM Behavior
Create the package under packages/tracing/ (or the repo’s established packages directory).
Add a package.json naming the package @dmwd-io/tracing with @opentelemetry/api and @opentelemetry/sdk-node as peer dependencies (not direct dependencies).
Create src/index.ts that re-exports the full OTel API surface: export * from '@opentelemetry/api'.
Add a src/sdk-node.ts entry that re-exports @opentelemetry/sdk-node: export * from '@opentelemetry/sdk-node'.
Document OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME, and OTEL_TRACES_SAMPLER_ARG as the platform env var names in the package README or Storybook docs page.
Document the {domain}.{action} span naming convention from ADR-023.
Run pnpm typecheck to confirm the package compiles cleanly.
LLM Should Not
Build a custom TracingProvider interface, factory function, or adapter layer.
Invent helper functions that duplicate what the OTel library already provides natively.
Decision Log
Date
Decision
Reason
Owner
2026-05-26
OTel SDK as peer dependencies, not bundled
Avoids version conflicts and reduces bundle size for apps that already use OTel
David Holmes
2026-05-26
{domain}.{action} span naming matching ADR-023
Consistent naming across logs and traces enables cross-tool queries
David Holmes
2026-05-26
Separate frontend and backend instrumentation functions
Different runtimes (browser vs Node.js) require different OTel providers and instrumentations
David Holmes
2026-06-02
Reframed per ADR-014 (Open Source First): adopt @opentelemetry/api / @opentelemetry/sdk-node as thin re-export rather than building a custom provider interface. Library API is the interface.
ADR-014 mandates designating community libraries as the standard rather than wrapping them
David Holmes
Document History
Date
Author
Change
2026-05-26
David Holmes
Initial draft
2026-06-02
David Holmes
Reframed per ADR-014: ship as thin re-export of @opentelemetry/api / @opentelemetry/sdk-node, drop custom interface.