Skip to main content

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