use std::marker::PhantomData;
use crate::{
LogContext,
stack::{CONTEXT_STACK, ContextStack},
};
#[non_exhaustive]
#[derive(Debug)]
pub struct LogContextGuard<'a> {
_marker: PhantomData<&'a *mut ()>,
}
impl LogContextGuard<'_> {
pub(crate) fn enter(context: LogContext) -> Self {
CONTEXT_STACK.with(|stack| stack.push(context.0));
Self {
_marker: PhantomData,
}
}
pub(crate) fn exit(self) -> LogContext {
std::mem::forget(self);
let properties = CONTEXT_STACK
.with(ContextStack::pop)
.expect("There is a bug in log context guard, context should be exists");
LogContext(properties)
}
}
impl Drop for LogContextGuard<'_> {
fn drop(&mut self) {
CONTEXT_STACK.with(ContextStack::pop);
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
use crate::stack::CONTEXT_STACK;
#[test]
fn test_log_context_guard_enter() {
let context = LogContext::new().record("simple", 42);
assert_eq!(CONTEXT_STACK.with(ContextStack::is_empty), true);
let guard = context.enter();
assert_eq!(CONTEXT_STACK.with(|stack| stack.top().unwrap().len()), 1);
drop(guard);
assert_eq!(CONTEXT_STACK.with(ContextStack::len), 0);
}
#[test]
fn test_log_context_nested_guards() {
let outer_context = LogContext::new().record("simple_record", "outer_value");
assert_eq!(CONTEXT_STACK.with(ContextStack::len), 0);
let outer_guard = outer_context.enter();
assert_eq!(CONTEXT_STACK.with(|stack| stack.top().unwrap().len()), 1);
CONTEXT_STACK.with(|stack| {
let property = &stack.top().unwrap()[0];
assert_eq!(property.0, "simple_record");
assert_eq!(property.1.to_string(), "outer_value");
});
let inner_context = LogContext::new().record("simple_record", "inner_value");
{
let inner_guard = inner_context.enter();
assert_eq!(CONTEXT_STACK.with(ContextStack::len), 2);
CONTEXT_STACK.with(|stack| {
let property = &stack.top().unwrap()[0];
assert_eq!(property.0, "simple_record");
assert_eq!(property.1.to_string(), "inner_value");
});
drop(inner_guard);
}
assert_eq!(CONTEXT_STACK.with(|stack| stack.top().unwrap().len()), 1);
CONTEXT_STACK.with(|stack| {
let property = &stack.top().unwrap()[0];
assert_eq!(property.0, "simple_record");
assert_eq!(property.1.to_string(), "outer_value");
});
drop(outer_guard);
assert_eq!(CONTEXT_STACK.with(ContextStack::is_empty), true);
}
#[test]
fn test_log_context_multithread() {
let local_context = LogContext::new().record("simple_record", "main");
let local_guard = local_context.enter();
let first_thread_handle = std::thread::spawn(|| {
let inner_context = LogContext::new().record("simple_record", "first_thread");
let inner_guard = inner_context.enter();
assert_eq!(CONTEXT_STACK.with(ContextStack::len), 1);
CONTEXT_STACK.with(|stack| {
let property = &stack.top().unwrap()[0];
assert_eq!(property.0, "simple_record");
assert_eq!(property.1.to_string(), "first_thread");
});
drop(inner_guard);
});
let second_thread_handle = std::thread::spawn(|| {
let inner_context = LogContext::new().record("simple_record", "second_thread");
let inner_guard = inner_context.enter();
assert_eq!(CONTEXT_STACK.with(ContextStack::len), 1);
CONTEXT_STACK.with(|stack| {
let property = &stack.top().unwrap()[0];
assert_eq!(property.0, "simple_record");
assert_eq!(property.1.to_string(), "second_thread");
});
drop(inner_guard);
});
first_thread_handle.join().unwrap();
second_thread_handle.join().unwrap();
CONTEXT_STACK.with(|stack| {
let property = &stack.top().unwrap()[0];
assert_eq!(property.0, "simple_record");
assert_eq!(property.1.to_string(), "main");
});
drop(local_guard);
}
}