Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion spdlog/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
176 changes: 172 additions & 4 deletions spdlog/src/level.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use std::{fmt, str::FromStr};
use std::{
fmt::{self, Debug},
str::FromStr,
sync::atomic::Ordering,
};

use crate::Error;

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<Level> {
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")]
Expand All @@ -281,17 +325,117 @@ impl From<log::LevelFilter> 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<T>` 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<LevelFilter> for LevelFilterLayout {
fn from(value: LevelFilter) -> Self {
Self {
discriminant: value.discriminant(),
level: value.level().unwrap_or(Self::UNDEFINED_FALLBACK),
}
}
}

impl From<LevelFilterLayout> 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<LevelFilterLayout>,
}

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::<Level>::is_lock_free());
const_assert!(atomic::Atomic::<LevelFilter>::is_lock_free());
const_assert!(atomic::Atomic::<LevelFilterLayout>::is_lock_free());
const_assert!(size_of::<Level>() * 2 == size_of::<LevelFilter>());
const_assert!(align_of::<Level>() * 2 == align_of::<LevelFilter>());

#[test]
fn from_usize() {
Expand Down Expand Up @@ -446,4 +590,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<Item = LevelFilter> {
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);
}
}
23 changes: 11 additions & 12 deletions spdlog/src/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<str>) -> StdResult<(), SetLoggerNameError> {
Expand Down Expand Up @@ -108,9 +108,9 @@ fn check_logger_name(name: impl AsRef<str>) -> StdResult<(), SetLoggerNameError>
/// [./examples]: https://github.com/SpriteOvO/spdlog-rs/tree/main/spdlog/examples
pub struct Logger {
name: Option<String>,
level_filter: Atomic<LevelFilter>,
level_filter: AtomicLevelFilter,
sinks: Sinks,
flush_level_filter: Atomic<LevelFilter>,
flush_level_filter: AtomicLevelFilter,
error_handler: RwLock<ErrorHandler>,
periodic_flusher: Mutex<Option<(Duration, PeriodicWorker)>>,
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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()),
}
Expand Down Expand Up @@ -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),
};
Expand Down
10 changes: 5 additions & 5 deletions spdlog/src/sink/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<LevelFilter>,
level_filter: AtomicLevelFilter,
formatter: RwLockMappable<Box<dyn Formatter>>,
error_handler: RwLock<ErrorHandler>,
}
Expand All @@ -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()),
}
Expand All @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions spdlog/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 - !{
Expand All @@ -53,5 +53,5 @@ macro_rules! const_assert {
} as usize] = [];
};
}
#[cfg(test)]
#[allow(unused_imports)]
pub(crate) use const_assert;