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}