context_logger/scope/
mod.rs1use std::{borrow::Cow, marker::PhantomData};
4
5use self::stack::{SCOPE_STACK, ScopeStack};
6use crate::{LogContext, LogValue};
7
8pub mod stack;
9
10#[non_exhaustive]
33#[derive(Debug)]
34pub struct LogScope {
35 _marker: PhantomData<*mut ()>,
38}
39
40impl LogScope {
41 #[must_use]
71 pub fn enter(context: LogContext) -> Self {
72 SCOPE_STACK.with(|stack| stack.push(context));
73 Self {
74 _marker: PhantomData,
75 }
76 }
77
78 pub fn in_scope<R>(context: LogContext, f: impl FnOnce() -> R) -> R {
98 let _guard = Self::enter(context);
99 f()
100 }
101
102 pub fn add_local_field(key: impl Into<Cow<'static, str>>, value: impl Into<LogValue>) {
134 SCOPE_STACK.with(|stack| {
135 if let Some(mut top) = stack.top_mut() {
136 top.0.local.insert(key, value);
137 }
138 });
139 }
140
141 #[doc = include_str!("../../examples/current_context.rs")]
151 #[must_use]
158 pub fn current_context() -> LogContext {
159 SCOPE_STACK
160 .with(|stack| stack.top().map(|frame| frame.clone().into()))
161 .unwrap_or_default()
162 }
163
164 pub(crate) fn exit(self) -> LogContext {
165 std::mem::forget(self);
168
169 let frame = SCOPE_STACK
170 .with(ScopeStack::pop)
171 .expect("bug in LogScope::exit: expected a scope frame to exist when popping on exit");
172 frame.into()
173 }
174}
175
176impl Drop for LogScope {
177 fn drop(&mut self) {
178 SCOPE_STACK.with(ScopeStack::pop);
179 }
180}
181
182pub trait LogContextExt: Sized + crate::private::Sealed {
188 fn in_scope<R>(self, f: impl FnOnce() -> R) -> R;
204}
205
206impl LogContextExt for LogContext {
207 fn in_scope<R>(self, f: impl FnOnce() -> R) -> R {
208 LogScope::in_scope(self, f)
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use pretty_assertions::assert_eq;
215 use static_assertions::assert_not_impl_any;
216
217 use super::*;
218
219 assert_not_impl_any!(LogScope: Send);
221
222 #[test]
223 fn test_log_context_guard_enter() {
224 let context = LogContext::new().with_local_field("simple", 42);
225 assert_eq!(SCOPE_STACK.with(ScopeStack::is_empty), true);
227
228 let guard = LogScope::enter(context);
229 assert_eq!(
231 SCOPE_STACK.with(|stack| stack.top().unwrap().fields().count()),
232 1
233 );
234
235 drop(guard);
237 assert_eq!(SCOPE_STACK.with(ScopeStack::len), 0);
238 }
239
240 #[test]
241 fn test_log_context_nested_guards() {
242 let outer_context = LogContext::new().with_local_field("simple_record", "outer_value");
243 assert_eq!(SCOPE_STACK.with(ScopeStack::len), 0);
244
245 let outer_guard = LogScope::enter(outer_context);
246 assert_eq!(
247 SCOPE_STACK.with(|stack| stack.top().unwrap().fields().count()),
248 1
249 );
250
251 SCOPE_STACK.with(|stack| {
252 let context = &stack.top().unwrap().0;
253 assert_eq!(
254 context.local.0.get("simple_record").unwrap().to_string(),
255 "outer_value"
256 );
257 });
258
259 let inner_context = LogContext::new().with_local_field("simple_record", "inner_value");
260 {
261 let inner_guard = LogScope::enter(inner_context);
262 assert_eq!(SCOPE_STACK.with(ScopeStack::len), 2);
264 SCOPE_STACK.with(|stack| {
265 let frame = stack.top().unwrap();
266 assert_eq!(
267 frame.0.local.find("simple_record").unwrap().to_string(),
268 "inner_value"
269 );
270 });
271
272 drop(inner_guard);
273 }
274 assert_eq!(
276 SCOPE_STACK.with(|stack| stack.top().unwrap().fields().count()),
277 1
278 );
279 SCOPE_STACK.with(|stack| {
280 let frame = stack.top().unwrap();
281 assert_eq!(
282 frame.0.local.find("simple_record").unwrap().to_string(),
283 "outer_value"
284 );
285 });
286
287 drop(outer_guard);
288 assert_eq!(SCOPE_STACK.with(ScopeStack::is_empty), true);
289 }
290
291 #[test]
292 fn test_log_context_multithread() {
293 let local_context = LogContext::new().with_local_field("simple_record", "main");
294 let local_guard = LogScope::enter(local_context);
295
296 let first_thread_handle = std::thread::spawn(|| {
297 let inner_context = LogContext::new().with_local_field("simple_record", "first_thread");
298 let inner_guard = LogScope::enter(inner_context);
299
300 assert_eq!(SCOPE_STACK.with(ScopeStack::len), 1);
302 SCOPE_STACK.with(|stack| {
303 let frame = stack.top().unwrap();
304 assert_eq!(
305 frame.0.local.find("simple_record").unwrap().to_string(),
306 "first_thread"
307 );
308 });
309
310 drop(inner_guard);
311 });
312 let second_thread_handle = std::thread::spawn(|| {
313 let inner_context =
314 LogContext::new().with_local_field("simple_record", "second_thread");
315 let inner_guard = LogScope::enter(inner_context);
316
317 assert_eq!(SCOPE_STACK.with(ScopeStack::len), 1);
319 SCOPE_STACK.with(|stack| {
320 let frame = stack.top().unwrap();
321 assert_eq!(
322 frame.0.local.find("simple_record").unwrap().to_string(),
323 "second_thread"
324 );
325 });
326
327 drop(inner_guard);
328 });
329
330 first_thread_handle.join().unwrap();
331 second_thread_handle.join().unwrap();
332
333 SCOPE_STACK.with(|stack| {
334 let frame = stack.top().unwrap();
335 assert_eq!(frame.0.local["simple_record"].to_string(), "main");
336 });
337 drop(local_guard);
338 }
339
340 #[test]
341 fn test_current_context_empty_scope() {
342 let context = LogScope::current_context();
343 assert!(context.is_empty());
344 }
345
346 #[test]
347 fn test_current_context_with_scope() {
348 let context = LogContext::new().with_local_field("record", 42);
349 {
350 let _guard = LogScope::enter(context);
351
352 let current_context = LogScope::current_context();
353 assert_eq!(current_context.local["record"].to_string(), "42");
354 }
355
356 assert!(LogScope::current_context().is_empty());
357 }
358
359 #[test]
360 fn test_in_scope_enters_context_and_returns_result() {
361 assert!(SCOPE_STACK.with(ScopeStack::is_empty));
362
363 let result = LogScope::in_scope(LogContext::new().with_local_field("record", 42), || {
364 let current_context = LogScope::current_context();
365 assert_eq!(current_context.local["record"].to_string(), "42");
366
367 40 + 2
368 });
369
370 assert_eq!(result, 42);
371 assert!(SCOPE_STACK.with(ScopeStack::is_empty));
372 }
373
374 #[test]
375 fn test_log_context_ext_in_scope_enters_context_and_returns_result() {
376 assert!(SCOPE_STACK.with(ScopeStack::is_empty));
377
378 let result = LogContext::new()
379 .with_local_field("record", 42)
380 .in_scope(|| {
381 let current_context = LogScope::current_context();
382 assert_eq!(current_context.local["record"].to_string(), "42");
383
384 40 + 2
385 });
386
387 assert_eq!(result, 42);
388 assert!(SCOPE_STACK.with(ScopeStack::is_empty));
389 }
390
391 #[test]
392 fn test_log_context_inherited_fields() {
393 LogContext::new()
394 .with_local_field("name", "Ann")
395 .with_inherited_field("tag", "42")
396 .with_inherited_field("target", "root")
397 .in_scope(|| {
398 let ctx = LogScope::current_context();
399
400 assert_eq!(ctx.local["name"].to_string(), "Ann");
401 assert_eq!(ctx.inherited["tag"].to_string(), "42");
402 assert_eq!(ctx.inherited["target"].to_string(), "root");
403
404 LogContext::new()
405 .with_local_field("target", "nested")
406 .in_scope(|| {
407 let ctx = LogScope::current_context();
408
409 assert_eq!(ctx.local["target"].to_string(), "nested");
410 assert_eq!(ctx.inherited["tag"].to_string(), "42");
411 assert!(ctx.local.find("name").is_none());
412 });
413 });
414 }
415
416 #[test]
418 fn test_panic_in_child_scope_does_not_break_parent() {
419 let outer_context = LogContext::new()
421 .with_inherited_field("outer", "val")
422 .with_local_field("outer_local", "ol");
423 {
424 let _parent_guard = LogScope::enter(outer_context);
425 assert_eq!(SCOPE_STACK.with(ScopeStack::len), 1);
427
428 let result = std::panic::catch_unwind(|| {
430 LogContext::new().in_scope(|| panic!("inner panic"));
431 });
432
433 assert!(result.is_err());
434 }
435
436 assert_eq!(SCOPE_STACK.with(ScopeStack::len), 0);
438 }
439
440 #[test]
442 fn test_sibling_scopes_get_independent_inherited_copies() {
443 let parent_ctx = LogContext::new()
444 .with_inherited_field("parent_key", "pv")
445 .with_local_field("parent_local", "pl");
446
447 {
448 let _g1 = LogScope::enter(parent_ctx);
449 assert_eq!(SCOPE_STACK.with(ScopeStack::len), 1);
450
451 let child1_result = LogContext::new()
453 .with_inherited_field("sibling", "child1")
454 .with_local_field("only_in_child1", "c1")
455 .in_scope(|| {
456 let c = LogScope::current_context();
457 format!(
458 "{}|{}",
459 c.inherited["parent_key"], c.local["only_in_child1"]
460 )
461 });
462 assert_eq!(child1_result, "pv|c1");
463
464 assert_eq!(SCOPE_STACK.with(ScopeStack::len), 1);
466
467 let c2_result = LogContext::new()
469 .with_inherited_field("sibling", "child2")
470 .with_local_field("only_in_child2", "c2")
471 .in_scope(|| {
472 let c = LogScope::current_context();
473 format!("{}|{}", c.inherited["parent_key"], c.inherited["sibling"])
474 });
475 assert_eq!(c2_result, "pv|child2");
476
477 assert_eq!(SCOPE_STACK.with(ScopeStack::len), 1);
479 }
480
481 assert_eq!(SCOPE_STACK.with(ScopeStack::len), 0);
483 }
484}