context_logger/
guard.rs

1//! A current logging context guard.
2
3use std::marker::PhantomData;
4
5use crate::{
6    LogContext,
7    stack::{CONTEXT_STACK, ContextStack},
8};
9
10/// A guard representing a current logging context in the context stack.
11///
12/// When the guard is dropped, the context is automatically removed from the stack.
13/// This is returned by the [`LogContext::enter`] method.
14///
15/// # Examples
16///
17/// ```
18/// use context_logger::LogContext;
19///
20/// // Create a context with some data
21/// let context = LogContext::new().record("user_id", 123);
22///
23/// // Enter the context (pushes to stack)
24/// let guard = context.enter();
25///
26/// // Log operations here will have access to the context
27/// // ...
28///
29/// // When `guard` goes out of scope, the context is automatically removed
30/// ```
31#[non_exhaustive]
32#[derive(Debug)]
33pub struct LogContextGuard<'a> {
34    // Make this guard unsendable.
35    _marker: PhantomData<&'a *mut ()>,
36}
37
38impl LogContextGuard<'_> {
39    pub(crate) fn enter(context: LogContext) -> Self {
40        CONTEXT_STACK.with(|stack| stack.push(context.0));
41        Self {
42            _marker: PhantomData,
43        }
44    }
45
46    pub(crate) fn exit(self) -> LogContext {
47        // We need to prevent the destructor from being called
48        // because we're manually managing the context stack here.
49        std::mem::forget(self);
50
51        let properties = CONTEXT_STACK
52            .with(ContextStack::pop)
53            .expect("There is a bug in log context guard, context should be exists");
54        LogContext(properties)
55    }
56}
57
58impl Drop for LogContextGuard<'_> {
59    fn drop(&mut self) {
60        CONTEXT_STACK.with(ContextStack::pop);
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use pretty_assertions::assert_eq;
67
68    use super::*;
69    use crate::stack::CONTEXT_STACK;
70
71    #[test]
72    fn test_log_context_guard_enter() {
73        let context = LogContext::new().record("simple", 42);
74        // Make sure the context stack is empty before entering the context.
75        assert_eq!(CONTEXT_STACK.with(ContextStack::is_empty), true);
76
77        let guard = context.enter();
78        // Check that the record was added to the top context.
79        assert_eq!(CONTEXT_STACK.with(|stack| stack.top().unwrap().len()), 1);
80
81        // Check that the context stack is empty after dropping the guard.
82        drop(guard);
83        assert_eq!(CONTEXT_STACK.with(ContextStack::len), 0);
84    }
85
86    #[test]
87    fn test_log_context_nested_guards() {
88        let outer_context = LogContext::new().record("simple_record", "outer_value");
89        assert_eq!(CONTEXT_STACK.with(ContextStack::len), 0);
90
91        let outer_guard = outer_context.enter();
92        assert_eq!(CONTEXT_STACK.with(|stack| stack.top().unwrap().len()), 1);
93
94        CONTEXT_STACK.with(|stack| {
95            let property = &stack.top().unwrap()[0];
96            assert_eq!(property.0, "simple_record");
97            assert_eq!(property.1.to_string(), "outer_value");
98        });
99
100        let inner_context = LogContext::new().record("simple_record", "inner_value");
101        {
102            let inner_guard = inner_context.enter();
103            // Test log context after inner guard is entered.
104            assert_eq!(CONTEXT_STACK.with(ContextStack::len), 2);
105            CONTEXT_STACK.with(|stack| {
106                let property = &stack.top().unwrap()[0];
107                assert_eq!(property.0, "simple_record");
108                assert_eq!(property.1.to_string(), "inner_value");
109            });
110
111            drop(inner_guard);
112        }
113        // Test log context after inner guard is dropped.
114        assert_eq!(CONTEXT_STACK.with(|stack| stack.top().unwrap().len()), 1);
115        CONTEXT_STACK.with(|stack| {
116            let property = &stack.top().unwrap()[0];
117            assert_eq!(property.0, "simple_record");
118            assert_eq!(property.1.to_string(), "outer_value");
119        });
120
121        drop(outer_guard);
122        assert_eq!(CONTEXT_STACK.with(ContextStack::is_empty), true);
123    }
124
125    #[test]
126    fn test_log_context_multithread() {
127        let local_context = LogContext::new().record("simple_record", "main");
128        let local_guard = local_context.enter();
129
130        let first_thread_handle = std::thread::spawn(|| {
131            let inner_context = LogContext::new().record("simple_record", "first_thread");
132            let inner_guard = inner_context.enter();
133
134            // Test log context after inner guard is entered.
135            assert_eq!(CONTEXT_STACK.with(ContextStack::len), 1);
136            CONTEXT_STACK.with(|stack| {
137                let property = &stack.top().unwrap()[0];
138                assert_eq!(property.0, "simple_record");
139                assert_eq!(property.1.to_string(), "first_thread");
140            });
141
142            drop(inner_guard);
143        });
144        let second_thread_handle = std::thread::spawn(|| {
145            let inner_context = LogContext::new().record("simple_record", "second_thread");
146            let inner_guard = inner_context.enter();
147            // Test log context after inner guard is entered.
148            assert_eq!(CONTEXT_STACK.with(ContextStack::len), 1);
149            CONTEXT_STACK.with(|stack| {
150                let property = &stack.top().unwrap()[0];
151                assert_eq!(property.0, "simple_record");
152                assert_eq!(property.1.to_string(), "second_thread");
153            });
154
155            drop(inner_guard);
156        });
157
158        first_thread_handle.join().unwrap();
159        second_thread_handle.join().unwrap();
160
161        CONTEXT_STACK.with(|stack| {
162            let property = &stack.top().unwrap()[0];
163            assert_eq!(property.0, "simple_record");
164            assert_eq!(property.1.to_string(), "main");
165        });
166        drop(local_guard);
167    }
168}