Skip to content

FRD: Tracing / OTel Wrapper

Document Summary

FieldDetails
Feature NameTracing / OTel Wrapper
StatusDraft
OwnerDavid Holmes
ContributorsEngineering
Target Releasev2.0.0
Related LinksADR-051 (Provider Pattern), ADR-023 (Wide Events Logging), ADR-027 (Default Tech Stack), ADR-014 (Open Source First)
Last Updated2026-06-02
Open Source Libraries@opentelemetry/api, @opentelemetry/sdk-node, @opentelemetry/exporter-trace-otlp-http, @opentelemetry/sdk-trace-web
DocumentationOTel JavaScript Docs · Getting Started · API Reference · OTLP Exporter

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

AreaDescription
Bootstrap functioncreateTracingProvider(config) initializing OTel SDK with exporter, service name, and environment
Span namingConvention: {domain}.{action} matching ADR-023 wide events
Context propagationW3C TraceContext propagation via traceparent header
Fetch instrumentationinstrumentFetch() wrapping the global fetch to create spans and propagate context
HTTP instrumentationinstrumentHttp() wrapping Node.js HTTP server to create incoming request spans
Log correlationHelper to extract trace_id and span_id from the current span for wide-event logging
ExportersOTLP (gRPC/HTTP) and console exporter support
Shutdownshutdown() method for graceful flush on process exit
Test adaptercreateTestTracingProvider() capturing spans in-memory for assertions

Out of Scope

AreaReason
Metrics (counters, histograms, gauges)Separate concern; tracing-only for v1
Custom tracing backendUse standard OTLP-compatible backends
Database instrumentationApp-level; use existing OTel ecosystem plugins
React render tracingComplex and high-overhead; not in initial scope
Tracing UI / visualizationVendor-hosted (Grafana, Jaeger)

Users and Pain Points

User Groups

UserDescriptionNeeds
Backend developersEngineers building services and API routesConsistent tracing initialization with propagation across service boundaries
Frontend developersEngineers building client-side appsFetch instrumentation with trace context propagation to backend
SRE / on-call engineersResponders investigating latency and failuresCorrelated traces and logs for end-to-end request visibility

Pain Points

UserPain PointImpact
Backend developersEach service initializes OTel differently — different span names, different exporters, inconsistent propagationTraces break at service boundaries; latency attribution is unreliable
Frontend developersNo frontend tracing — client-side requests are invisible in tracesBackend engineers cannot see the full request lifecycle
SRE engineersLogs and traces are not correlated — trace_id is missing from log entriesCross-referencing logs and traces requires manual guesswork
Backend developersSpan names vary across services — handleRequest vs api.users.list vs GET /usersTrace viewers are hard to navigate; filtering is unreliable

Definitions

TermDefinition
SpanA named, timed operation within a trace representing a unit of work
TraceA tree of spans representing a distributed operation across services
Trace contextMetadata (trace_id, span_id, trace_flags) propagated between services
W3C TraceContextThe standard HTTP header format (traceparent) for propagating trace context
OTLPOpenTelemetry Protocol — the standard wire format for exporting spans to collectors
InstrumentationCode that automatically creates spans for common operations (fetch, http)
ExporterA component that sends collected spans to a tracing backend
Wide eventA 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
import { trace, context, SpanStatusCode } from '@dmwd-io/tracing';
import { NodeSDK } from '@dmwd-io/tracing/sdk-node';

Platform conventions added on top of the library

  • 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

IDRequirementPriorityNotes
FR-001Export createTracingProvider(config) initializing OTel with configurable exporter, service name, and sample rateMust-
FR-002Export instrumentFetch() for client-side fetch instrumentation with context propagationMust-
FR-003Export instrumentHttp(server) for Node.js HTTP server instrumentationMust-
FR-004Export getTraceContext() returning { traceId, spanId } for log correlationMust-
FR-005Export withSpan(name, fn) for wrapping arbitrary operations in a spanMust-
FR-006Export shutdown() for graceful span flush on process exitMust-
FR-007Export createTestTracingProvider() capturing spans in-memoryMust-
FR-008Support OTLP (HTTP) and console exporters via configShould-

Priority Definitions

PriorityMeaning
MustRequired for this feature to ship.
ShouldImportant, but can be deferred if needed.
CouldNice to have. Not required for initial release.

Functional Requirements

IDRequirementUser BenefitPriority
FUNC-001createTracingProvider({ serviceName: "api", exporter: "otlp" }) initializes OTel and starts collecting spansOne-line tracing setup in service entry pointsMust
FUNC-002instrumentFetch() creates a span for each fetch() call with the URL and method as span attributesClient-side requests are visible in tracesMust
FUNC-003instrumentFetch() injects traceparent header into outgoing requestsTrace context propagates from frontend to backendMust
FUNC-004instrumentHttp(server) creates a span for each incoming HTTP requestBackend request latency is visible in tracesMust
FUNC-005getTraceContext() returns the current traceId and spanId for embedding in log entriesLogs and traces are correlated per ADR-023Must
FUNC-006withSpan("billing.charge", async () => {...}) creates a child span around the operationCustom operations are traced without manual OTel SDK usageMust
FUNC-007Span names follow {domain}.{action} convention: http.request, fetch.call, billing.chargeConsistent naming across all servicesMust
FUNC-008Test adapter’s .spans array contains all created spans with names, attributes, and durationsInstrumentation is testableMust

Non-Functional Requirements

IDRequirementCategoryPriority
NFR-001Tracing overhead does not exceed 5% latency increase on instrumented operationsPerformanceMust
NFR-002Tracing failures (exporter down, span creation error) must not crash the appReliabilityMust
NFR-003instrumentFetch() must not modify the original fetch response or error behaviorCompatibilityMust
NFR-004Test adapter operates fully in-memory with no network I/OTestingMust
NFR-005OTel SDK packages are peer dependencies, not bundledPerformanceMust
NFR-006shutdown() flushes all pending spans within a configurable timeout (default 5s)ReliabilityShould
NFR-007Sampling rate is configurable (0.0 to 1.0) to control span volumePerformanceShould

API / Interface Requirements

Public API

NameTypeDescriptionRequired
TracingProviderinterfaceCore contract with withSpan, getTraceContext, shutdownYes
TracingConfigtype{ serviceName, environment, exporter, sampleRate, otlpEndpoint? }Yes
TraceContexttype{ traceId: string, spanId: string }Yes
SpanRecordtype{ name, attributes, startTime, endTime, status, parentSpanId? } (test adapter)Yes
createTracingProviderfunctionBootstrap factory returning TracingProviderYes
instrumentFetchfunctionWraps global fetch with span creation and context propagationYes
instrumentHttpfunctionWraps Node.js HTTP server with incoming request spansYes
getTraceContextfunctionReturns TraceContext from active spanYes
withSpanfunction<T>(name: string, fn: () => Promise<T>) => Promise<T>Yes
createTestTracingProviderfunctionReturns TracingProvider & { spans: SpanRecord[] }Yes

Example Usage

// Backend entry point
import { createTracingProvider, instrumentHttp, getTraceContext } from "@dmwd/design-system/observability/tracing";
const tracing = createTracingProvider({
serviceName: "billing-api",
environment: "production",
exporter: "otlp",
otlpEndpoint: "http://collector:4318",
sampleRate: 0.1,
});
// Auto-instrument incoming requests
instrumentHttp(server);
// Manual span
const result = await tracing.withSpan("billing.create_invoice", async () => {
return await createInvoice(customerId);
});
// Log correlation
const { traceId, spanId } = getTraceContext();
logger.info({ msg: "invoice.created", trace_id: traceId, span_id: spanId });
// Graceful shutdown
process.on("SIGTERM", () => tracing.shutdown());
// Frontend
import { createTracingProvider, instrumentFetch } from "@dmwd/design-system/observability/tracing";
createTracingProvider({ serviceName: "web-app", exporter: "otlp", sampleRate: 0.05 });
instrumentFetch(); // All subsequent fetch() calls are traced

API Notes

  • instrumentFetch() patches the global fetch; call it once at app startup.
  • instrumentHttp() accepts a Node.js http.Server or compatible object.
  • withSpan propagates the span context to child operations automatically.
  • OTel SDK packages (@opentelemetry/sdk-trace-base, @opentelemetry/api) are peer dependencies.

Accessibility Requirements

IDRequirementNotes
A11Y-001Package is a backend/infrastructure utility with no UI — accessibility requirements do not applyNo UI components

Checklist

  • Keyboard support is defined. (N/A — no UI)
  • Focus behavior is defined. (N/A — no UI)
  • Screen reader behavior is defined. (N/A — no UI)
  • Color contrast requirements are met. (N/A — no UI)
  • Reduced motion behavior is considered. (N/A — no UI)
  • Semantic HTML expectations are documented. (N/A — no UI)
  • ARIA usage is defined only where needed. (N/A — no UI)

Content and Documentation Requirements

IDRequirementLocationPriority
DOC-001Storybook docs page explaining tracing setup for frontend and backendStorybookMust
DOC-002Inline JSDoc on every exported function and typeSource codeMust
DOC-003Example: backend service setup with instrumentHttp and log correlationStorybookMust
DOC-004Example: frontend setup with instrumentFetchStorybookMust
DOC-005Example: custom span with withSpanStorybookShould
DOC-006Example: test assertions using the test adapterStorybookShould

Dependencies

DependencyTypeOwnerStatusNotes
ADR-051 provider patternArchitectureEngineeringReadyGeneral structure guidance
ADR-023 wide events loggingArchitectureEngineeringReadyDefines trace_id field and {domain}.{action} naming
@opentelemetry/apiPeer dependencyOTel projectReadyCore OTel API
@opentelemetry/sdk-trace-basePeer dependencyOTel projectReadySpan processing
@opentelemetry/exporter-trace-otlp-httpPeer dependencyOTel projectReadyOTLP exporter

Risks and Tradeoffs

Risk / TradeoffImpactMitigation
OTel SDK is large — peer dependencies increase app bundle for frontendFrontend bundle size increasesTree-shakeable imports; sampling rate 0.0 effectively disables tracing; document bundle impact
Patching global fetch may conflict with other libraries that also patch fetchUnpredictable behavior if multiple patches stackDocument the patching approach; provide a non-patching tracedFetch() wrapper alternative
OTel SDK API changes could break the wrapperMaintenance burdenPin to a stable OTel API version; abstract the OTel API behind the TracingProvider interface
Sampling rate 0.0 still initializes OTel infrastructureWasted resources when tracing is effectively disabledDocument that omitting createTracingProvider() entirely is the zero-overhead path
Frontend tracing at high sample rates generates many spansCollector capacity may be overwhelmedDefault sample rate is 0.05 (5%) for frontend; document tuning guidance

Open Questions

IDQuestionOwnerStatusResolution
Q-001Should the wrapper support OTLP gRPC in addition to HTTP?David HolmesOpen
Q-002Should instrumentFetch support filtering which URLs are traced (e.g. exclude analytics calls)?David HolmesOpen
Q-003Should the wrapper include a baggage helper for propagating custom key-value pairs across services?David HolmesOpen
Q-004Should the frontend wrapper support PerformanceObserver integration for Web Vitals correlation?David HolmesOpen

Acceptance Criteria

IDCriteriaRelated Requirement
AC-001createTracingProvider({ serviceName: "test", exporter: "console" }) initializes without errorFR-001
AC-002instrumentFetch() adds a traceparent header to outgoing fetch requestsFR-002, FUNC-003
AC-003getTraceContext() returns a valid traceId and spanId within an active spanFR-004
AC-004withSpan("test.op", fn) creates a span visible in the test adapter’s .spans arrayFR-005, FUNC-008
AC-005Span names follow {domain}.{action} conventionFUNC-007
AC-006shutdown() flushes pending spans without errorFR-006
AC-007Test adapter captures spans with names, attributes, and durationsFR-007
AC-008Tracing failures do not throw — they are silently handledNFR-002
AC-009Unit tests cover provider creation, withSpan, fetch instrumentation, log correlation, and shutdownFR-001 through FR-008
AC-010OTel SDK packages are listed as peer dependencies, not direct dependenciesNFR-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.
  • Bundle OTel SDK packages as direct dependencies.
  • Implement metrics or logging (tracing only).
  • Build framework-specific middleware (Express, Hono, Astro).
  • Invent helper functions that duplicate what the OTel library already provides natively.

Decision Log

DateDecisionReasonOwner
2026-05-26OTel SDK as peer dependencies, not bundledAvoids version conflicts and reduces bundle size for apps that already use OTelDavid Holmes
2026-05-26{domain}.{action} span naming matching ADR-023Consistent naming across logs and traces enables cross-tool queriesDavid Holmes
2026-05-26Separate frontend and backend instrumentation functionsDifferent runtimes (browser vs Node.js) require different OTel providers and instrumentationsDavid Holmes
2026-06-02Reframed 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 themDavid Holmes

Document History

DateAuthorChange
2026-05-26David HolmesInitial draft
2026-06-02David HolmesReframed per ADR-014: ship as thin re-export of @opentelemetry/api / @opentelemetry/sdk-node, drop custom interface.