acta 0.0.0

A customizable logging library for Rust
Documentation
use std::collections::HashMap;
use smart_default::SmartDefault;
use std::path::PathBuf;

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub enum LogFormat {
    Pretty,
    #[default]
    Compact,
    Json,
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
#[derive(Clone, Copy, Debug, Default)]
#[non_exhaustive]
pub enum LogRotation {
    #[default]
    None,
    Rename,
    #[cfg(feature = "compress")]
    Compress,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, derive_more::Display, derive_more::From)]
pub struct FilterDirective(String);

impl FilterDirective {
    pub fn new(value: impl Into<String>) -> Self {
        Self(value.into())
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl AsRef<str> for FilterDirective {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum LogLevel {
    Error,
    Warn,
    Info,
    Debug,
    Trace,
    Off,
    Custom(FilterDirective),
}

impl LogLevel {
    pub fn as_filter_directive(&self) -> &str {
        match self {
            Self::Error => "error",
            Self::Warn => "warn",
            Self::Info => "info",
            Self::Debug => "debug",
            Self::Trace => "trace",
            Self::Off => "off",
            Self::Custom(directive) => directive.as_str(),
        }
    }
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug)]
pub struct LogFilter {
    pub level: LogLevel,
    pub targets: HashMap<String, LogLevel>,
}

impl LogFilter {
    pub fn new(level: LogLevel) -> Self {
        Self {
            level,
            targets: HashMap::new(),
        }
    }

    pub fn with_target_level(mut self, target: impl Into<String>, level: LogLevel) -> Self {
        self.set_target_level(target, level);
        self
    }

    pub fn set_target_level(&mut self, target: impl Into<String>, level: LogLevel) {
        self.targets.insert(target.into(), level);
    }

    pub fn remove_target_level(&mut self, target: &str) -> bool {
        self.targets.remove(target).is_some()
    }

    pub fn as_filter_directive(&self) -> String {
        let mut directive = String::from(self.level.as_filter_directive());
        for (target, level) in &self.targets {
            directive.push(',');
            directive.push_str(target);
            directive.push('=');
            directive.push_str(level.as_filter_directive());
        }
        directive
    }
}

impl From<LogLevel> for LogFilter {
    fn from(level: LogLevel) -> Self {
        Self::new(level)
    }
}

#[cfg(feature = "serde")]
impl serde::Serialize for LogLevel {
    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_str(self.as_filter_directive())
    }
}

#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for LogLevel {
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let s = String::deserialize(deserializer)?;
        Ok(match s.as_str() {
            "error" => Self::Error,
            "warn" => Self::Warn,
            "info" => Self::Info,
            "debug" => Self::Debug,
            "trace" => Self::Trace,
            "off" => Self::Off,
            other => Self::Custom(FilterDirective::new(other)),
        })
    }
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug)]
pub struct FileLoggingConfig {
    pub path: PathBuf,
    #[cfg_attr(feature = "serde", serde(default))]
    pub rotation: LogRotation,
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub enum ConsoleWriter {
    #[default]
    Stdout,
    Stderr,
    #[cfg(any(feature = "custom-async", feature = "native-async"))]
    AsyncStdout(AsyncWriterMode),
    #[cfg(any(feature = "custom-async", feature = "native-async"))]
    AsyncStderr(AsyncWriterMode),
}

#[cfg(any(feature = "custom-async", feature = "native-async"))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
#[derive(Clone, Copy, Debug, Default)]
pub enum AsyncWriterMode {
    #[cfg(feature = "custom-async")]
    #[default]
    Custom,
    #[cfg(feature = "native-async")]
    Native,
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, SmartDefault)]
pub struct ConsoleConfig {
    #[default(LogFormat::default())]
    pub format: LogFormat,
    #[default = true]
    #[cfg_attr(feature = "serde", serde(default = "default_true"))]
    pub ansi: bool,
    #[cfg_attr(feature = "serde", serde(default))]
    pub writer: ConsoleWriter,
    #[default = true]
    #[cfg_attr(feature = "serde", serde(default = "default_true"))]
    pub show_path: bool,
    #[default = true]
    #[cfg_attr(feature = "serde", serde(default = "default_true"))]
    pub show_spans: bool,
    #[cfg_attr(feature = "serde", serde(default))]
    pub time_format: Option<String>,
}

#[cfg(feature = "serde")]
fn default_true() -> bool {
    true
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, SmartDefault)]
pub struct LoggingConfig {
    #[default(LogLevel::Info)]
    pub level: LogLevel,
    #[default(Some(ConsoleConfig::default()))]
    #[cfg_attr(feature = "serde", serde(default = "default_console"))]
    pub console: Option<ConsoleConfig>,
    #[cfg_attr(feature = "serde", serde(default))]
    pub file: Option<FileLoggingConfig>,
}

#[cfg(feature = "serde")]
fn default_console() -> Option<ConsoleConfig> {
    Some(ConsoleConfig::default())
}

#[cfg(test)]
mod test;