OpenTelemetry (id_effect_opentelemetry)
The workspace crate id_effect_opentelemetry implements Phase B of the Effect.ts parity plan:
first-class OpenTelemetry traces and metrics alongside id_effect’s fiber-local
with_span and
Metric primitives.
Design goals match @effect/opentelemetry:
- Opt-in at the dependency boundary — the core
id_effectcrate does not depend on OTEL. - Tracing — bridge
tracingspans to OTEL exporters (OTLP in production; in-memory in tests). - Propagation — W3C Trace Context (
traceparent/tracestate) on portable header maps. - Metrics — dual-write bridges from
Metriccounters and duration histograms to OTEL instruments.
Tracing: compose with_span and OTEL
Use id_effect_opentelemetry::with_span_otel
when you want both:
- Fiber-local span stack and
EffectEventstream (id_effect::with_span), and - A
tracingspan exported throughtracing_opentelemetry.
Build a tracer provider (here: in-memory for tests), install a tracing subscriber with an OTEL layer,
then run effects under with_span_otel:
#![allow(unused)] fn main() { use id_effect::{install_tracing_layer, run_blocking, succeed, TracingConfig}; use id_effect_opentelemetry::{ sdk_tracer_provider_with_in_memory_exporter, trace_subscriber_for_provider, with_span_otel, }; use opentelemetry_sdk::trace::InMemorySpanExporter; let exporter = InMemorySpanExporter::default(); let provider = sdk_tracer_provider_with_in_memory_exporter(&exporter); let subscriber = trace_subscriber_for_provider(&provider, false); tracing::subscriber::with_default(subscriber, || { let _ = run_blocking(install_tracing_layer(TracingConfig::enabled()), ()); let eff = with_span_otel("request", succeed::<(), (), ()>(())); let _ = run_blocking(eff, ()); }); let _ = provider.force_flush(); let _ = provider.shutdown(); }
In production you typically:
- Configure an OTLP exporter (or another
SpanExporter) onSdkTracerProvider. - Call
register_global_tracer_provideronce at startup. - Install a global
tracing_subscriberstack withtracing_opentelemetry::layer().
W3C Trace Context on header maps
For HTTP clients built on Vec<(String, String)> (or similar), use:
install_w3c_trace_context_propagatoronce per process.inject_trace_context_into_headersbefore sending a downstream request.extract_trace_context_from_headerswhen handling an incoming request.
For Axum / http::HeaderMap, map headers into this shape at the boundary or add a small adapter in your app.
Metrics: CounterBridge and DurationHistogramBridge
CounterBridge and
DurationHistogramBridge
keep id_effect::Metric snapshots for tests while exporting measurements to OTEL.
Create an SdkMeterProvider
with a PeriodicReader
and an InMemoryMetricExporter
for CI-style assertions, or wire an OTLP metrics exporter for production.
Cardinality: keep label sets small and stable; prefer bounded service, route, tenant keys over
high-cardinality user IDs in metric attributes.
Axum + Tokio production sketch
- Build tracer + meter providers with OTLP endpoints from
id_effect_config(or env). - Register globals and
try_initatracing_subscriberregistry (fmt + OTEL + optionalEnvFilter). - In Axum middleware, extract
traceparentinto an OTELContext, attach it to the request extension, and spawn handler work inside that context. - On graceful shutdown, call
force_flush/shutdownon providers (mirrorScopefinalizer patterns fromid_effect).
See also: docs/effect-ts-parity/phases/phase-b-opentelemetry.md in the repository for the full task breakdown and Beads slug map.