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}