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