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