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}