log_args_runtime/
lib.rs

1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::sync::{Arc, Mutex};
4
5// Global context store for cross-boundary persistence
6static GLOBAL_CONTEXT: std::sync::LazyLock<Arc<Mutex<HashMap<String, String>>>> = 
7    std::sync::LazyLock::new(|| Arc::new(Mutex::new(HashMap::new())));
8
9/// Set global context that persists across all boundaries
10pub fn set_global_context(key: &str, value: &str) {
11    if let Ok(mut global) = GLOBAL_CONTEXT.lock() {
12        global.insert(key.to_string(), value.to_string());
13    }
14}
15
16/// Get global context for cross-boundary persistence
17pub fn get_global_context() -> Option<HashMap<String, String>> {
18    if let Ok(global) = GLOBAL_CONTEXT.lock() {
19        if !global.is_empty() {
20            return Some(global.clone());
21        }
22    }
23    None
24}
25
26// Thread-local storage for context stacks
27thread_local! {
28    static CONTEXT_STACK: RefCell<Vec<HashMap<String, String>>> = RefCell::new(Vec::new());
29    static ASYNC_CONTEXT_STACK: RefCell<Vec<HashMap<String, String>>> = RefCell::new(Vec::new());
30}
31
32/// Guard for synchronous context that automatically pops on drop
33#[doc(hidden)]
34pub struct ContextGuard;
35
36impl Drop for ContextGuard {
37    fn drop(&mut self) {
38        CONTEXT_STACK.with(|stack| {
39            stack.borrow_mut().pop();
40        });
41    }
42}
43
44// Function to get a context value from the current span context
45pub fn get_context_value(key: &str) -> Option<String> {
46    // First, try async context stack
47    if let Ok(stack) = ASYNC_CONTEXT_STACK.try_with(|stack| stack.borrow().clone()) {
48        for context_map in stack.iter().rev() {
49            if let Some(value) = context_map.get(key) {
50                return Some(value.clone());
51            }
52        }
53    }
54    
55    // Then try sync context stack
56    let result = CONTEXT_STACK.with(|stack| {
57        let stack = stack.borrow();
58        for context_map in stack.iter().rev() {
59            if let Some(value) = context_map.get(key) {
60                return Some(value.clone());
61            }
62        }
63        None
64    });
65    
66    if result.is_some() {
67        return result;
68    }
69    
70    // Finally, try global context store for cross-boundary persistence
71    if let Ok(global) = GLOBAL_CONTEXT.lock() {
72        if let Some(value) = global.get(key) {
73            return Some(value.clone());
74        }
75    }
76    
77    None
78}
79
80/// Get current synchronous context
81#[doc(hidden)]
82pub fn get_context() -> HashMap<String, String> {
83    CONTEXT_STACK.with(|stack| {
84        stack
85            .borrow()
86            .iter()
87            .fold(HashMap::new(), |mut acc, context| {
88                acc.extend(context.clone());
89                acc
90            })
91    })
92}
93
94#[doc(hidden)]
95pub fn get_async_context() -> HashMap<String, String> {
96    ASYNC_CONTEXT_STACK
97        .try_with(|stack| {
98            stack
99                .borrow()
100                .iter()
101                .fold(HashMap::new(), |mut acc, context| {
102                    acc.extend(context.clone());
103                    acc
104                })
105        })
106        .unwrap_or_default()
107}
108
109#[doc(hidden)]
110pub fn get_current_async_stack() -> Vec<HashMap<String, String>> {
111    ASYNC_CONTEXT_STACK
112        .try_with(|stack| stack.borrow().clone())
113        .unwrap_or_else(|_| vec![HashMap::new()])
114}
115
116/// Push context for synchronous functions with span
117#[doc(hidden)]
118pub fn push_context(context: HashMap<String, String>) -> ContextGuard {
119    CONTEXT_STACK.with(|stack| {
120        stack.borrow_mut().push(context);
121    });
122    ContextGuard
123}
124
125/// Push context for asynchronous functions with span
126#[doc(hidden)]
127pub fn push_async_context(context: HashMap<String, String>) -> AsyncContextGuard {
128    ASYNC_CONTEXT_STACK.with(|stack| {
129        stack.borrow_mut().push(context);
130    });
131    AsyncContextGuard
132}
133
134/// Guard for async context that automatically pops on drop
135pub struct AsyncContextGuard;
136
137impl Drop for AsyncContextGuard {
138    fn drop(&mut self) {
139        ASYNC_CONTEXT_STACK.with(|stack| {
140            stack.borrow_mut().pop();
141        });
142    }
143}
144
145// Helper macro to dynamically add context fields to log statements
146// This macro is now completely dynamic with no hardcoded field names
147#[macro_export]
148macro_rules! add_context_fields {
149    ($log_macro:path, $ctx:expr, $($args:tt)*) => {
150        // Completely dynamic approach - no hardcoded field names
151        // Create field tokens for all context fields dynamically
152        let mut field_tokens = Vec::new();
153
154        // Add all context fields dynamically without hardcoding any field names
155        for (key, value) in $ctx.iter() {
156            // Create a field token for any field name
157            let field_token = if key.contains('.') {
158                // Handle dotted field names (like "user.id")
159                format!("\"{key}\" = %{value}", key = key, value = value)
160            } else {
161                // Handle regular field names
162                format!("{key} = %{value}", key = key, value = value)
163            };
164            field_tokens.push(field_token);
165        }
166
167        // Note: This approach still has Rust macro limitations
168        // The field tokens can't be directly injected into the macro call
169        // This is kept for potential future use or alternative implementations
170    };
171}
172
173#[macro_export]
174macro_rules! log_with_context {
175    ($log_macro:path, $context:expr, $($args:tt)*) => {
176        {
177            let ctx = $context;
178            if ctx.is_empty() {
179                $log_macro!($($args)*);
180            } else {
181                // Completely dynamic approach - NO hardcoded field names whatsoever
182                // Since Rust macros cannot dynamically generate field names at compile time,
183                // we use a runtime approach that works with any field names
184                
185                // Create individual log fields dynamically using tracing's structured logging
186                // This approach works with any field names without hardcoding
187                
188                // Create a span with dynamic fields and log within it
189                let span = ::tracing::info_span!(
190                    "context",
191                    // We can't dynamically create field names in the span macro
192                    // So we'll use the record API instead
193                );
194                
195                // Record all context fields dynamically
196                for (key, value) in ctx.iter() {
197                    span.record(key.as_str(), &::tracing::field::display(value));
198                }
199                
200                // Execute the log within the context span
201                let _enter = span.enter();
202                $log_macro!($($args)*);
203                
204                // Alternative: if span approach doesn't work, fall back to context string
205                // This ensures we never lose context information
206                drop(_enter);
207                drop(span);
208                
209                // Fallback: create a single context field with all data
210                let mut context_fields = Vec::new();
211                for (key, value) in ctx.iter() {
212                    context_fields.push(format!("{}={}", key, value));
213                }
214                if !context_fields.is_empty() {
215                    let context_data = context_fields.join(" ");
216                    // This logs context as a single structured field
217                    // Individual fields will be available through the span above
218                    $log_macro!(context = %context_data, $($args)*);
219                } else {
220                    $log_macro!($($args)*);
221                }
222            }
223        }
224    };
225}
226
227/// Global context-aware logging macros that inherit parent context
228/// These can be used in any function to automatically include context from parent functions with span
229#[macro_export]
230macro_rules! info {
231    ($($t:tt)*) => {
232        $crate::log_with_context!(::tracing::info, $crate::get_context(), $($t)*);
233    };
234}
235
236#[macro_export]
237macro_rules! warn {
238    ($($t:tt)*) => {
239        $crate::log_with_context!(::tracing::warn, $crate::get_context(), $($t)*);
240    };
241}
242
243#[macro_export]
244macro_rules! error {
245    ($($t:tt)*) => {
246        $crate::log_with_context!(::tracing::error, $crate::get_context(), $($t)*);
247    };
248}
249
250#[macro_export]
251macro_rules! debug {
252    ($($t:tt)*) => {
253        $crate::log_with_context!(::tracing::debug, $crate::get_context(), $($t)*);
254    };
255}
256
257#[macro_export]
258macro_rules! trace {
259    ($($t:tt)*) => {
260        $crate::log_with_context!(::tracing::trace, $crate::get_context(), $($t)*);
261    };
262}
263
264/// Automatically capture and preserve current context for function execution
265/// This ensures context is maintained across function boundaries without user intervention
266pub fn auto_capture_context() -> ContextGuard {
267    let current_context = get_context();
268    
269    // Push to both async and sync stacks to ensure maximum compatibility
270    let _async_guard = push_async_context(current_context.clone());
271    let _sync_guard = push_context(current_context);
272    
273    // Return the existing ContextGuard (empty struct)
274    ContextGuard
275}
276
277/// Capture current context and store it globally for cross-boundary persistence
278/// This function is automatically called by the macro to ensure context is preserved
279pub fn capture_context() -> ContextGuard {
280    let current_context = get_context();
281    
282    // Store each context field globally for cross-boundary access
283    for (key, value) in &current_context {
284        set_global_context(key, value);
285    }
286    
287    // Also push to context stacks for immediate access
288    let _async_guard = push_async_context(current_context.clone());
289    let _sync_guard = push_context(current_context);
290    
291    // Return the existing ContextGuard (empty struct)
292    ContextGuard
293}
294
295/// Helper function to capture context for closure boundaries
296/// This captures the current context and returns a closure that restores it
297/// Usage: let captured = with_context_capture1(|arg| { /* your code */ });
298pub fn with_context_capture1<F, A, R>(f: F) -> impl FnOnce(A) -> R
299where
300    F: FnOnce(A) -> R,
301{
302    let captured_context = get_context();
303    
304    move |a| {
305        // Use async context for better propagation and ensure it persists across function calls
306        let _guard = push_async_context(captured_context.clone());
307        
308        // Also push to sync context for better compatibility
309        let _sync_guard = push_context(captured_context);
310        
311        f(a)
312    }
313}
314
315/// Helper function for automatic closure context capture with two arguments
316pub fn with_context_capture2<F, A, B, R>(f: F) -> impl FnOnce(A, B) -> R
317where
318    F: FnOnce(A, B) -> R,
319{
320    let captured_context = get_context();
321    
322    move |a, b| {
323        let _guard = push_context(captured_context);
324        f(a, b)
325    }
326}
327
328/// Helper function for automatic closure context capture with three arguments
329pub fn with_context_capture3<F, A, B, C, R>(f: F) -> impl FnOnce(A, B, C) -> R
330where
331    F: FnOnce(A, B, C) -> R,
332{
333    let captured_context = get_context();
334    
335    move |a, b, c| {
336        let _guard = push_context(captured_context);
337        f(a, b, c)
338    }
339}
340
341/// Get inherited context as a formatted string for automatic span propagation
342/// This function retrieves all context fields from the current span context
343/// and formats them as a string for logging
344pub fn get_inherited_context_string() -> String {
345    let mut context_parts = Vec::new();
346    
347    // First, try to get context from tracing span (most reliable for cross-boundary propagation)
348    let current_span = tracing::Span::current();
349    if !current_span.is_none() {
350        // Try to extract fields from the current span
351        // This works across async boundaries when spans are properly propagated
352        // Note: Direct span field extraction is complex, so we rely on other methods
353    }
354    
355    // Try async context stack (most likely to have the context)
356    if let Ok(stack) = ASYNC_CONTEXT_STACK.try_with(|stack| stack.borrow().clone()) {
357        // Search through all contexts in the stack, not just the most recent
358        for context_map in stack.iter().rev() {
359            for (key, value) in context_map {
360                // Skip function name to avoid duplication
361                if key != "function" && !context_parts.iter().any(|p: &String| p.starts_with(&format!("{}=", key))) {
362                    context_parts.push(format!("{}={}", key, value));
363                }
364            }
365        }
366    }
367    
368    // Also try sync context stack and merge results
369    CONTEXT_STACK.with(|stack| {
370        let stack = stack.borrow();
371        for context_map in stack.iter().rev() {
372            for (key, value) in context_map {
373                // Skip function name and avoid duplicates
374                if key != "function" && !context_parts.iter().any(|p: &String| p.starts_with(&format!("{}=", key))) {
375                    context_parts.push(format!("{}={}", key, value));
376                }
377            }
378        }
379    });
380    
381    // If still no context, try global context store (for cross-boundary persistence)
382    if context_parts.is_empty() {
383        if let Some(global_context) = get_global_context() {
384            for (key, value) in global_context {
385                if key != "function" {
386                    context_parts.push(format!("{}={}", key, value));
387                }
388            }
389        }
390    }
391    
392    if context_parts.is_empty() {
393        "<no_context>".to_string()
394    } else {
395        context_parts.join(",")
396    }
397}
398
399/// Get inherited context fields as individual key-value pairs
400/// This function returns a HashMap of inherited context fields for dynamic field injection
401pub fn get_inherited_fields_map() -> std::collections::HashMap<String, String> {
402    let mut context_map = std::collections::HashMap::new();
403    
404    // Try async context stack first
405    if let Ok(stack) = ASYNC_CONTEXT_STACK.try_with(|stack| stack.borrow().clone()) {
406        for stack_context in stack.iter().rev() {
407            for (key, value) in stack_context {
408                // Skip function name to avoid duplication
409                if key != "function" {
410                    context_map.insert(key.clone(), value.clone());
411                }
412            }
413            if !context_map.is_empty() {
414                return context_map; // Use the most recent context
415            }
416        }
417    }
418    
419    // If no async context, try sync context stack
420    if context_map.is_empty() {
421        CONTEXT_STACK.with(|stack| {
422            let stack = stack.borrow();
423            for stack_context in stack.iter().rev() {
424                for (key, value) in stack_context {
425                    // Skip function name to avoid duplication
426                    if key != "function" {
427                        context_map.insert(key.clone(), value.clone());
428                    }
429                }
430                if !context_map.is_empty() {
431                    return; // Use the most recent context
432                }
433            }
434        });
435    }
436    
437    context_map
438}