lambda_otel_utils/
http_tracer_provider.rs

1//! This module provides utilities for configuring and building an OpenTelemetry TracerProvider
2//! specifically tailored for use in AWS Lambda environments.
3//!
4//! It includes:
5//! - `HttpTracerProviderBuilder`: A builder struct for configuring and initializing a TracerProvider.
6//! - `get_lambda_resource`: A function to create a Resource with Lambda-specific attributes.
7//!
8//! The module supports various configuration options, including:
9//! - Custom HTTP clients for exporting traces
10//! - Enabling/disabling logging layers
11//! - Setting custom tracer names
12//! - Configuring propagators and ID generators
13//! - Choosing between simple and batch exporters
14//!
15//! It also respects environment variables for certain configurations, such as the span processor type
16//! and the OTLP exporter protocol.
17//!
18//! # Examples
19//!
20//! ```
21//! use lambda_otel_utils::HttpTracerProviderBuilder;
22//! use opentelemetry_sdk::trace::{TracerProvider, Tracer};
23//!
24//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
25//! let tracer_provider: TracerProvider = HttpTracerProviderBuilder::default()
26//!     .with_stdout_client()
27//!     .with_default_text_map_propagator()
28//!     .with_default_id_generator()
29//!     .enable_global(true)
30//!     .build()?;
31//! # Ok(())
32//! # }
33//! ```
34
35use opentelemetry::propagation::TextMapPropagator;
36use opentelemetry_aws::trace::{XrayIdGenerator, XrayPropagator};
37use opentelemetry_http::HttpClient;
38use opentelemetry_otlp::{WithExportConfig, WithHttpConfig};
39use opentelemetry_sdk::{
40    propagation::TraceContextPropagator,
41    trace::{IdGenerator, RandomIdGenerator, SdkTracerProvider},
42};
43use otlp_stdout_client::StdoutClient;
44use std::{env, fmt::Debug};
45use thiserror::Error;
46
47#[derive(Debug)]
48enum ExporterType {
49    Simple,
50    Batch,
51}
52
53#[derive(Debug, Error)]
54pub enum BuilderError {
55    #[error("Failed to build exporter: {0}")]
56    ExporterBuildError(#[from] opentelemetry::trace::TraceError),
57}
58
59/// A type-safe builder for configuring and initializing a TracerProvider.
60pub struct HttpTracerProviderBuilder<
61    C: HttpClient = StdoutClient,
62    I: IdGenerator = RandomIdGenerator,
63    P: TextMapPropagator = TraceContextPropagator,
64> {
65    client: C,
66    install_global: bool,
67    id_generator: I,
68    propagators: Vec<Box<dyn TextMapPropagator + Send + Sync>>,
69    exporter_type: ExporterType,
70    _propagator_type: std::marker::PhantomData<P>,
71}
72
73/// Provides a default implementation for `HttpTracerProviderBuilder`.
74///
75/// This implementation creates a new `HttpTracerProviderBuilder` with default settings
76/// by calling the `new()` method.
77///
78/// # Examples
79///
80/// ```
81/// use lambda_otel_utils::HttpTracerProviderBuilder;
82///
83/// let default_builder = HttpTracerProviderBuilder::default();
84/// ```
85impl Default
86    for HttpTracerProviderBuilder<StdoutClient, RandomIdGenerator, TraceContextPropagator>
87{
88    fn default() -> Self {
89        Self::new()
90    }
91}
92
93impl<C, I, P> HttpTracerProviderBuilder<C, I, P>
94where
95    C: HttpClient + 'static,
96    I: IdGenerator + Send + Sync + 'static,
97    P: TextMapPropagator + Send + Sync + 'static,
98{
99    /// Creates a new `HttpTracerProviderBuilder` with default settings.
100    ///
101    /// The default exporter type is determined by the `LAMBDA_OTEL_SPAN_PROCESSOR` environment variable:
102    /// - "batch" - Uses batch span processor
103    /// - "simple" - Uses simple span processor (default)
104    /// - Any other value will default to simple span processor with a warning
105    ///
106    /// # Examples
107    ///
108    /// ```
109    /// use lambda_otel_utils::HttpTracerProviderBuilder;
110    /// use otlp_stdout_client::StdoutClient;
111    /// use opentelemetry_sdk::propagation::TraceContextPropagator;
112    /// use opentelemetry_sdk::trace::RandomIdGenerator;
113    ///
114    /// let builder = HttpTracerProviderBuilder::default();
115    /// ```
116    pub fn new(
117    ) -> HttpTracerProviderBuilder<StdoutClient, RandomIdGenerator, TraceContextPropagator> {
118        let exporter_type = match env::var("LAMBDA_OTEL_SPAN_PROCESSOR")
119            .unwrap_or_else(|_| "simple".to_string())
120            .to_lowercase()
121            .as_str()
122        {
123            "batch" => ExporterType::Batch,
124            "simple" => ExporterType::Simple,
125            invalid => {
126                eprintln!(
127                    "Warning: Invalid LAMBDA_OTEL_SPAN_PROCESSOR value '{}'. Defaulting to Simple.",
128                    invalid
129                );
130                ExporterType::Simple
131            }
132        };
133
134        HttpTracerProviderBuilder {
135            client: StdoutClient::new(),
136            install_global: false,
137            id_generator: RandomIdGenerator::default(),
138            propagators: Vec::new(),
139            exporter_type,
140            _propagator_type: std::marker::PhantomData,
141        }
142    }
143
144    /// Configures the builder to use a stdout client for exporting traces.
145    ///
146    /// # Examples
147    ///
148    /// ```
149    /// use lambda_otel_utils::HttpTracerProviderBuilder;
150    ///
151    /// let builder = HttpTracerProviderBuilder::default()
152    ///     .with_stdout_client();
153    /// ```
154    pub fn with_stdout_client(self) -> HttpTracerProviderBuilder<StdoutClient, I, P> {
155        HttpTracerProviderBuilder {
156            client: StdoutClient::new(),
157            install_global: self.install_global,
158            id_generator: self.id_generator,
159            propagators: self.propagators,
160            exporter_type: self.exporter_type,
161            _propagator_type: std::marker::PhantomData,
162        }
163    }
164
165    /// Sets a custom HTTP client for exporting traces.
166    ///
167    /// # Examples
168    ///
169    /// ```no_run
170    /// use lambda_otel_utils::HttpTracerProviderBuilder;
171    /// use reqwest::Client;
172    /// use opentelemetry_sdk::propagation::TraceContextPropagator;
173    /// use opentelemetry_sdk::trace::RandomIdGenerator;
174    ///
175    /// let client = Client::new();
176    /// let builder = HttpTracerProviderBuilder::default()
177    ///     .with_http_client(client);
178    /// ```
179    pub fn with_http_client<NewC>(self, client: NewC) -> HttpTracerProviderBuilder<NewC, I, P>
180    where
181        NewC: HttpClient + 'static,
182    {
183        HttpTracerProviderBuilder {
184            client,
185            install_global: self.install_global,
186            id_generator: self.id_generator,
187            propagators: self.propagators,
188            exporter_type: self.exporter_type,
189            _propagator_type: std::marker::PhantomData,
190        }
191    }
192
193    /// Adds a custom text map propagator.
194    ///
195    /// # Examples
196    ///
197    /// ```
198    /// use lambda_otel_utils::HttpTracerProviderBuilder;
199    /// use opentelemetry_sdk::propagation::TraceContextPropagator;
200    ///
201    /// let builder = HttpTracerProviderBuilder::default()
202    ///     .with_text_map_propagator(TraceContextPropagator::new());
203    /// ```
204    pub fn with_text_map_propagator<NewP>(
205        mut self,
206        propagator: NewP,
207    ) -> HttpTracerProviderBuilder<C, I, NewP>
208    where
209        NewP: TextMapPropagator + Send + Sync + 'static,
210    {
211        self.propagators.push(Box::new(propagator));
212        HttpTracerProviderBuilder {
213            client: self.client,
214            install_global: self.install_global,
215            id_generator: self.id_generator,
216            propagators: self.propagators,
217            exporter_type: self.exporter_type,
218            _propagator_type: std::marker::PhantomData,
219        }
220    }
221
222    /// Adds the default text map propagator (TraceContextPropagator).
223    ///
224    /// # Examples
225    ///
226    /// ```
227    /// use lambda_otel_utils::HttpTracerProviderBuilder;
228    ///
229    /// let builder = HttpTracerProviderBuilder::default()
230    ///     .with_default_text_map_propagator();
231    /// ```
232    pub fn with_default_text_map_propagator(mut self) -> Self {
233        self.propagators
234            .push(Box::new(TraceContextPropagator::new()));
235        self
236    }
237
238    /// Adds the XRay text map propagator.
239    ///
240    /// # Examples
241    ///
242    /// ```
243    /// use lambda_otel_utils::HttpTracerProviderBuilder;
244    ///
245    /// let builder = HttpTracerProviderBuilder::default()
246    ///     .with_xray_text_map_propagator();
247    /// ```
248    pub fn with_xray_text_map_propagator(mut self) -> Self {
249        self.propagators.push(Box::new(XrayPropagator::new()));
250        self
251    }
252
253    /// Sets a custom ID generator.
254    ///
255    /// # Examples
256    ///
257    /// ```
258    /// use lambda_otel_utils::HttpTracerProviderBuilder;
259    /// use opentelemetry_sdk::trace::RandomIdGenerator;
260    ///
261    /// let builder = HttpTracerProviderBuilder::default()
262    ///     .with_id_generator(RandomIdGenerator::default());
263    /// ```
264    pub fn with_id_generator<NewI>(
265        self,
266        id_generator: NewI,
267    ) -> HttpTracerProviderBuilder<C, NewI, P>
268    where
269        NewI: IdGenerator + Send + Sync + 'static,
270    {
271        HttpTracerProviderBuilder {
272            client: self.client,
273            install_global: self.install_global,
274            id_generator,
275            propagators: self.propagators,
276            exporter_type: self.exporter_type,
277            _propagator_type: std::marker::PhantomData,
278        }
279    }
280
281    /// Sets the default ID generator (RandomIdGenerator).
282    ///
283    /// # Examples
284    ///
285    /// ```
286    /// use lambda_otel_utils::HttpTracerProviderBuilder;
287    ///
288    /// let builder = HttpTracerProviderBuilder::default()
289    ///     .with_default_id_generator();
290    /// ```
291    pub fn with_default_id_generator(self) -> HttpTracerProviderBuilder<C, RandomIdGenerator, P> {
292        self.with_id_generator(RandomIdGenerator::default())
293    }
294
295    /// Sets the XRay ID generator.
296    ///
297    /// # Examples
298    ///
299    /// ```
300    /// use lambda_otel_utils::HttpTracerProviderBuilder;
301    ///
302    /// let builder = HttpTracerProviderBuilder::default()
303    ///     .with_xray_id_generator();
304    /// ```
305    pub fn with_xray_id_generator(self) -> HttpTracerProviderBuilder<C, XrayIdGenerator, P> {
306        self.with_id_generator(XrayIdGenerator::default())
307    }
308
309    /// Configures the builder to use a simple exporter.
310    ///
311    /// # Examples
312    ///
313    /// ```
314    /// use lambda_otel_utils::HttpTracerProviderBuilder;
315    ///
316    /// let builder = HttpTracerProviderBuilder::default()
317    ///     .with_simple_exporter();
318    /// ```
319    pub fn with_simple_exporter(mut self) -> Self {
320        self.exporter_type = ExporterType::Simple;
321        self
322    }
323
324    /// Configures the builder to use a batch exporter.
325    ///
326    /// # Examples
327    ///
328    /// ```
329    /// use lambda_otel_utils::HttpTracerProviderBuilder;
330    ///
331    /// let builder = HttpTracerProviderBuilder::default()
332    ///     .with_batch_exporter();
333    /// ```
334    pub fn with_batch_exporter(mut self) -> Self {
335        self.exporter_type = ExporterType::Batch;
336        self
337    }
338
339    /// Enables or disables global installation of the tracer provider.
340    ///
341    /// # Examples
342    ///
343    /// ```
344    /// use lambda_otel_utils::HttpTracerProviderBuilder;
345    ///
346    /// let builder = HttpTracerProviderBuilder::default()
347    ///     .enable_global(true);
348    /// ```
349    pub fn enable_global(mut self, set_global: bool) -> Self {
350        self.install_global = set_global;
351        self
352    }
353
354    /// Builds the TracerProvider with the configured settings.
355    ///
356    /// # Errors
357    ///
358    /// Returns a `BuilderError` if the exporter fails to build
359    pub fn build(self) -> Result<SdkTracerProvider, BuilderError> {
360        let exporter = opentelemetry_otlp::SpanExporter::builder()
361            .with_http()
362            .with_protocol(crate::protocol::get_protocol())
363            .with_http_client(self.client)
364            .build()
365            .map_err(BuilderError::ExporterBuildError)?;
366
367        let builder = match self.exporter_type {
368            ExporterType::Simple => SdkTracerProvider::builder().with_simple_exporter(exporter),
369            ExporterType::Batch => SdkTracerProvider::builder().with_batch_exporter(exporter),
370        };
371
372        let tracer_provider = builder
373            .with_resource(crate::resource::get_lambda_resource())
374            .with_id_generator(self.id_generator)
375            .build();
376
377        if !self.propagators.is_empty() {
378            let composite_propagator =
379                opentelemetry::propagation::TextMapCompositePropagator::new(self.propagators);
380            opentelemetry::global::set_text_map_propagator(composite_propagator);
381        }
382
383        if self.install_global {
384            opentelemetry::global::set_tracer_provider(tracer_provider.clone());
385        }
386
387        Ok(tracer_provider)
388    }
389}
390
391#[cfg(test)]
392mod tests {
393    use super::*;
394    use opentelemetry::trace::Span;
395    use opentelemetry::trace::Tracer;
396    use opentelemetry::trace::TracerProvider;
397
398    #[test]
399    fn test_default_builder() {
400        let builder = HttpTracerProviderBuilder::default();
401        assert!(!builder.install_global);
402        assert!(matches!(builder.exporter_type, ExporterType::Simple));
403    }
404
405    #[tokio::test]
406    async fn test_successful_build() -> Result<(), BuilderError> {
407        let provider = HttpTracerProviderBuilder::default().build()?;
408
409        let tracer = provider.tracer("test");
410        let span = tracer.span_builder("test_span").start(&tracer);
411        assert!(span.is_recording());
412        Ok(())
413    }
414
415    #[tokio::test]
416    async fn test_multiple_propagators() -> Result<(), BuilderError> {
417        let provider = HttpTracerProviderBuilder::default()
418            .with_text_map_propagator(TraceContextPropagator::new())
419            .with_text_map_propagator(XrayPropagator::new())
420            .build()?;
421
422        let tracer = provider.tracer("test");
423        let span = tracer.span_builder("test_span").start(&tracer);
424        assert!(span.is_recording());
425        Ok(())
426    }
427
428    #[tokio::test]
429    async fn test_custom_id_generator() -> Result<(), BuilderError> {
430        let provider = HttpTracerProviderBuilder::default()
431            .with_id_generator(XrayIdGenerator::default())
432            .build()?;
433
434        let tracer = provider.tracer("test");
435        let span = tracer.span_builder("test_span").start(&tracer);
436        assert!(span.is_recording());
437        Ok(())
438    }
439}