with_async_context/
logger.rs

1//! # Context Logger Implementation
2//!
3//! This module provides a logging implementation that automatically includes the current
4//! async context in log messages. It integrates with the standard `log` crate and allows
5//! customizing the log format and level.
6//!
7//! ## Features
8//!
9//! - Thread-safe logging with context information
10//! - Configurable log format with placeholders for level, context, and message
11//! - Log level control via RUST_LOG environment variable
12//! - Implementation of the standard `log::Log` trait
13//!
14//! ## Usage Example
15//!
16//! ```rust,no_run
17//! use with_async_context::{ContextLogger, init_context_logger, with_async_context, from_context_mut};
18//! use log::{info, warn};
19//!
20//! #[derive(Clone)]
21//! struct MyContext {
22//!     request_id: String
23//! }
24//!
25//! impl ToString for MyContext {
26//!     fn to_string(&self) -> String {
27//!         self.request_id.clone()
28//!     }
29//! }
30//!
31//! // Initialize the logger with default format
32//! init_context_logger!(MyContext);
33//!
34//! // Or initialize with custom format
35//! // init_context_logger!(MyContext, "[{level}] Request {context}: {message}");
36//!
37//! async fn handle_request() {
38//!     info!("Starting request processing");
39//!     // Do some work
40//!     warn!("Something unexpected happened");
41//! }
42//!
43//! # async fn example() {
44//! // Run function with context
45//! let ctx = MyContext { request_id: "req-123".to_string() };
46//! let (_, _) = with_async_context(ctx, handle_request()).await;
47//!
48//! // Run another request with different context
49//! let ctx2 = MyContext { request_id: "req-456".to_string() };
50//! let (_, _) = with_async_context(ctx2, handle_request()).await;
51//! # }
52//! ```
53
54use std::{env, io::Write};
55
56use crate::context_as_string;
57use log::{Level, Log, Metadata, Record};
58
59/// A logger implementation that includes async context information in log messages
60pub struct ContextLogger<C>
61where
62    C: 'static + ToString + std::marker::Send + std::marker::Sync,
63{
64    /// Phantom data to hold the context type parameter
65    pub _phantom: std::marker::PhantomData<C>,
66    /// Optional custom format string for log messages
67    format: Option<String>,
68    /// Current log level threshold
69    level: Option<Level>,
70}
71
72impl<C> ContextLogger<C>
73where
74    C: 'static + ToString + std::marker::Send + std::marker::Sync,
75{
76    /// Creates a new uninitialized context logger
77    pub const fn new() -> Self {
78        Self {
79            _phantom: std::marker::PhantomData,
80            format: None,
81            level: None,
82        }
83    }
84
85    /// Initializes the logger with an optional custom format
86    ///
87    /// # Arguments
88    ///
89    /// * `format` - Optional custom format string with {level}, {context}, and {message} placeholders
90    pub fn init(&mut self, format: Option<String>) {
91        let level = match env::var("RUST_LOG")
92            .unwrap_or_default()
93            .to_uppercase()
94            .as_str()
95        {
96            "ERROR" => Level::Error,
97            "WARN" => Level::Warn,
98            "INFO" => Level::Info,
99            "DEBUG" => Level::Debug,
100            "TRACE" => Level::Trace,
101            _ => Level::Info,
102        };
103
104        self.format =
105            Some(format.unwrap_or_else(|| String::from("{level} - {context} - {message}")));
106        self.level = Some(level);
107    }
108}
109
110impl<C> Log for ContextLogger<C>
111where
112    C: 'static + ToString + std::marker::Send + std::marker::Sync,
113{
114    /// Checks if a log level is enabled
115    fn enabled(&self, metadata: &Metadata) -> bool {
116        if let Some(level) = self.level {
117            metadata.level() <= level
118        } else {
119            false
120        }
121    }
122
123    /// Logs a message with the current context
124    fn log(&self, record: &Record) {
125        if self.enabled(record.metadata()) {
126            let context = context_as_string::<C>();
127
128            if let Some(format) = &self.format {
129                let msg = format
130                    .replace("{level}", &record.level().to_string())
131                    .replace("{context}", &context)
132                    .replace("{message}", &record.args().to_string());
133
134                let _ = std::io::stderr().write_all(msg.as_bytes());
135                let _ = std::io::stderr().write_all(b"\n");
136            }
137        }
138    }
139
140    /// Flushes any buffered records
141    fn flush(&self) {}
142}
143
144/// Macro to initialize the context logger
145///
146/// # Arguments
147///
148/// * `$context_type` - The type implementing the context
149/// * `$format` - Optional custom format string
150///
151/// # Examples
152///
153/// ```rust,no_run
154/// use with_async_context::{ContextLogger, init_context_logger, with_async_context, from_context_mut};
155/// use log::{info, warn};
156///
157/// #[derive(Debug)]
158/// struct MyContext {
159///     function_name: String,
160///     context_id: String,
161/// }
162///
163/// impl ToString for MyContext {
164///    fn to_string(&self) -> String {
165///       format!("[{}:{}]", self.function_name, self.context_id)
166///   }
167/// }
168///
169/// // Initialize with default format
170/// init_context_logger!(MyContext);
171///
172/// // Initialize with custom format
173/// // init_context_logger!(MyContext, "{level} [{context}] {message}");
174///
175/// async fn example_function() {
176///     from_context_mut(|ctx: Option<&mut MyContext>| {
177///         if let Some(ctx) = ctx {
178///             ctx.function_name = "example_function".to_string();
179///         }
180///     });
181///     info!("Inside example function");
182///     warn!("Something to warn about");
183/// }
184///
185/// async fn another_function() {
186///     from_context_mut(|ctx: Option<&mut MyContext>| {
187///         if let Some(ctx) = ctx {
188///             ctx.function_name = "another_function".to_string();
189///         }
190///     });
191///     info!("Inside another function");
192/// }
193///
194/// # async fn run_example() {
195/// let ctx = MyContext {
196///     function_name: String::new(),
197///     context_id: "ctx1".to_string()
198/// };
199/// let (_, _) = with_async_context(ctx, example_function()).await;
200///
201/// let ctx2 = MyContext {
202///     function_name: String::new(),
203///     context_id: "ctx2".to_string()
204/// };
205/// let (_, _) = with_async_context(ctx2, another_function()).await;
206/// # }
207/// ```
208#[macro_export]
209macro_rules! init_context_logger {
210    ($context_type:ty) => {{
211        use with_async_context::ContextLogger;
212        static mut LOGGER: ContextLogger<$context_type> = ContextLogger::<$context_type>::new();
213        unsafe {
214            LOGGER.init(None);
215            let max_level = match std::env::var("RUST_LOG")
216                .unwrap_or_default()
217                .to_uppercase()
218                .as_str()
219            {
220                "ERROR" => log::LevelFilter::Error,
221                "WARN" => log::LevelFilter::Warn,
222                "INFO" => log::LevelFilter::Info,
223                "DEBUG" => log::LevelFilter::Debug,
224                "TRACE" => log::LevelFilter::Trace,
225                _ => log::LevelFilter::Info,
226            };
227            let _ = log::set_logger(&LOGGER).map(|()| log::set_max_level(max_level));
228        }
229    }};
230    ($context_type:ty, $format:expr) => {{
231        use with_async_context::ContextLogger;
232        static mut LOGGER: ContextLogger<$context_type> = ContextLogger::<$context_type>::new();
233        unsafe {
234            LOGGER.init(Some($format.to_string()));
235            let max_level = match std::env::var("RUST_LOG")
236                .unwrap_or_default()
237                .to_uppercase()
238                .as_str()
239            {
240                "ERROR" => log::LevelFilter::Error,
241                "WARN" => log::LevelFilter::Warn,
242                "INFO" => log::LevelFilter::Info,
243                "DEBUG" => log::LevelFilter::Debug,
244                "TRACE" => log::LevelFilter::Trace,
245                _ => log::LevelFilter::Info,
246            };
247            let _ = log::set_logger(&LOGGER).map(|()| log::set_max_level(max_level));
248        }
249    }};
250}