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}