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}