#![allow(dead_code)]
use std::fmt;
use std::sync::Arc;
use std::sync::atomic::{AtomicU8, Ordering};
use crate::level::LogLevel;
use crate::sink::Sink;
pub struct Logger {
name: String,
sinks: Vec<Arc<dyn Sink>>,
level: AtomicU8,
}
impl Logger {
pub(crate) fn new(name: String, sinks: Vec<Arc<dyn Sink>>, level: LogLevel) -> Self {
Self {
name,
sinks,
level: AtomicU8::new(level as u8),
}
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn sinks(&self) -> &[Arc<dyn Sink>] {
&self.sinks
}
#[must_use]
pub fn level(&self) -> LogLevel {
let raw = self.level.load(Ordering::Relaxed);
LogLevel::try_from(raw).expect(
"Logger.level only ever holds a value written from a valid LogLevel; \
a stored byte that does not round-trip indicates memory corruption \
or an unhandled LogLevel variant",
)
}
pub fn set_level(&self, level: LogLevel) {
self.level.store(level as u8, Ordering::Relaxed);
}
}
impl fmt::Debug for Logger {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Logger")
.field("name", &self.name)
.field("level", &self.level())
.field("sinks", &self.sinks.len())
.finish()
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::thread;
use super::*;
use crate::sink::NullSink;
fn stub(level: LogLevel) -> Arc<dyn Sink> {
Arc::new(NullSink::new(level))
}
#[test]
fn name_round_trips_through_construction() {
let logger = Logger::new("app".to_owned(), Vec::new(), LogLevel::Info);
assert_eq!(logger.name(), "app");
}
#[test]
fn level_round_trips_each_variant() {
for level in [
LogLevel::Trace,
LogLevel::Debug,
LogLevel::Info,
LogLevel::Warning,
LogLevel::Error,
] {
let logger = Logger::new("x".to_owned(), Vec::new(), level);
assert_eq!(logger.level(), level);
}
}
#[test]
fn set_level_updates_level() {
let logger = Logger::new("x".to_owned(), Vec::new(), LogLevel::Info);
assert_eq!(logger.level(), LogLevel::Info);
logger.set_level(LogLevel::Error);
assert_eq!(logger.level(), LogLevel::Error);
logger.set_level(LogLevel::Trace);
assert_eq!(logger.level(), LogLevel::Trace);
}
#[test]
fn sinks_round_trip_through_construction() {
let a = stub(LogLevel::Info);
let b = stub(LogLevel::Error);
let logger = Logger::new(
"two".to_owned(),
vec![Arc::clone(&a), Arc::clone(&b)],
LogLevel::Info,
);
let stored = logger.sinks();
assert_eq!(stored.len(), 2);
assert!(Arc::ptr_eq(&stored[0], &a));
assert!(Arc::ptr_eq(&stored[1], &b));
}
#[test]
fn logger_is_send_and_sync() {
const fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Logger>();
}
#[test]
fn set_level_is_visible_across_threads() {
let logger = Arc::new(Logger::new("shared".to_owned(), Vec::new(), LogLevel::Info));
let setter = Arc::clone(&logger);
thread::spawn(move || {
setter.set_level(LogLevel::Error);
})
.join()
.expect("spawned thread must not panic");
assert_eq!(logger.level(), LogLevel::Error);
}
#[test]
fn logger_pins_sinks_until_dropped() {
let sink = stub(LogLevel::Info);
let weak = Arc::downgrade(&sink);
let logger = Logger::new("pin".to_owned(), vec![sink], LogLevel::Info);
assert!(
weak.upgrade().is_some(),
"logger must hold a strong ref to its sinks while alive"
);
drop(logger);
assert!(
weak.upgrade().is_none(),
"dropping the logger must release its sinks"
);
}
}