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: {}, using config or default",
109 value
110 ));
111 config_mode.unwrap_or(ProcessorMode::Sync)
112 }
113 Err(_) => {
114 // No environment variable set, use config mode or default
115 config_mode.unwrap_or(ProcessorMode::Sync)
116 }
117 };
118
119 // Log the resolved mode
120 LOGGER.debug(format!(
121 "ProcessorMode.resolve: using {} processor mode",
122 result
123 ));
124
125 result
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use serial_test::serial;
133 use std::env;
134
135 // Helper function to set processor mode environment variable
136 fn set_processor_mode(value: Option<&str>) {
137 match value {
138 Some(v) => env::set_var(constants::env_vars::PROCESSOR_MODE, v),
139 None => env::remove_var(constants::env_vars::PROCESSOR_MODE),
140 }
141 }
142
143 #[test]
144 #[serial]
145 fn test_processor_mode_env_only() {
146 // Default to sync mode (env var not set)
147 set_processor_mode(None);
148 assert!(matches!(ProcessorMode::resolve(None), ProcessorMode::Sync));
149
150 // Explicit mode tests
151 let test_cases = [
152 ("sync", ProcessorMode::Sync),
153 ("async", ProcessorMode::Async),
154 ("finalize", ProcessorMode::Finalize),
155 ("invalid", ProcessorMode::Sync), // Invalid mode defaults to sync
156 ];
157
158 for (env_value, expected_mode) in test_cases {
159 set_processor_mode(Some(env_value));
160 let result = ProcessorMode::resolve(None);
161 assert_eq!(result, expected_mode, "Failed for env value: {}", env_value);
162 }
163 }
164
165 #[test]
166 #[serial]
167 fn test_processor_mode_resolve() {
168 // Test environment variable precedence over config
169 let precedence_tests = [
170 // (env_value, config_value, expected)
171 (
172 Some("sync"),
173 Some(ProcessorMode::Async),
174 ProcessorMode::Sync,
175 ),
176 (
177 Some("async"),
178 Some(ProcessorMode::Sync),
179 ProcessorMode::Async,
180 ),
181 (
182 Some("invalid"),
183 Some(ProcessorMode::Finalize),
184 ProcessorMode::Finalize,
185 ),
186 (None, Some(ProcessorMode::Async), ProcessorMode::Async),
187 (None, None, ProcessorMode::Sync),
188 ];
189
190 for (env_value, config_mode, expected) in precedence_tests {
191 set_processor_mode(env_value);
192 let result = ProcessorMode::resolve(config_mode.clone());
193 assert_eq!(
194 result, expected,
195 "Failed for env: {:?}, config: {:?}",
196 env_value, config_mode
197 );
198 }
199 }
200}