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}