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}