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