use super::PastaLogger;
use std::collections::HashMap;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, OnceLock};
use tracing_subscriber::fmt::MakeWriter;
static REGISTRY: OnceLock<GlobalLoggerRegistry> = OnceLock::new();
#[derive(Clone)]
pub struct GlobalLoggerRegistry {
loggers: Arc<Mutex<HashMap<PathBuf, Arc<PastaLogger>>>>,
}
impl GlobalLoggerRegistry {
fn new() -> Self {
Self {
loggers: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn instance() -> &'static Self {
REGISTRY.get_or_init(GlobalLoggerRegistry::new)
}
pub fn register(&self, load_dir: PathBuf, logger: Arc<PastaLogger>) {
let mut loggers = self
.loggers
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
loggers.insert(load_dir, logger);
}
pub fn unregister(&self, load_dir: &Path) {
let mut loggers = self
.loggers
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
loggers.remove(load_dir);
}
pub fn get(&self, load_dir: &Path) -> Option<Arc<PastaLogger>> {
let loggers = self
.loggers
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
loggers.get(load_dir).cloned()
}
#[cfg(test)]
pub fn len(&self) -> usize {
self.loggers.lock().unwrap().len()
}
}
pub struct RoutingWriter {
logger: Option<Arc<PastaLogger>>,
}
impl Write for RoutingWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if let Some(ref logger) = self.logger {
logger.write(buf)
} else {
Ok(buf.len())
}
}
fn flush(&mut self) -> io::Result<()> {
if let Some(ref logger) = self.logger {
logger.flush()
} else {
Ok(())
}
}
}
impl<'a> MakeWriter<'a> for GlobalLoggerRegistry {
type Writer = RoutingWriter;
fn make_writer(&'a self) -> Self::Writer {
let load_dir = CURRENT_LOAD_DIR.with(|cell| cell.borrow().clone());
let logger = load_dir.and_then(|path| self.get(&path));
RoutingWriter { logger }
}
}
thread_local! {
static CURRENT_LOAD_DIR: std::cell::RefCell<Option<PathBuf>> = const { std::cell::RefCell::new(None) };
}
pub fn set_current_load_dir(load_dir: Option<PathBuf>) {
CURRENT_LOAD_DIR.with(|cell| {
*cell.borrow_mut() = load_dir;
});
}
pub fn get_current_load_dir() -> Option<PathBuf> {
CURRENT_LOAD_DIR.with(|cell| cell.borrow().clone())
}
pub struct LoadDirGuard {
previous: Option<PathBuf>,
}
impl LoadDirGuard {
pub fn new(load_dir: PathBuf) -> Self {
let previous = get_current_load_dir();
set_current_load_dir(Some(load_dir));
Self { previous }
}
}
impl Drop for LoadDirGuard {
fn drop(&mut self) {
set_current_load_dir(self.previous.take());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_registry_singleton() {
let r1 = GlobalLoggerRegistry::instance();
let r2 = GlobalLoggerRegistry::instance();
assert!(std::ptr::eq(r1, r2));
}
#[test]
fn test_load_dir_context() {
assert!(get_current_load_dir().is_none());
set_current_load_dir(Some(PathBuf::from("/test/path")));
assert_eq!(get_current_load_dir(), Some(PathBuf::from("/test/path")));
set_current_load_dir(None);
assert!(get_current_load_dir().is_none());
}
#[test]
fn test_load_dir_guard() {
set_current_load_dir(Some(PathBuf::from("/original")));
{
let _guard = LoadDirGuard::new(PathBuf::from("/guarded"));
assert_eq!(get_current_load_dir(), Some(PathBuf::from("/guarded")));
}
assert_eq!(get_current_load_dir(), Some(PathBuf::from("/original")));
set_current_load_dir(None);
}
}