telemetry_rust/
lib.rs

1// Initialization logic was retired from https://github.com/davidB/tracing-opentelemetry-instrumentation-sdk/
2// which is licensed under CC0 1.0 Universal
3// https://github.com/davidB/tracing-opentelemetry-instrumentation-sdk/blob/d3609ac2cc699d3a24fbf89754053cc8e938e3bf/LICENSE
4
5use tracing::level_filters::LevelFilter;
6#[cfg(debug_assertions)]
7use tracing_subscriber::fmt::format::FmtSpan;
8use tracing_subscriber::layer::SubscriberExt;
9
10use opentelemetry::trace::TracerProvider as _;
11pub use opentelemetry::{Array, Context, Key, KeyValue, StringValue, Value, global};
12pub use opentelemetry_sdk::{
13    Resource, error::OTelSdkError, trace::SdkTracerProvider as TracerProvider,
14};
15pub use opentelemetry_semantic_conventions::attribute as semconv;
16pub use tracing_opentelemetry::{OpenTelemetryLayer, OpenTelemetrySpanExt};
17
18pub mod fmt;
19pub mod http;
20pub mod middleware;
21pub mod otlp;
22pub mod propagation;
23
24#[cfg(feature = "axum")]
25pub use tracing_opentelemetry_instrumentation_sdk;
26
27#[cfg(feature = "test")]
28pub mod test;
29
30#[cfg(feature = "future")]
31pub mod future;
32
33mod filter;
34mod util;
35
36#[derive(Debug, Default)]
37pub struct DetectResource {
38    fallback_service_name: &'static str,
39    fallback_service_version: &'static str,
40}
41
42impl DetectResource {
43    /// `service.name` is first extracted from environment variables
44    /// (in this order) `OTEL_SERVICE_NAME`, `SERVICE_NAME`, `APP_NAME`.
45    /// But a default value can be provided with this method.
46    pub fn new(
47        fallback_service_name: &'static str,
48        fallback_service_version: &'static str,
49    ) -> Self {
50        DetectResource {
51            fallback_service_name,
52            fallback_service_version,
53        }
54    }
55
56    pub fn build(self) -> Resource {
57        let service_name = util::env_var("OTEL_SERVICE_NAME")
58            .or_else(|| util::env_var("SERVICE_NAME"))
59            .or_else(|| util::env_var("APP_NAME"))
60            .or_else(|| Some(self.fallback_service_name.to_string()))
61            .map(|v| KeyValue::new(semconv::SERVICE_NAME, v));
62        let service_version = util::env_var("OTEL_SERVICE_VERSION")
63            .or_else(|| util::env_var("SERVICE_VERSION"))
64            .or_else(|| util::env_var("APP_VERSION"))
65            .or_else(|| Some(self.fallback_service_version.to_string()))
66            .map(|v| KeyValue::new(semconv::SERVICE_VERSION, v));
67
68        let rsrc = Resource::builder()
69            .with_attributes([service_name, service_version].into_iter().flatten())
70            .build();
71
72        // Debug
73        rsrc.iter().for_each(
74            |kv| tracing::debug!(target: "otel::setup::resource", key = %kv.0, value = %kv.1),
75        );
76
77        rsrc
78    }
79}
80
81macro_rules! fmt_layer {
82    () => {{
83        let layer = tracing_subscriber::fmt::layer();
84
85        #[cfg(debug_assertions)]
86        let layer = layer.compact().with_span_events(FmtSpan::CLOSE);
87        #[cfg(not(debug_assertions))]
88        let layer = layer.json().event_format(fmt::JsonFormat);
89
90        layer.with_writer(std::io::stdout)
91    }};
92}
93
94pub fn init_tracing_with_fallbacks(
95    log_level: tracing::Level,
96    fallback_service_name: &'static str,
97    fallback_service_version: &'static str,
98) -> TracerProvider {
99    // set to debug to log detected resources, configuration read and infered
100    let setup_subscriber = tracing_subscriber::registry()
101        .with(Into::<LevelFilter>::into(log_level))
102        .with(fmt_layer!());
103    let _guard = tracing::subscriber::set_default(setup_subscriber);
104    tracing::info!("init logging & tracing");
105
106    let otel_rsrc =
107        DetectResource::new(fallback_service_name, fallback_service_version).build();
108    let tracer_provider =
109        otlp::init_tracer(otel_rsrc, otlp::identity).expect("TracerProvider setup");
110
111    global::set_tracer_provider(tracer_provider.clone());
112    global::set_text_map_propagator(
113        propagation::TextMapSplitPropagator::from_env().expect("TextMapPropagator setup"),
114    );
115
116    let otel_layer =
117        OpenTelemetryLayer::new(tracer_provider.tracer(env!("CARGO_PKG_NAME")));
118    let subscriber = tracing_subscriber::registry()
119        .with(Into::<filter::TracingFilter>::into(log_level))
120        .with(fmt_layer!())
121        .with(otel_layer);
122    tracing::subscriber::set_global_default(subscriber).unwrap();
123
124    tracer_provider
125}
126
127#[macro_export]
128macro_rules! init_tracing {
129    ($log_level:expr) => {
130        $crate::init_tracing_with_fallbacks(
131            $log_level,
132            env!("CARGO_PKG_NAME"),
133            env!("CARGO_PKG_VERSION"),
134        )
135    };
136}
137
138#[inline]
139pub fn shutdown_tracer_provider(provider: &TracerProvider) {
140    if let Err(err) = provider.force_flush() {
141        tracing::warn!("failed to flush tracer provider: {err:?}");
142    }
143    if let Err(err) = provider.shutdown() {
144        tracing::warn!("failed to shutdown tracer provider: {err:?}");
145    } else {
146        tracing::info!("tracer provider is shutdown")
147    }
148}