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