use std::cell::{Ref, RefCell, RefMut};
use crate::{LogContext, records::LogRecordRef};
thread_local! {
pub static SCOPE_STACK: ScopeStack = const { ScopeStack::new() };
}
#[derive(Debug, Clone, Default)]
pub struct ScopeFrame(pub LogContext);
#[derive(Debug)]
pub struct ScopeStack {
inner: RefCell<Vec<ScopeFrame>>,
}
impl ScopeFrame {
pub fn new() -> Self {
Self(LogContext::new())
}
pub fn records(&self) -> impl Iterator<Item = LogRecordRef<'_>> + Clone {
self.0.inherited.iter().chain(self.0.local.iter())
}
}
impl From<LogContext> for ScopeFrame {
fn from(context: LogContext) -> Self {
Self(context)
}
}
impl From<ScopeFrame> for LogContext {
fn from(frame: ScopeFrame) -> Self {
frame.0
}
}
impl ScopeStack {
pub const fn new() -> Self {
Self {
inner: RefCell::new(Vec::new()),
}
}
pub fn push(&self, mut context: LogContext) {
let mut inherited = self
.top()
.map(|top| top.0.inherited.clone())
.unwrap_or_default();
inherited.extend(context.inherited);
context.inherited = inherited;
self.inner.borrow_mut().push(ScopeFrame::from(context));
}
pub fn pop(&self) -> Option<ScopeFrame> {
self.inner.borrow_mut().pop()
}
pub fn top(&self) -> Option<Ref<'_, ScopeFrame>> {
let inner = self.inner.borrow();
if inner.is_empty() {
None
} else {
Some(Ref::map(inner, |inner| inner.last().unwrap()))
}
}
pub fn top_mut(&self) -> Option<RefMut<'_, ScopeFrame>> {
let inner = self.inner.borrow_mut();
if inner.is_empty() {
None
} else {
Some(RefMut::map(inner, |inner| inner.last_mut().unwrap()))
}
}
}
impl Default for ScopeStack {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
impl ScopeStack {
pub fn len(&self) -> usize {
self.inner.borrow().len()
}
pub fn is_empty(&self) -> bool {
self.inner.borrow().is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::LogRecords;
fn record_to_string(record: LogRecordRef<'_>) -> (&str, String) {
(record.0.as_ref(), record.1.to_string())
}
#[test]
fn test_scope_frame_records_with_inherited() {
let frame = ScopeFrame(LogContext {
local: LogRecords::new().field("name", "bob"),
inherited: LogRecords::new().field("tag", 42),
});
let records: Vec<_> = frame.records().map(record_to_string).collect();
assert_eq!(records.len(), 2);
assert_eq!(records[0], ("tag", "42".to_string()));
}
}