telemetry_rust/
lib.rs

1#![warn(missing_docs, clippy::missing_panics_doc)]
2
3//! A comprehensive OpenTelemetry telemetry library for Rust applications.
4//!
5//! This crate provides easy-to-use telemetry integration for Rust applications, with support for
6//! OpenTelemetry tracing, metrics, and logging. It includes middleware for popular frameworks
7//! like Axum and AWS Lambda, along with utilities for context propagation and configuration.
8//!
9//! # Features
10//!
11//! - OpenTelemetry tracing instrumentation
12//! - Formatted logs with tracing metadata
13//! - Context Propagation for incoming and outgoing HTTP requests
14//! - Axum middleware to instrument http services
15//! - AWS Lambda instrumentation layer
16//! - AWS SDK instrumentation
17//! - Integration testing tools
18//!
19//! # Available Feature Flags
20//!
21//! - `axum`: Axum web framework middleware support
22//! - `aws-span`: AWS SDK span creation utilities
23//! - `aws-instrumentation`: AWS SDK automatic instrumentation
24//! - `aws-lambda`: AWS Lambda runtime middleware
25//! - `aws`: All AWS features (span + instrumentation)
26//! - `aws-full`: All AWS features including Lambda
27//! - `future`: Future instrumentation utilities
28//! - `test`: Testing utilities for OpenTelemetry validation
29//! - `zipkin`: Zipkin context propagation support (enabled by default)
30//! - `full`: All features enabled
31//!
32//! # Quick Start
33//!
34//! ```rust
35//! use telemetry_rust::{init_tracing, shutdown_tracer_provider};
36//! use tracing::Level;
37//!
38//! // Initialize telemetry
39//! let tracer_provider = init_tracing!(Level::INFO);
40//!
41//! // Your application code here...
42//!
43//! // Shutdown telemetry when done
44//! shutdown_tracer_provider(&tracer_provider);
45//! ```
46
47// Initialization logic was retired from https://github.com/davidB/tracing-opentelemetry-instrumentation-sdk/
48// which is licensed under CC0 1.0 Universal
49// https://github.com/davidB/tracing-opentelemetry-instrumentation-sdk/blob/d3609ac2cc699d3a24fbf89754053cc8e938e3bf/LICENSE
50
51use tracing::level_filters::LevelFilter;
52#[cfg(debug_assertions)]
53use tracing_subscriber::fmt::format::FmtSpan;
54use tracing_subscriber::layer::SubscriberExt;
55
56use opentelemetry::trace::TracerProvider as _;
57pub use opentelemetry::{Array, Context, Key, KeyValue, StringValue, Value, global};
58pub use opentelemetry_sdk::{
59    Resource, error::OTelSdkError, trace::SdkTracerProvider as TracerProvider,
60};
61pub use opentelemetry_semantic_conventions::attribute as semconv;
62pub use tracing_opentelemetry::{OpenTelemetryLayer, OpenTelemetrySpanExt};
63
64pub mod fmt;
65pub mod http;
66pub mod middleware;
67pub mod otlp;
68pub mod propagation;
69
70#[cfg(feature = "axum")]
71pub use tracing_opentelemetry_instrumentation_sdk;
72
73#[cfg(feature = "test")]
74pub mod test;
75
76#[cfg(feature = "future")]
77pub mod future;
78
79mod filter;
80mod util;
81
82/// Resource detection utility for automatically configuring OpenTelemetry service metadata.
83///
84/// This struct helps detect and configure service information from environment variables
85/// with fallback values. It supports the standard OpenTelemetry environment variables
86/// as well as common service naming conventions.
87///
88/// # Environment Variables
89///
90/// The following environment variables are checked in order of priority:
91/// - Service name: `OTEL_SERVICE_NAME`, `SERVICE_NAME`, `APP_NAME`
92/// - Service version: `OTEL_SERVICE_VERSION`, `SERVICE_VERSION`, `APP_VERSION`
93#[derive(Debug, Default)]
94pub struct DetectResource {
95    fallback_service_name: &'static str,
96    fallback_service_version: &'static str,
97}
98
99impl DetectResource {
100    /// `service.name` is first extracted from environment variables
101    /// (in this order) `OTEL_SERVICE_NAME`, `SERVICE_NAME`, `APP_NAME`.
102    /// But a default value can be provided with this method.
103    pub fn new(
104        fallback_service_name: &'static str,
105        fallback_service_version: &'static str,
106    ) -> Self {
107        DetectResource {
108            fallback_service_name,
109            fallback_service_version,
110        }
111    }
112
113    /// Builds the OpenTelemetry resource with detected service information.
114    ///
115    /// This method checks environment variables in order of priority and falls back
116    /// to the provided default values if no environment variables are set.
117    ///
118    /// # Returns
119    ///
120    /// A configured [`Resource`] with service name and version attributes.
121    pub fn build(self) -> Resource {
122        let service_name = util::env_var("OTEL_SERVICE_NAME")
123            .or_else(|| util::env_var("SERVICE_NAME"))
124            .or_else(|| util::env_var("APP_NAME"))
125            .or_else(|| Some(self.fallback_service_name.to_string()))
126            .map(|v| KeyValue::new(semconv::SERVICE_NAME, v));
127        let service_version = util::env_var("OTEL_SERVICE_VERSION")
128            .or_else(|| util::env_var("SERVICE_VERSION"))
129            .or_else(|| util::env_var("APP_VERSION"))
130            .or_else(|| Some(self.fallback_service_version.to_string()))
131            .map(|v| KeyValue::new(semconv::SERVICE_VERSION, v));
132
133        let rsrc = Resource::builder()
134            .with_attributes([service_name, service_version].into_iter().flatten())
135            .build();
136
137        // Debug
138        rsrc.iter().for_each(
139            |kv| tracing::debug!(target: "otel::setup::resource", key = %kv.0, value = %kv.1),
140        );
141
142        rsrc
143    }
144}
145
146macro_rules! fmt_layer {
147    () => {{
148        let layer = tracing_subscriber::fmt::layer();
149
150        #[cfg(debug_assertions)]
151        let layer = layer.compact().with_span_events(FmtSpan::CLOSE);
152        #[cfg(not(debug_assertions))]
153        let layer = layer.json().event_format(fmt::JsonFormat);
154
155        layer.with_writer(std::io::stdout)
156    }};
157}
158
159/// Initializes tracing with OpenTelemetry integration and fallback service information.
160///
161/// This function sets up a complete tracing infrastructure including:
162/// - A temporary subscriber for setup logging
163/// - Resource detection from environment variables with fallbacks
164/// - OTLP tracer provider initialization
165/// - Global propagator configuration
166/// - Final subscriber with both console output and OpenTelemetry export
167///
168/// # Arguments
169///
170/// - `log_level`: The minimum log level for events
171/// - `fallback_service_name`: Default service name if not found in environment variables
172/// - `fallback_service_version`: Default service version if not found in environment variables
173///
174/// # Returns
175///
176/// A configured [`TracerProvider`] that should be kept alive for the duration of the application
177/// and passed to [`shutdown_tracer_provider`] on shutdown.
178///
179/// # Examples
180///
181/// ```rust
182/// use telemetry_rust::{init_tracing_with_fallbacks, shutdown_tracer_provider};
183/// use tracing::Level;
184///
185/// let tracer_provider = init_tracing_with_fallbacks(Level::INFO, "my-service", "1.0.0");
186///
187/// // Your application code here...
188///
189/// shutdown_tracer_provider(&tracer_provider);
190/// ```
191///
192/// # Panics
193///
194/// This function will panic if:
195/// - The OTLP tracer provider cannot be initialized
196/// - The text map propagator cannot be configured
197pub fn init_tracing_with_fallbacks(
198    log_level: tracing::Level,
199    fallback_service_name: &'static str,
200    fallback_service_version: &'static str,
201) -> TracerProvider {
202    // set to debug to log detected resources, configuration read and infered
203    let setup_subscriber = tracing_subscriber::registry()
204        .with(Into::<LevelFilter>::into(log_level))
205        .with(fmt_layer!());
206    let _guard = tracing::subscriber::set_default(setup_subscriber);
207    tracing::info!("init logging & tracing");
208
209    let otel_rsrc =
210        DetectResource::new(fallback_service_name, fallback_service_version).build();
211    let tracer_provider =
212        otlp::init_tracer(otel_rsrc, otlp::identity).expect("TracerProvider setup");
213
214    global::set_tracer_provider(tracer_provider.clone());
215    global::set_text_map_propagator(
216        propagation::TextMapSplitPropagator::from_env().expect("TextMapPropagator setup"),
217    );
218
219    let otel_layer =
220        OpenTelemetryLayer::new(tracer_provider.tracer(env!("CARGO_PKG_NAME")));
221    let subscriber = tracing_subscriber::registry()
222        .with(Into::<filter::TracingFilter>::into(log_level))
223        .with(fmt_layer!())
224        .with(otel_layer);
225    tracing::subscriber::set_global_default(subscriber).unwrap();
226
227    tracer_provider
228}
229
230/// Convenience macro for initializing tracing with package name and version as fallbacks.
231///
232/// This macro calls [`init_tracing_with_fallbacks`] using the current package's name and version
233/// from `CARGO_PKG_NAME` and `CARGO_PKG_VERSION` environment variables as fallback values.
234///
235/// # Arguments
236///
237/// - `log_level`: The minimum log level for events (e.g., `Level::INFO`)
238///
239/// # Returns
240///
241/// A configured [`TracerProvider`] that should be kept alive for the duration of the application.
242///
243/// # Examples
244///
245/// ```rust
246/// use telemetry_rust::{init_tracing, shutdown_tracer_provider};
247/// use tracing::Level;
248///
249/// let tracer_provider = init_tracing!(Level::INFO);
250///
251/// // Your application code here...
252///
253/// shutdown_tracer_provider(&tracer_provider);
254/// ```
255#[macro_export]
256macro_rules! init_tracing {
257    ($log_level:expr) => {
258        $crate::init_tracing_with_fallbacks(
259            $log_level,
260            env!("CARGO_PKG_NAME"),
261            env!("CARGO_PKG_VERSION"),
262        )
263    };
264}
265
266/// Properly shuts down a tracer provider, flushing pending spans and cleaning up resources.
267///
268/// This function performs a graceful shutdown of the tracer provider by:
269/// 1. Attempting to flush any pending spans to the exporter
270/// 2. Shutting down the tracer provider and its associated resources
271/// 3. Logging any errors that occur during the shutdown process
272///
273/// # Arguments
274///
275/// - `provider`: Reference to the [`TracerProvider`] to shut down
276///
277/// # Examples
278///
279/// ```rust
280/// use telemetry_rust::{init_tracing, shutdown_tracer_provider};
281/// use tracing::Level;
282///
283/// let tracer_provider = init_tracing!(Level::INFO);
284///
285/// // Your application code here...
286///
287/// shutdown_tracer_provider(&tracer_provider);
288/// ```
289#[inline]
290pub fn shutdown_tracer_provider(provider: &TracerProvider) {
291    if let Err(err) = provider.force_flush() {
292        tracing::warn!("failed to flush tracer provider: {err:?}");
293    }
294    if let Err(err) = provider.shutdown() {
295        tracing::warn!("failed to shutdown tracer provider: {err:?}");
296    } else {
297        tracing::info!("tracer provider is shutdown")
298    }
299}