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