controller/
telemetry.rs

1#![allow(unused_imports)] // some used only for telemetry feature
2use opentelemetry::trace::{TraceId, TracerProvider};
3use opentelemetry_sdk::{runtime, trace as sdktrace, trace::Config, Resource};
4use tracing::Instrument;
5use tracing_subscriber::{prelude::*, EnvFilter, Registry};
6
7///  Fetch an opentelemetry::trace::TraceId as hex through the full tracing stack
8pub fn get_trace_id() -> TraceId {
9    use opentelemetry::trace::TraceContextExt as _; // opentelemetry::Context -> opentelemetry::trace::Span
10    use tracing_opentelemetry::OpenTelemetrySpanExt as _; // tracing::Span to opentelemetry::Context
11    tracing::Span::current()
12        .context()
13        .span()
14        .span_context()
15        .trace_id()
16}
17
18#[cfg(feature = "telemetry")]
19fn resource() -> Resource {
20    use opentelemetry::KeyValue;
21    Resource::new([
22        KeyValue::new("service.name", env!("CARGO_PKG_NAME")),
23        KeyValue::new("service.version", env!("CARGO_PKG_VERSION")),
24    ])
25}
26
27#[cfg(feature = "telemetry")]
28fn init_tracer() -> sdktrace::Tracer {
29    use opentelemetry_otlp::{SpanExporter, WithExportConfig};
30    let endpoint = std::env::var("OPENTELEMETRY_ENDPOINT_URL").expect("Needs an otel collector");
31    let exporter = SpanExporter::builder()
32        .with_tonic()
33        .with_endpoint(endpoint)
34        .build()
35        .unwrap();
36
37    let provider = sdktrace::TracerProvider::builder()
38        .with_batch_exporter(exporter, runtime::Tokio)
39        .with_resource(resource())
40        .build();
41
42    opentelemetry::global::set_tracer_provider(provider.clone());
43    provider.tracer("tracing-otel-subscriber")
44}
45
46/// Initialize tracing
47pub async fn init() {
48    // Setup tracing layers
49    #[cfg(feature = "telemetry")]
50    let otel = tracing_opentelemetry::OpenTelemetryLayer::new(init_tracer());
51
52    let logger = tracing_subscriber::fmt::layer().compact();
53    let env_filter = EnvFilter::try_from_default_env()
54        .or(EnvFilter::try_new("info"))
55        .unwrap();
56
57    // Decide on layers
58    let reg = Registry::default();
59    #[cfg(feature = "telemetry")]
60    reg.with(env_filter).with(logger).with(otel).init();
61    #[cfg(not(feature = "telemetry"))]
62    reg.with(env_filter).with(logger).init();
63}
64
65#[cfg(test)]
66mod test {
67    // This test only works when telemetry is initialized fully
68    // and requires OPENTELEMETRY_ENDPOINT_URL pointing to a valid server
69    #[cfg(feature = "telemetry")]
70    #[tokio::test]
71    #[ignore = "requires a trace exporter"]
72    async fn get_trace_id_returns_valid_traces() {
73        use super::*;
74        super::init().await;
75        #[tracing::instrument(name = "test_span")] // need to be in an instrumented fn
76        fn test_trace_id() -> TraceId {
77            get_trace_id()
78        }
79        assert_ne!(test_trace_id(), TraceId::INVALID, "valid trace");
80    }
81}