lambda_otel_lite/mode.rs
1use crate::constants;
2use crate::logger::Logger;
3
4/// Module-specific logger
5static LOGGER: Logger = Logger::const_new("mode");
6
7use std::{env, fmt};
8
9/// Controls how spans are processed and exported.
10///
11/// This enum determines when and how OpenTelemetry spans are flushed from the buffer
12/// to the configured exporter. Each mode offers different tradeoffs between latency,
13/// reliability, and flexibility.
14///
15/// # Modes
16///
17/// - `Sync`: Immediate flush in handler thread
18/// - Spans are flushed before handler returns
19/// - Direct export without extension coordination
20/// - May be more efficient for small payloads and low memory configurations
21/// - Guarantees span delivery before response
22///
23/// - `Async`: Flush via Lambda extension
24/// - Spans are flushed after handler returns
25/// - Requires coordination with extension process
26/// - Additional overhead from IPC with extension
27/// - Provides retry capabilities through extension
28///
29/// - `Finalize`: Delegated to processor
30/// - Spans handled by configured processor
31/// - Compatible with BatchSpanProcessor
32/// - Best for custom export strategies
33/// - Full control over export timing
34///
35/// # Configuration
36///
37/// The mode can be configured in two ways:
38///
39/// 1. Using the `LAMBDA_EXTENSION_SPAN_PROCESSOR_MODE` environment variable:
40/// - "sync" for Sync mode (default)
41/// - "async" for Async mode
42/// - "finalize" for Finalize mode
43///
44/// 2. Programmatically through `TelemetryConfig`:
45/// ```no_run
46/// use lambda_otel_lite::{ProcessorMode, TelemetryConfig};
47///
48/// let config = TelemetryConfig::builder()
49/// .processor_mode(ProcessorMode::Async)
50/// .build();
51/// ```
52///
53/// The environment variable takes precedence over programmatic configuration.
54///
55/// # Example
56///
57/// ```no_run
58/// use lambda_otel_lite::ProcessorMode;
59/// use std::env;
60///
61/// // Set mode via environment variable
62/// env::set_var("LAMBDA_EXTENSION_SPAN_PROCESSOR_MODE", "async");
63///
64/// // Get mode from environment
65/// let mode = ProcessorMode::resolve(None);
66/// assert!(matches!(mode, ProcessorMode::Async));
67///
68/// // Programmatically provide a default but let environment override it
69/// let mode = ProcessorMode::resolve(Some(ProcessorMode::Sync));
70/// assert!(matches!(mode, ProcessorMode::Async)); // Environment still takes precedence
71/// ```
72#[derive(Debug, Clone, PartialEq)]
73pub enum ProcessorMode {
74 /// Synchronous flush in handler thread. Best for development and debugging.
75 Sync,
76 /// Asynchronous flush via extension. Best for production use to minimize latency.
77 Async,
78 /// Let processor handle flushing. Best with BatchSpanProcessor for custom export strategies.
79 Finalize,
80}
81
82impl fmt::Display for ProcessorMode {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 match self {
85 ProcessorMode::Sync => write!(f, "sync"),
86 ProcessorMode::Async => write!(f, "async"),
87 ProcessorMode::Finalize => write!(f, "finalize"),
88 }
89 }
90}
91
92impl ProcessorMode {
93 /// Resolve processor mode from environment variable or provided configuration.
94 ///
95 /// If LAMBDA_EXTENSION_SPAN_PROCESSOR_MODE environment variable is set, it takes precedence.
96 /// Otherwise, uses the provided mode or defaults to Sync mode if neither is set.
97 pub fn resolve(config_mode: Option<ProcessorMode>) -> Self {
98 // Environment variable takes precedence if set
99 let result = match env::var(constants::env_vars::PROCESSOR_MODE)
100 .map(|s| s.to_lowercase())
101 .as_deref()
102 {
103 Ok("sync") => ProcessorMode::Sync,
104 Ok("async") => ProcessorMode::Async,
105 Ok("finalize") => ProcessorMode::Finalize,
106 Ok(value) => {
107 LOGGER.warn(format!(
108 "ProcessorMode.resolve: invalid processor mode in env: {value}, using config or default"
109 ));
110 config_mode.unwrap_or(ProcessorMode::Sync)
111 }
112 Err(_) => {
113 // No environment variable set, use config mode or default
114 config_mode.unwrap_or(ProcessorMode::Sync)
115 }
116 };
117
118 // Log the resolved mode
119 LOGGER.debug(format!(
120 "ProcessorMode.resolve: using {result} processor mode"
121 ));
122
123 result
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use serial_test::serial;
131 use std::env;
132
133 // Helper function to set processor mode environment variable
134 fn set_processor_mode(value: Option<&str>) {
135 match value {
136 Some(v) => env::set_var(constants::env_vars::PROCESSOR_MODE, v),
137 None => env::remove_var(constants::env_vars::PROCESSOR_MODE),
138 }
139 }
140
141 #[test]
142 #[serial]
143 fn test_processor_mode_env_only() {
144 // Default to sync mode (env var not set)
145 set_processor_mode(None);
146 assert!(matches!(ProcessorMode::resolve(None), ProcessorMode::Sync));
147
148 // Explicit mode tests
149 let test_cases = [
150 ("sync", ProcessorMode::Sync),
151 ("async", ProcessorMode::Async),
152 ("finalize", ProcessorMode::Finalize),
153 ("invalid", ProcessorMode::Sync), // Invalid mode defaults to sync
154 ];
155
156 for (env_value, expected_mode) in test_cases {
157 set_processor_mode(Some(env_value));
158 let result = ProcessorMode::resolve(None);
159 assert_eq!(result, expected_mode, "Failed for env value: {env_value}");
160 }
161 }
162
163 #[test]
164 #[serial]
165 fn test_processor_mode_resolve() {
166 // Test environment variable precedence over config
167 let precedence_tests = [
168 // (env_value, config_value, expected)
169 (
170 Some("sync"),
171 Some(ProcessorMode::Async),
172 ProcessorMode::Sync,
173 ),
174 (
175 Some("async"),
176 Some(ProcessorMode::Sync),
177 ProcessorMode::Async,
178 ),
179 (
180 Some("invalid"),
181 Some(ProcessorMode::Finalize),
182 ProcessorMode::Finalize,
183 ),
184 (None, Some(ProcessorMode::Async), ProcessorMode::Async),
185 (None, None, ProcessorMode::Sync),
186 ];
187
188 for (env_value, config_mode, expected) in precedence_tests {
189 set_processor_mode(env_value);
190 let result = ProcessorMode::resolve(config_mode.clone());
191 assert_eq!(
192 result, expected,
193 "Failed for env: {env_value:?}, config: {config_mode:?}"
194 );
195 }
196 }
197}