diff --git a/spdlog/Cargo.toml b/spdlog/Cargo.toml index 9345ec2e..29527321 100644 --- a/spdlog/Cargo.toml +++ b/spdlog/Cargo.toml @@ -47,7 +47,8 @@ sval = ["value-bag/sval"] [dependencies] arc-swap = "1.5.1" -atomic = "0.5.1" +atomic = "0.6.1" +bytemuck = { version = "1.24.0", features = ["derive"] } chrono = "0.4.22" crossbeam = { version = "0.8.2", optional = true } dyn-clone = "1.0.14" diff --git a/spdlog/src/level.rs b/spdlog/src/level.rs index b5508894..05d6efa4 100644 --- a/spdlog/src/level.rs +++ b/spdlog/src/level.rs @@ -1,4 +1,8 @@ -use std::{fmt, str::FromStr}; +use std::{ + fmt::{self, Debug}, + str::FromStr, + sync::atomic::Ordering, +}; use crate::Error; @@ -49,7 +53,7 @@ const LOG_LEVEL_SHORT_NAMES: [&str; Level::count()] = ["C", "E", "W", "I", "D", /// /// [`log!`]: crate::log! #[repr(u16)] -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, bytemuck::NoUninit)] pub enum Level { /// Designates critical errors. Critical = 0, @@ -182,6 +186,19 @@ impl FromStr for Level { } } +#[repr(u16)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, bytemuck::NoUninit)] +enum LevelFilterDiscriminant { + Off, + Equal, + NotEqual, + MoreSevere, + MoreSevereEqual, + MoreVerbose, + MoreVerboseEqual, + All, +} + /// Represents log level logical filter conditions. /// /// Use [`LevelFilter::test`] method to check if a [`Level`] satisfies the @@ -269,6 +286,33 @@ impl LevelFilter { None } } + + #[must_use] + fn discriminant(&self) -> LevelFilterDiscriminant { + match self { + Self::Off => LevelFilterDiscriminant::Off, + Self::Equal(_) => LevelFilterDiscriminant::Equal, + Self::NotEqual(_) => LevelFilterDiscriminant::NotEqual, + Self::MoreSevere(_) => LevelFilterDiscriminant::MoreSevere, + Self::MoreSevereEqual(_) => LevelFilterDiscriminant::MoreSevereEqual, + Self::MoreVerbose(_) => LevelFilterDiscriminant::MoreVerbose, + Self::MoreVerboseEqual(_) => LevelFilterDiscriminant::MoreVerboseEqual, + Self::All => LevelFilterDiscriminant::All, + } + } + + #[must_use] + fn level(&self) -> Option { + match *self { + Self::Equal(level) + | Self::NotEqual(level) + | Self::MoreSevere(level) + | Self::MoreSevereEqual(level) + | Self::MoreVerbose(level) + | Self::MoreVerboseEqual(level) => Some(level), + Self::Off | Self::All => None, + } + } } #[cfg(feature = "log")] @@ -281,17 +325,120 @@ impl From for LevelFilter { } } +// Atomic + +// This is a version of `LevelFilter` that does not contain uninitialized bytes. +// For variants like `LevelFilter::{Off,All}`, since they have no field, their +// field memory space is treated as uninitialized bytes. `Atomic` from the +// `atomic` crate uses `std::mem::transmute`, and its operations on +// uninitialized bytes are undefined behavior. Therefore, we created this +// `LevelFilterLayout` type, where uninitialized bytes are filled with +// `UNDEFINED_FALLBACK`, enabling safe atomic operations. +#[repr(C, align(4))] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, bytemuck::NoUninit)] +struct LevelFilterLayout { + discriminant: LevelFilterDiscriminant, + level: Level, +} + +impl LevelFilterLayout { + const UNDEFINED_FALLBACK: Level = Level::Critical; +} + +impl From for LevelFilterLayout { + fn from(value: LevelFilter) -> Self { + Self { + discriminant: value.discriminant(), + level: value.level().unwrap_or(Self::UNDEFINED_FALLBACK), + } + } +} + +impl From for LevelFilter { + fn from(layout: LevelFilterLayout) -> Self { + match layout.discriminant { + LevelFilterDiscriminant::Off => Self::Off, + LevelFilterDiscriminant::Equal => Self::Equal(layout.level), + LevelFilterDiscriminant::NotEqual => Self::NotEqual(layout.level), + LevelFilterDiscriminant::MoreSevere => Self::MoreSevere(layout.level), + LevelFilterDiscriminant::MoreSevereEqual => Self::MoreSevereEqual(layout.level), + LevelFilterDiscriminant::MoreVerbose => Self::MoreVerbose(layout.level), + LevelFilterDiscriminant::MoreVerboseEqual => Self::MoreVerboseEqual(layout.level), + LevelFilterDiscriminant::All => Self::All, + } + } +} + +/// Atomic version of [`LevelFilter`] which can be safely shared between +/// threads. +pub struct AtomicLevelFilter { + inner: atomic::Atomic, +} + +impl AtomicLevelFilter { + const ORDERING: Ordering = Ordering::Relaxed; + + /// Creates a new `AtomicLevelFilter`. + pub fn new(init: LevelFilter) -> Self { + Self { + inner: atomic::Atomic::new(init.into()), + } + } + + /// Loads the level filter with `Relaxed` ordering. + pub fn get(&self) -> LevelFilter { + self.load(Self::ORDERING) + } + + /// Stores a level filter with `Relaxed` ordering. + pub fn set(&self, new: LevelFilter) { + self.store(new, Self::ORDERING); + } + + /// Loads the level filter. + /// + /// # Panics + /// + /// Panics if the ordering is `Release` or `AcqRel`. + pub fn load(&self, ordering: Ordering) -> LevelFilter { + self.inner.load(ordering).into() + } + + /// Stores a level filter. + /// + /// # Panics + /// + /// Panics if the ordering is `Acquire` or `AcqRel`. + pub fn store(&self, value: LevelFilter, ordering: Ordering) { + self.inner.store(value.into(), ordering); + } + + /// Stores a level filter, returning the old level filter. + pub fn swap(&self, new: LevelFilter, ordering: Ordering) -> LevelFilter { + self.inner.swap(new.into(), ordering).into() + } +} + +impl Debug for AtomicLevelFilter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.get().fmt(f) + } +} + #[cfg(test)] mod tests { - use std::mem::{align_of, size_of}; + use std::mem::size_of; use super::*; use crate::utils::const_assert; const_assert!(atomic::Atomic::::is_lock_free()); const_assert!(atomic::Atomic::::is_lock_free()); + const_assert!(atomic::Atomic::::is_lock_free()); const_assert!(size_of::() * 2 == size_of::()); const_assert!(align_of::() * 2 == align_of::()); + const_assert!(size_of::() == size_of::()); + const_assert!(align_of::() == align_of::()); #[test] fn from_usize() { @@ -446,4 +593,28 @@ mod tests { LevelFilter::MoreSevereEqual(Level::Trace) ); } + + #[test] + fn atomic_level_filter() { + let atomic_level_filter = AtomicLevelFilter::new(LevelFilter::All); + + let assert_this = |new: LevelFilter| { + assert_ne!(atomic_level_filter.get(), new); + atomic_level_filter.set(new); + assert_eq!(atomic_level_filter.get(), new); + }; + + fn produce_all(cond: impl Fn(Level) -> LevelFilter) -> impl Iterator { + Level::iter().map(cond) + } + + assert_this(LevelFilter::Off); + produce_all(LevelFilter::Equal).for_each(assert_this); + produce_all(LevelFilter::NotEqual).for_each(assert_this); + produce_all(LevelFilter::MoreSevere).for_each(assert_this); + produce_all(LevelFilter::MoreSevereEqual).for_each(assert_this); + produce_all(LevelFilter::MoreVerbose).for_each(assert_this); + produce_all(LevelFilter::MoreVerboseEqual).for_each(assert_this); + assert_this(LevelFilter::All); + } } diff --git a/spdlog/src/logger.rs b/spdlog/src/logger.rs index 2a408c5e..4dd1d340 100644 --- a/spdlog/src/logger.rs +++ b/spdlog/src/logger.rs @@ -6,7 +6,7 @@ use crate::{ periodic_worker::PeriodicWorker, sink::{Sink, Sinks}, sync::*, - Level, LevelFilter, Record, Result, + AtomicLevelFilter, Level, LevelFilter, Record, Result, }; fn check_logger_name(name: impl AsRef) -> StdResult<(), SetLoggerNameError> { @@ -108,9 +108,9 @@ fn check_logger_name(name: impl AsRef) -> StdResult<(), SetLoggerNameError> /// [./examples]: https://github.com/SpriteOvO/spdlog-rs/tree/main/spdlog/examples pub struct Logger { name: Option, - level_filter: Atomic, + level_filter: AtomicLevelFilter, sinks: Sinks, - flush_level_filter: Atomic, + flush_level_filter: AtomicLevelFilter, error_handler: RwLock, periodic_flusher: Mutex>, } @@ -258,7 +258,7 @@ impl Logger { /// Gets the flush level filter. #[must_use] pub fn flush_level_filter(&self) -> LevelFilter { - self.flush_level_filter.load(Ordering::Relaxed) + self.flush_level_filter.get() } /// Sets a flush level filter. @@ -286,14 +286,13 @@ impl Logger { /// trace!(logger: logger, "world"); // Logs and flushes the buffer once /// ``` pub fn set_flush_level_filter(&self, level_filter: LevelFilter) { - self.flush_level_filter - .store(level_filter, Ordering::Relaxed); + self.flush_level_filter.set(level_filter); } /// Gets the log level filter. #[must_use] pub fn level_filter(&self) -> LevelFilter { - self.level_filter.load(Ordering::Relaxed) + self.level_filter.get() } /// Sets the log level filter. @@ -302,7 +301,7 @@ impl Logger { /// /// See [`Logger::should_log`]. pub fn set_level_filter(&self, level_filter: LevelFilter) { - self.level_filter.store(level_filter, Ordering::Relaxed); + self.level_filter.set(level_filter); } /// Sets automatic periodic flushing. @@ -473,9 +472,9 @@ impl Logger { fn clone_lossy(&self) -> Self { Logger { name: self.name.clone(), - level_filter: Atomic::new(self.level_filter()), + level_filter: AtomicLevelFilter::new(self.level_filter()), sinks: self.sinks.clone(), - flush_level_filter: Atomic::new(self.flush_level_filter()), + flush_level_filter: AtomicLevelFilter::new(self.flush_level_filter()), periodic_flusher: Mutex::new(None), error_handler: RwLock::new(self.error_handler.read_expect().clone()), } @@ -671,9 +670,9 @@ impl LoggerBuilder { let logger = Logger { name: self.name.clone(), - level_filter: Atomic::new(self.level_filter), + level_filter: AtomicLevelFilter::new(self.level_filter), sinks: self.sinks.clone(), - flush_level_filter: Atomic::new(self.flush_level_filter), + flush_level_filter: AtomicLevelFilter::new(self.flush_level_filter), error_handler: RwLock::new(self.error_handler.clone()), periodic_flusher: Mutex::new(None), }; diff --git a/spdlog/src/sink/mod.rs b/spdlog/src/sink/mod.rs index 4df6ec50..6e8cc1a1 100644 --- a/spdlog/src/sink/mod.rs +++ b/spdlog/src/sink/mod.rs @@ -64,7 +64,7 @@ pub use write_sink::*; use crate::{ formatter::{Formatter, FullFormatter}, sync::*, - Error, ErrorHandler, Level, LevelFilter, Record, Result, + AtomicLevelFilter, Error, ErrorHandler, Level, LevelFilter, Record, Result, }; /// Contains definitions of sink properties. @@ -78,7 +78,7 @@ use crate::{ /// types, changing behavior), this struct is not needed. Instead, define /// properties manually within your sink, and then implement [`SinkPropAccess`]. pub struct SinkProp { - level_filter: Atomic, + level_filter: AtomicLevelFilter, formatter: RwLockMappable>, error_handler: RwLock, } @@ -93,7 +93,7 @@ impl Default for SinkProp { /// | `error_handler` | [`ErrorHandler::default()`] | fn default() -> Self { Self { - level_filter: Atomic::new(LevelFilter::All), + level_filter: AtomicLevelFilter::new(LevelFilter::All), formatter: RwLockMappable::new(Box::new(FullFormatter::new())), error_handler: RwLock::new(ErrorHandler::default()), } @@ -104,12 +104,12 @@ impl SinkProp { /// Gets the log level filter. #[must_use] pub fn level_filter(&self) -> LevelFilter { - self.level_filter.load(Ordering::Relaxed) + self.level_filter.get() } /// Sets the log level filter. pub fn set_level_filter(&self, level_filter: LevelFilter) { - self.level_filter.store(level_filter, Ordering::Relaxed) + self.level_filter.set(level_filter) } /// Gets the formatter. diff --git a/spdlog/src/utils/mod.rs b/spdlog/src/utils/mod.rs index d53c91b3..9d229b79 100644 --- a/spdlog/src/utils/mod.rs +++ b/spdlog/src/utils/mod.rs @@ -44,7 +44,7 @@ pub fn open_file_bufw( } // Credits `static_assertions` crate -#[cfg(test)] +#[allow(unused_macros)] macro_rules! const_assert { ( $cond:expr $(,)? ) => { const _: [(); 0 - !{ @@ -53,5 +53,5 @@ macro_rules! const_assert { } as usize] = []; }; } -#[cfg(test)] +#[allow(unused_imports)] pub(crate) use const_assert;