crates.io ↗

iet-telemetry-otel

feature implementation official

Initializes tracing, metrics, and logs exporters explicitly at process start.

CoreFeaturesIntegrationsStacksTooling
Used byno framework crate yetexamples/htmx-starter
iet-telemetry-otelimplementationOpenTelemetry telemetry
Depends oniet-configcontractiet-corecontractiet-telemetrycontract

How to use it

iet-telemetry-otel initializes OpenTelemetry once for the process and installs the tracing subscriber used across the framework. Call OpenTelemetry::init at startup, emit spans, logs, and metrics through iet_telemetry_otel::tracing, then shutdown().await after the application has drained.

rust
// examples/htmx-starter/servers/web/src/main.rs
let telemetry = iet_telemetry_otel::OpenTelemetry::init(
    iet_telemetry_otel::config_from_env()?,
)?;
iet_telemetry_otel::tracing::info!(target: "fw::lifecycle", "process initialized");

let result = run().await;                // serve, migrate, seed...
let shutdown = telemetry.shutdown().await;
result?;
shutdown?;
  • config_from_env snapshots only FW_TELEMETRY_* and OTEL_EXPORTER_OTLP_ENDPOINT; logs go to OTLP or JSON stdout, never both. An explicit config works too: TelemetryConfig { endpoint, ..TelemetryConfig::profile(Profile::Debug) }.
  • init must be called once (a second call fails with telemetry.already_initialized); tracing events on the fw::telemetry::metrics target become counters, gauges, and histograms.
  • shutdown drains exporters off the async executor with a timeout; export outcome counters are exposed as trace_exports, log_exports, and metric_exports.

How it works

OpenTelemetry::init builds one provider per signal — SdkTracerProvider, SdkMeterProvider, and SdkLoggerProvider — each feeding an OTLP/HTTP exporter through its batch or periodic processor, then installs a tracing_subscriber registry with the trace, metric, and log layers via a single try_init. shutdown moves the blocking SDK flush off the async executor in spawn_blocking and bounds it with a timeout.

flowchart LR
  A["OpenTelemetry::init"] --> B["SdkTracerProvider"]
  A --> C["SdkMeterProvider"]
  A --> D["SdkLoggerProvider"]
  B --> E["BatchSpanProcessor"]
  C --> F["PeriodicReader"]
  D --> G["BatchLogProcessor"]
  E --> H["OTLP http exporter"]
  F --> H
  G --> H
  A --> I["tracing_subscriber registry"]
  I -->|"try_init once"| J["global subscriber"]
  A --> K["shutdown"]
  K --> L["spawn_blocking flush"]

What it implements

  • Concrete mechanism; see the public API below.

What's inside

Modules

  • src/cardinality.rs
  • src/diagnostics.rs
  • src/lib.rs
  • src/resources.rs
  • src/testing.rs
  • src/testing/sdk.rs

Public API

(root)

  • pub fn config_from_env() -> AppResult<TelemetryConfig> — Explicit snapshot of only telemetry-related environment values.
  • struct OpenTelemetry
  • pub fn init(config: TelemetryConfig) -> AppResult<Self> — Uses OTLP/HTTP protobuf on dedicated exporter threads. Call once at startup. Logs go to OTLP or JSON stdout, not both. No global meter provider is needed.
  • pub fn meter(&self, name: &'static str) -> opentelemetry::metrics::Meter
  • pub fn duration_view( config: iet_telemetry::HistogramConfig, ) -> impl Fn(&opentelemetry_sdk::metrics::Instrument) -> Option<opentelemetry_sdk::metrics::Stream> — Shared by production and test providers. Quantiles are computed downstream, never averaged across instances. All framework duration observations are seconds.

cardinality

  • pub fn new(limit: usize) -> Self

diagnostics

  • struct ExportStats
  • struct ExportSnapshot
  • pub fn snapshot(&self) -> ExportSnapshot
  • struct Observed

testing

  • struct CapturedRecord
  • struct Snapshot
  • pub fn assert_span_exists(&self, name: &str)
  • pub fn assert_no_dropped_records(&self)
  • struct TestTelemetry
  • pub fn new(capacity: usize) -> Self
  • pub fn dispatch(&self) -> tracing::Dispatch
  • pub fn snapshot(&self) -> Snapshot

testing::sdk

  • struct TestProviders — Owns isolated SDK providers and a scoped subscriber for all three signals. Keep this alive until application tasks and response bodies have completed.
  • pub fn new(capacity: usize) -> Self
  • pub fn with_histogram(capacity: usize, histogram: iet_telemetry::HistogramConfig) -> Self
  • pub fn dispatch(&self) -> tracing::Dispatch
  • pub fn meter(&self, name: &'static str) -> opentelemetry::metrics::Meter
  • pub fn flush(&self) -> OTelSdkResult
  • pub fn shutdown(&self) -> OTelSdkResult
  • struct Capture
  • pub fn new(capacity: usize) -> Self
  • pub fn take(&self) -> Vec<T>
  • pub fn dropped(&self) -> u64
  • pub fn bounded(capacity: usize, max_points: usize, max_debug_bytes: usize) -> Self — Reject an entire oversized export before taking ownership. Debug-byte budget also bounds histogram arrays and exemplars, not just attributes.

Impl map

  • Visit for Identity<H>
  • Filter<S> for Cardinality
  • Clone for Capture<T>
  • SpanExporter for Capture<SpanData>
  • LogExporter for Capture<SdkLogRecord>
  • PushMetricExporter for Capture<ResourceMetrics>
  • std::fmt::Write for LimitedText
  • Visit for Fields
  • Layer<S> for TestTelemetry

Depends on

External crates: async-trait, opentelemetry, opentelemetry-appender-tracing, opentelemetry-otlp, opentelemetry_sdk, rustix, tokio, tracing, tracing-opentelemetry, tracing-subscriber

Used by

  • No other framework crate depends on it directly.

In this repository

  • examples/htmx-starter

Source: crates/features/iet-telemetry-otel