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