lambda_otel_utils/subscriber.rs
1//! Utilities for configuring and managing OpenTelemetry tracing subscribers.
2//!
3//! This module provides a builder pattern for configuring tracing subscribers with
4//! OpenTelemetry support, along with utility functions for creating tracing and metrics layers.
5//!
6//! # Examples
7//!
8//! Basic usage with default configuration:
9//!
10//! ```rust,no_run
11//! use lambda_otel_utils::{
12//! HttpTracerProviderBuilder,
13//! HttpMeterProviderBuilder,
14//! init_otel_subscriber
15//! };
16//! use std::time::Duration;
17//!
18//! # async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
19//! let tracer_provider = HttpTracerProviderBuilder::default()
20//! .with_stdout_client()
21//! .build()?;
22//!
23//! let meter_provider = HttpMeterProviderBuilder::default()
24//! .with_stdout_client()
25//! .with_meter_name("my-service")
26//! .with_export_interval(Duration::from_secs(30))
27//! .build()?;
28//!
29//! // Initialize with default settings
30//! init_otel_subscriber(tracer_provider, meter_provider, "my-service")?;
31//! # Ok(())
32//! # }
33//! ```
34//!
35//! Custom configuration using the builder:
36//!
37//! ```rust,no_run
38//! use lambda_otel_utils::{
39//! HttpTracerProviderBuilder,
40//! HttpMeterProviderBuilder,
41//! OpenTelemetrySubscriberBuilder
42//! };
43//! use std::time::Duration;
44//!
45//! # async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
46//! let tracer_provider = HttpTracerProviderBuilder::default()
47//! .with_stdout_client()
48//! .build()?;
49//!
50//! let meter_provider = HttpMeterProviderBuilder::default()
51//! .with_stdout_client()
52//! .with_meter_name("my-service")
53//! .with_export_interval(Duration::from_secs(30))
54//! .build()?;
55//!
56//! // Custom configuration
57//! OpenTelemetrySubscriberBuilder::new()
58//! .with_tracer_provider(tracer_provider)
59//! .with_meter_provider(meter_provider)
60//! .with_service_name("my-service")
61//! .with_env_filter(true)
62//! .with_json_format(true)
63//! .init()?;
64//! # Ok(())
65//! # }
66//! ```
67
68use opentelemetry::trace::TracerProvider;
69use opentelemetry_sdk::{metrics::SdkMeterProvider, trace::SdkTracerProvider};
70use tracing_subscriber::prelude::*;
71
72/// Returns a tracing layer configured with the given tracer provider and tracer name.
73///
74/// This function creates an OpenTelemetry tracing layer that can be used with a
75/// tracing subscriber to enable OpenTelemetry integration.
76///
77/// # Type Parameters
78///
79/// * `S` - The type of the subscriber that this layer will be applied to.
80///
81/// # Arguments
82///
83/// * `tracer_provider` - A reference to the tracer provider.
84/// * `tracer_name` - The name of the tracer to be used (must have static lifetime).
85///
86/// # Returns
87///
88/// A `tracing_opentelemetry::OpenTelemetryLayer` configured with the specified tracer.
89///
90/// # Examples
91///
92/// ```rust,no_run
93/// use tracing_subscriber::Registry;
94/// use lambda_otel_utils::{HttpTracerProviderBuilder, create_otel_tracing_layer};
95/// use tracing_subscriber::prelude::*;
96///
97/// # async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
98/// let tracer_provider = HttpTracerProviderBuilder::default()
99/// .with_stdout_client()
100/// .build()?;
101///
102/// let subscriber = Registry::default()
103/// .with(create_otel_tracing_layer(&tracer_provider, "my-service"));
104/// # Ok(())
105/// # }
106/// ```
107pub fn create_otel_tracing_layer<S>(
108 tracer_provider: &SdkTracerProvider,
109 tracer_name: &'static str,
110) -> tracing_opentelemetry::OpenTelemetryLayer<S, opentelemetry_sdk::trace::Tracer>
111where
112 S: tracing::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
113{
114 tracing_opentelemetry::OpenTelemetryLayer::new(tracer_provider.tracer(tracer_name))
115}
116
117/// Returns a metrics layer configured with the given meter provider.
118///
119/// This function creates an OpenTelemetry metrics layer that can be used with a
120/// tracing subscriber to enable metrics collection.
121///
122/// # Type Parameters
123///
124/// * `S` - The type of the subscriber that this layer will be applied to.
125///
126/// # Arguments
127///
128/// * `meter_provider` - A reference to the meter provider.
129///
130/// # Returns
131///
132/// A `tracing_opentelemetry::MetricsLayer` configured with the specified meter provider.
133///
134/// # Examples
135///
136/// ```rust,no_run
137/// use tracing_subscriber::Registry;
138/// use tracing_subscriber::prelude::*;
139/// use lambda_otel_utils::{HttpMeterProviderBuilder, create_otel_metrics_layer};
140/// use std::time::Duration;
141///
142/// # async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
143/// let meter_provider = HttpMeterProviderBuilder::default()
144/// .with_stdout_client()
145/// .with_meter_name("my-service")
146/// .with_export_interval(Duration::from_secs(30))
147/// .build()?;
148///
149/// let subscriber = Registry::default()
150/// .with(create_otel_metrics_layer(&meter_provider));
151/// # Ok(())
152/// # }
153/// ```
154pub fn create_otel_metrics_layer<S>(
155 meter_provider: &SdkMeterProvider,
156) -> tracing_opentelemetry::MetricsLayer<S>
157where
158 S: tracing::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
159{
160 tracing_opentelemetry::MetricsLayer::new(meter_provider.clone())
161}
162
163/// A builder for configuring and creating a tracing subscriber with OpenTelemetry support.
164///
165/// This builder provides a flexible way to configure various aspects of the tracing
166/// subscriber, including OpenTelemetry integration, environment filters, and JSON formatting.
167///
168/// # Examples
169///
170/// ```rust,no_run
171/// use lambda_otel_utils::{
172/// OpenTelemetrySubscriberBuilder,
173/// HttpTracerProviderBuilder,
174/// HttpMeterProviderBuilder
175/// };
176/// use std::time::Duration;
177///
178/// # async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
179/// let tracer_provider = HttpTracerProviderBuilder::default()
180/// .with_stdout_client()
181/// .build()?;
182///
183/// let meter_provider = HttpMeterProviderBuilder::default()
184/// .with_stdout_client()
185/// .with_meter_name("my-service")
186/// .with_export_interval(Duration::from_secs(30))
187/// .build()?;
188///
189/// OpenTelemetrySubscriberBuilder::new()
190/// .with_tracer_provider(tracer_provider)
191/// .with_meter_provider(meter_provider)
192/// .with_service_name("my-service")
193/// .with_env_filter(true)
194/// .with_json_format(true)
195/// .init()?;
196/// # Ok(())
197/// # }
198/// ```
199#[derive(Default)]
200pub struct OpenTelemetrySubscriberBuilder {
201 tracer_provider: Option<SdkTracerProvider>,
202 meter_provider: Option<SdkMeterProvider>,
203 service_name: Option<&'static str>,
204 with_env_filter: bool,
205 env_filter_string: Option<String>,
206 with_json_format: bool,
207}
208
209impl OpenTelemetrySubscriberBuilder {
210 /// Creates a new builder instance with default settings.
211 pub fn new() -> Self {
212 Self::default()
213 }
214
215 /// Sets the tracer provider for the subscriber.
216 pub fn with_tracer_provider(mut self, provider: SdkTracerProvider) -> Self {
217 self.tracer_provider = Some(provider);
218 self
219 }
220
221 /// Sets the meter provider for the subscriber.
222 pub fn with_meter_provider(mut self, provider: SdkMeterProvider) -> Self {
223 self.meter_provider = Some(provider);
224 self
225 }
226
227 /// Sets the service name for the subscriber.
228 pub fn with_service_name(mut self, name: &'static str) -> Self {
229 self.service_name = Some(name);
230 self
231 }
232
233 /// Enables or disables the environment filter.
234 ///
235 /// When enabled, the subscriber will use the `RUST_LOG` environment variable
236 /// to configure logging levels.
237 pub fn with_env_filter(mut self, enabled: bool) -> Self {
238 self.with_env_filter = enabled;
239 self
240 }
241
242 /// Sets a custom string for the environment filter.
243 ///
244 /// This allows specifying a filter directive string directly instead of using
245 /// the `RUST_LOG` environment variable.
246 ///
247 /// # Examples
248 ///
249 /// ```rust,no_run
250 /// use lambda_otel_utils::OpenTelemetrySubscriberBuilder;
251 ///
252 /// OpenTelemetrySubscriberBuilder::new()
253 /// .with_env_filter_string("info,my_crate=debug")
254 /// .init().unwrap();
255 /// ```
256 pub fn with_env_filter_string(mut self, filter: impl Into<String>) -> Self {
257 self.env_filter_string = Some(filter.into());
258 self
259 }
260
261 /// Enables or disables JSON formatting for log output.
262 pub fn with_json_format(mut self, enabled: bool) -> Self {
263 self.with_json_format = enabled;
264 self
265 }
266
267 /// Builds and sets the global default subscriber.
268 ///
269 /// # Returns
270 ///
271 /// Returns `Ok(())` if the subscriber was successfully set as the global default,
272 /// or an error if something went wrong.
273 pub fn init(self) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
274 let subscriber = tracing_subscriber::registry::Registry::default();
275
276 // Create optional layers
277 let otel_layer = self
278 .tracer_provider
279 .as_ref()
280 .zip(self.service_name)
281 .map(|(provider, name)| create_otel_tracing_layer(provider, name));
282
283 let metrics_layer = self.meter_provider.as_ref().map(create_otel_metrics_layer);
284
285 let env_filter = if self.with_env_filter {
286 if let Some(filter_string) = self.env_filter_string {
287 Some(tracing_subscriber::EnvFilter::new(filter_string))
288 } else {
289 Some(tracing_subscriber::EnvFilter::from_default_env())
290 }
291 } else {
292 None
293 };
294
295 let fmt_layer = if self.with_json_format {
296 Some(
297 tracing_subscriber::fmt::layer()
298 .json()
299 .with_target(false)
300 .without_time(),
301 )
302 } else {
303 None
304 };
305
306 // Build the subscriber by conditionally adding layers
307 let subscriber = subscriber
308 .with(otel_layer)
309 .with(metrics_layer)
310 .with(env_filter)
311 .with(fmt_layer);
312
313 // Set the subscriber as the default
314 tracing::subscriber::set_global_default(subscriber)?;
315
316 Ok(())
317 }
318}
319
320/// Convenience function to create and initialize a default OpenTelemetry subscriber.
321///
322/// This function provides a simple way to set up a tracing subscriber with OpenTelemetry
323/// support using sensible defaults.
324///
325/// # Arguments
326///
327/// * `tracer_provider` - The tracer provider to use.
328/// * `meter_provider` - The meter provider to use.
329/// * `service_name` - The name of the service (must have static lifetime).
330///
331/// # Returns
332///
333/// Returns `Ok(())` if the subscriber was successfully initialized, or an error if
334/// something went wrong.
335///
336/// # Examples
337///
338/// ```rust,no_run
339/// use lambda_otel_utils::{
340/// init_otel_subscriber,
341/// HttpTracerProviderBuilder,
342/// HttpMeterProviderBuilder
343/// };
344/// use std::time::Duration;
345///
346/// # async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
347/// let tracer_provider = HttpTracerProviderBuilder::default()
348/// .with_stdout_client()
349/// .build()?;
350///
351/// let meter_provider = HttpMeterProviderBuilder::default()
352/// .with_stdout_client()
353/// .with_meter_name("my-service")
354/// .with_export_interval(Duration::from_secs(30))
355/// .build()?;
356///
357/// init_otel_subscriber(tracer_provider, meter_provider, "my-service")?;
358/// # Ok(())
359/// # }
360/// ```
361pub fn init_otel_subscriber(
362 tracer_provider: SdkTracerProvider,
363 meter_provider: SdkMeterProvider,
364 service_name: &'static str,
365) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
366 OpenTelemetrySubscriberBuilder::new()
367 .with_tracer_provider(tracer_provider)
368 .with_meter_provider(meter_provider)
369 .with_service_name(service_name)
370 .init()
371}