context_logger/
context.rs

1//! Context builder for structured logging.
2
3use crate::{
4    ContextValue, StaticCowStr,
5    guard::LogContextGuard,
6    stack::{CONTEXT_STACK, ContextProperties},
7};
8
9/// A contextual properties that can be attached to log records.
10///
11/// [`LogContext`] represents a set of key-value pairs that will be
12/// automatically added to log messages when the context is active.
13#[derive(Debug)]
14pub struct LogContext(pub(crate) ContextProperties);
15
16impl LogContext {
17    /// Creates a new, empty context.
18    #[must_use]
19    pub const fn new() -> Self {
20        Self(ContextProperties::new())
21    }
22
23    /// Adds property to this context.
24    ///
25    /// # Examples
26    ///
27    /// ```
28    /// use context_logger::LogContext;
29    ///
30    /// let context = LogContext::new()
31    ///     .record("user_id", "user-123")
32    ///     .record("request_id", 42)
33    ///     .record("is_admin", true);
34    /// ```
35    #[must_use]
36    pub fn record(mut self, key: impl Into<StaticCowStr>, value: impl Into<ContextValue>) -> Self {
37        let property = (key.into(), value.into());
38        self.0.push(property);
39        self
40    }
41
42    /// Adds property to the current active context.
43    ///
44    /// This is useful for adding context information dynamically without having
45    /// direct access to the context.
46    ///
47    /// # Note
48    ///
49    /// If there is no active context, this operation will have no effect.
50    ///
51    /// # Examples
52    ///
53    /// ```
54    /// use context_logger::{LogContext, ContextValue};
55    /// use log::info;
56    ///
57    /// fn process_request() {
58    ///     // Add context information dynamically
59    ///     LogContext::add_record("processing_time_ms", 42);
60    ///     info!("Request processed");
61    /// }
62    ///
63    /// let _guard = LogContext::new()
64    ///     .record("request_id", "req-123")
65    ///     .enter();
66    ///
67    /// process_request(); // Will log with both request_id and processing_time_ms
68    /// ```
69    pub fn add_record(key: impl Into<StaticCowStr>, value: impl Into<ContextValue>) {
70        let property = (key.into(), value.into());
71
72        CONTEXT_STACK.with(|stack| {
73            if let Some(mut top) = stack.top_mut() {
74                top.push(property);
75            }
76        });
77    }
78
79    /// Activating this context, returning a guard that will exit the context when dropped.
80    ///
81    /// # In Asynchronous Code
82    ///
83    /// *Warning:* in asynchronous code [`Self::enter`] should be used very carefully or avoided entirely.
84    /// Holding the drop guard across `.await` points will result in incorrect logs:
85    ///
86    /// ```rust
87    /// use context_logger::LogContext;
88    ///
89    /// async fn my_async_fn() {
90    ///     let ctx = LogContext::new()
91    ///         .record("request_id", "req-123")
92    ///         .record("user_id", 42);
93    ///     // WARNING: This context will remain active until this
94    ///     // guard is dropped...
95    ///     let _guard = ctx.enter();
96    ///     // But this code causing the runtime to switch to another task,
97    ///     // while remaining in this context.
98    ///     tokio::task::yield_now().await;
99    ///     }
100    /// ```
101    ///
102    /// Please use the [`crate::FutureExt::in_log_context`] instead.
103    ///
104    #[must_use]
105    pub fn enter<'a>(self) -> LogContextGuard<'a> {
106        LogContextGuard::enter(self)
107    }
108}
109
110impl Default for LogContext {
111    fn default() -> Self {
112        Self::new()
113    }
114}