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