Skip to main content

bb_runtime/telemetry/
otel.rs

1//! Opt-in OTLP tracing exporter for the engine's spans
2//! (, gated by the `tracing-otel`
3//! feature).
4//!
5//! Construct a layer with [`install_otlp_layer`] and compose it into
6//! your host's `tracing_subscriber::Registry`. The framework
7//! deliberately does NOT install a global subscriber - span
8//! emission, runtime ownership, and dispatch-default registration
9//! all stay with the host.
10//!
11//! ## Example
12//!
13//! ```ignore
14//! use tracing_subscriber::prelude::*;
15//! let otel = bytesandbrains::telemetry::otel::install_otlp_layer(
16//!     "my-service",
17//!     "http://localhost:4317",
18//! )?;
19//! tracing_subscriber::registry()
20//!     .with(tracing_subscriber::fmt::layer())
21//!     .with(otel)
22//!     .init();
23//! # Ok::<(), Box<dyn std::error::Error>>(())
24//! ```
25
26use opentelemetry::trace::TracerProvider as _;
27use opentelemetry::KeyValue;
28use opentelemetry_otlp::WithExportConfig;
29use opentelemetry_sdk::trace::SdkTracerProvider;
30use opentelemetry_sdk::Resource;
31
32/// Error returned when constructing the OTLP layer fails.
33#[derive(Debug)]
34pub struct InstallError(pub String);
35
36impl std::fmt::Display for InstallError {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        write!(f, "OTLP layer install: {}", self.0)
39    }
40}
41
42impl std::error::Error for InstallError {}
43
44/// Build an OTLP-exporting `tracing` layer that the host can
45/// `.with(...)` into a `tracing_subscriber::Registry`. Spans
46/// emitted by the engine (`engine.poll`, `engine.phase*`,
47/// `engine.invoke_one`, `engine.deliver_fill`, ...) propagate to
48/// the configured collector endpoint.
49///
50/// `service_name` becomes the OTel `service.name` resource
51/// attribute. `endpoint` is the OTLP gRPC endpoint (typically
52/// `http://localhost:4317`).
53pub fn install_otlp_layer(
54    service_name: &str,
55    endpoint: &str,
56) -> Result<
57    tracing_opentelemetry::OpenTelemetryLayer<
58        tracing_subscriber::registry::Registry,
59        opentelemetry_sdk::trace::Tracer,
60    >,
61    InstallError,
62> {
63    let exporter = opentelemetry_otlp::SpanExporter::builder()
64        .with_tonic()
65        .with_endpoint(endpoint.to_string())
66        .build()
67        .map_err(|e| InstallError(format!("build exporter: {e}")))?;
68
69    let resource = Resource::builder()
70        .with_attribute(KeyValue::new("service.name", service_name.to_string()))
71        .build();
72
73    let provider = SdkTracerProvider::builder()
74        .with_batch_exporter(exporter)
75        .with_resource(resource)
76        .build();
77    let tracer = provider.tracer("bytesandbrains");
78    // Hand the provider off as the global so downstream `Tracer`
79    // lookups via context-propagation find it.
80    let _ = opentelemetry::global::set_tracer_provider(provider);
81
82    Ok(tracing_opentelemetry::layer().with_tracer(tracer))
83}