diff --git a/Cargo.toml b/Cargo.toml index 67de485..efd4ff2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "1.9.0" +version = "2.0.0" edition = "2021" authors = ["baoyachi "] description = "A simple log. It's really simple use" @@ -30,11 +30,12 @@ log = "0.4.11" log4rs = { version = "1.1.1", default-features = false, features = ["all_components", "humantime", "serde", "serde-value", "yaml_format", "gzip"], optional = true } once_cell = { version = "1.15.0", default-features = false, optional = true } serde = { version = "1.0.145", default-features = false, features = ["derive"] } +winnow = "0.6.18" [dependencies.simple-log-derive] path = "derive" optional = true -version = "1" +version = "2" [dev-dependencies] serde_json = "1" diff --git a/examples/console.rs b/examples/console.rs index 5e61a46..044c724 100644 --- a/examples/console.rs +++ b/examples/console.rs @@ -9,8 +9,10 @@ #[macro_use] extern crate simple_log; +use log::Level; + fn main() -> Result<(), String> { - simple_log::console("debug")?; + simple_log::console(Level::Debug)?; debug!("test console debug"); info!("test console info"); diff --git a/examples/file.rs b/examples/file.rs index 5007ba8..7f10128 100644 --- a/examples/file.rs +++ b/examples/file.rs @@ -10,7 +10,8 @@ extern crate simple_log; fn main() -> Result<(), String> { - simple_log::file("./log/file.log", "debug", 100, 10)?; + let path = "./log/file.log"; + simple_log::file(path, log::Level::Debug, 100, 10)?; debug!("test file debug"); info!("test file info"); diff --git a/examples/filter_module.rs b/examples/filter_module.rs index 85ce05c..7907e30 100644 --- a/examples/filter_module.rs +++ b/examples/filter_module.rs @@ -15,17 +15,36 @@ #[macro_use] extern crate simple_log; +use log::LevelFilter; use simple_log::LogConfig; fn main() -> Result<(), String> { let config = r#" - level = "debug" + level = "debug,filter_module::app::ctrl=warn,filter_module::app::launch::conf=error" out_kind = "console" time_format = "%H:%M:%S.%f" - filter_module = ["filter_module::app::ctrl","filter_module::app::launch::conf"] "#; let conf: LogConfig = toml::from_str(config).unwrap(); + assert_eq!( + conf, + LogConfig { + path: None, + directory: None, + level: ( + LevelFilter::Debug, + vec![ + ("filter_module::app::ctrl", LevelFilter::Warn).into(), + ("filter_module::app::launch::conf", LevelFilter::Error).into(), + ] + ), + size: 0, + out_kind: vec!["console".into()], + roll_count: 0, + time_format: Some("%H:%M:%S.%f".to_string()), + } + ); + simple_log::new(conf).unwrap(); //init log debug!("test console debug"); @@ -35,7 +54,8 @@ fn main() -> Result<(), String> { app::init_app(); app::launch::init_launch(); - app::launch::conf::err_conf(); // this log filter + app::launch::conf::err_conf(); + app::launch::conf::debug_conf(); // this log filter app::launch::parser::err_parser(); app::ctrl::init_ctrl(); // this log filter @@ -56,6 +76,10 @@ pub(crate) mod app { pub fn err_conf() { error!("conf log") } + + pub fn debug_conf() { + debug!("conf log") + } } pub mod parser { diff --git a/examples/get_log_conf.rs b/examples/get_log_conf.rs index 438d963..9b1968c 100644 --- a/examples/get_log_conf.rs +++ b/examples/get_log_conf.rs @@ -16,7 +16,7 @@ fn main() -> Result<(), String> { .path("./log/builder_log.log") .size(100) .roll_count(10) - .level("debug") + .level("debug")? .output_file() .output_console() .build(); diff --git a/examples/json_log.rs b/examples/json_log.rs index 6289b58..2a432f7 100644 --- a/examples/json_log.rs +++ b/examples/json_log.rs @@ -13,6 +13,18 @@ extern crate simple_log; use simple_log::LogConfig; fn main() { + let config = r#" + { + "path":"./log/tmp.log", + "level":"debug", + "size":10, + "out_kind":"console", + "roll_count":10, + "time_format":"%H:%M:%S.%f" + }"#; + let log_config: LogConfig = serde_json::from_str(config).unwrap(); + assert_eq!(log_config.out_kind, vec!["console".into()]); + let config = r#" { "path":"./log/tmp.log", diff --git a/examples/new.rs b/examples/new.rs index 6c6aeef..7eefc27 100644 --- a/examples/new.rs +++ b/examples/new.rs @@ -16,7 +16,7 @@ fn main() -> Result<(), String> { .path("./log/builder_log.log") .size(100) .roll_count(10) - .level("debug") + .level("debug")? .time_format("%Y-%m-%d %H:%M:%S.%f") .output_file() .output_console() diff --git a/examples/update_log_conf.rs b/examples/update_log_conf.rs index 9c87a1e..f57dc02 100644 --- a/examples/update_log_conf.rs +++ b/examples/update_log_conf.rs @@ -17,7 +17,7 @@ fn main() -> Result<(), String> { .path("./log/builder_log.log") .size(100) .roll_count(10) - .level("debug") + .level("debug")? .output_file() .output_console() .build(); @@ -33,7 +33,7 @@ fn main() -> Result<(), String> { .path("./log/builder_log.log") .size(2) .roll_count(2) - .level("info") + .level("info")? .output_file() .output_console() .build(); diff --git a/src/inner.rs b/src/inner.rs index 05ce8ac..c892387 100644 --- a/src/inner.rs +++ b/src/inner.rs @@ -39,7 +39,7 @@ //! .path("./log/builder_log.log") //! .size(1 * 100) //! .roll_count(10) -//! .level("debug") +//! .level("debug")? //! .output_file() //! .output_console() //! .build(); @@ -85,7 +85,7 @@ //! [examples](https://github.com/baoyachi/simple-log/tree/main/examples). //! -use crate::out_kind::deserialize_out_kind; +use crate::level::{parse_level, LevelInto}; use crate::out_kind::OutKind; use crate::SimpleResult; use log::LevelFilter; @@ -138,7 +138,7 @@ fn init_log_conf(mut log_config: LogConfig) -> SimpleResult<()> { /// .path("./log/builder_log.log") /// .size(1 * 100) /// .roll_count(10) -/// .level("debug") +/// .level("debug")? /// .output_file() /// .output_console() /// .build(); @@ -154,7 +154,7 @@ fn init_log_conf(mut log_config: LogConfig) -> SimpleResult<()> { /// .path("./log/builder_log.log") /// .size(2) /// .roll_count(2) -/// .level("info") +/// .level("info")? /// .output_file() /// .output_console() /// .build(); @@ -182,28 +182,28 @@ pub fn update_log_conf(mut log_config: LogConfig) -> SimpleResult { /// /// ```rust /// fn main() -> Result<(), String> { -/// use simple_log::{LogConfigBuilder, update_log_level, log_level}; +/// use simple_log::{LogConfigBuilder, update_log_level}; /// let config = LogConfigBuilder::builder() /// .path("./log/builder_log.log") /// .size(1 * 64) /// .roll_count(10) -/// .level("debug") +/// .level("debug")? /// .output_file() /// .output_console() /// .build(); /// simple_log::new(config)?; /// /// //update log level -/// let config = update_log_level(log_level::DEBUG)?; -/// assert_eq!("debug",config.get_level()); +/// let config = update_log_level(log::Level::Debug)?; +/// assert_eq!("DEBUG",config.get_level()); /// Ok(()) /// } /// ``` /// -pub fn update_log_level>(level: S) -> SimpleResult { +pub fn update_log_level(level: S) -> SimpleResult { let log_conf = LOG_CONF.get().unwrap(); let mut guard = log_conf.lock().unwrap(); - guard.log_config.level = level.into(); + guard.log_config.set_level(level)?; let config = build_config(&mut guard.log_config)?; guard.handle.set_config(config); Ok(guard.log_config.clone()) @@ -222,7 +222,7 @@ pub fn update_log_level>(level: S) -> SimpleResult { /// .path("./log/builder_log.log") /// .size(1 * 100) /// .roll_count(10) -/// .level("debug") +/// .level("debug")? /// .output_file() /// .output_console() /// .build(); @@ -241,14 +241,19 @@ pub fn get_log_conf() -> SimpleResult { let config = log_conf.lock().unwrap().log_config.clone(); Ok(config) } +use crate::level::deserialize_level; +use crate::out_kind::deserialize_out_kind; -#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub(crate) type InnerLevel = (LevelFilter, Vec); +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] pub struct LogConfig { #[serde(default)] pub path: Option, #[serde(default)] pub directory: Option, - pub level: String, + #[serde(deserialize_with = "deserialize_level")] + pub level: InnerLevel, #[serde(default)] pub size: u64, #[serde(deserialize_with = "deserialize_out_kind", default)] @@ -257,8 +262,38 @@ pub struct LogConfig { pub roll_count: u32, #[serde(default)] pub time_format: Option, - #[serde(default)] - pub filter_module: Vec, +} + +impl Default for LogConfig { + fn default() -> Self { + LogConfig { + path: None, + directory: None, + level: (LevelFilter::Debug, vec![]), + size: 0, + out_kind: vec![], + roll_count: 0, + time_format: None, + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct TargetLevel { + name: String, + level: LevelFilter, +} + +impl From<(S, LevelFilter)> for TargetLevel +where + S: AsRef, +{ + fn from(value: (S, LevelFilter)) -> Self { + Self { + name: value.0.as_ref().to_string(), + level: value.1, + } + } } impl LogConfig { @@ -281,8 +316,8 @@ impl LogConfig { self.directory.as_ref() } - pub fn get_level(&self) -> &String { - &self.level + pub fn get_level(&self) -> &str { + self.level.0.as_str() } pub fn get_size(&self) -> u64 { @@ -300,6 +335,13 @@ impl LogConfig { pub fn get_time_format(&self) -> Option<&String> { self.time_format.as_ref() } + + pub(crate) fn set_level(&mut self, level: T) -> SimpleResult<()> { + let level = level.into_level(); + let level = parse_level(level)?; + self.level = level; + Ok(()) + } } /// The [LogConfig] with builder wrapper. @@ -352,9 +394,9 @@ impl LogConfigBuilder { self } - pub fn level>(mut self, level: S) -> LogConfigBuilder { - self.0.level = level.into(); - self + pub fn level(mut self, level: S) -> SimpleResult { + self.0.set_level(level)?; + Ok(self) } pub fn size(mut self, size: u64) -> LogConfigBuilder { @@ -403,7 +445,7 @@ impl LogConfigBuilder { /// .path("./log/builder_log.log") /// .size(1 * 100) /// .roll_count(10) - /// .level("debug") + /// .level("debug").unwrap() /// .time_format("%Y-%m-%d %H:%M:%S.%f") /// .output_file() /// .output_console() @@ -434,7 +476,7 @@ impl LogConfigBuilder { /// .path("./log/builder_log.log") /// .size(1 * 100) /// .roll_count(10) -/// .level("info") +/// .level("info")? /// .output_file() /// .output_console() /// .build(); @@ -483,16 +525,23 @@ pub fn new(log_config: LogConfig) -> SimpleResult<()> { /// } /// ``` pub fn quick() -> SimpleResult<()> { - quick_log_level(log_level::DEBUG, None) + quick_log_level::<_, &str>("debug", None) } -pub fn quick_log_level>(level: S, path: Option) -> SimpleResult<()> { - let level = level.into(); - log_level::validate_log_level(&level)?; +pub fn quick_log_level>( + level: S, + path: Option

, +) -> SimpleResult<()> { + let level = level.into_level(); + let level = parse_level(level)?; let mut config = LogConfig { - path: path.map(|x| x.into()), + path: path.map(|v| v.into()), + directory: None, level, - ..Default::default() + size: 0, + out_kind: vec![], + roll_count: 0, + time_format: None, }; init_default_log(&mut config); init_log_conf(config)?; @@ -515,16 +564,17 @@ pub fn quick_log_level>(level: S, path: Option) -> SimpleResu /// Ok(()) /// } /// ``` -pub fn console>(level: S) -> SimpleResult<()> { +pub fn console(level: S) -> SimpleResult<()> { + let level = level.into_level(); + let level = parse_level(level)?; let config = LogConfig { path: None, directory: None, - level: level.into(), + level, size: 0, out_kind: vec![OutKind::Console], roll_count: 0, time_format: Some(DEFAULT_DATE_TIME_FORMAT.to_string()), - filter_module: vec![], }; init_log_conf(config)?; Ok(()) @@ -552,16 +602,22 @@ pub fn console>(level: S) -> SimpleResult<()> { /// Ok(()) /// } /// ``` -pub fn file>(path: S, level: S, size: u64, roll_count: u32) -> SimpleResult<()> { +pub fn file, S: LevelInto>( + path: P, + level: S, + size: u64, + roll_count: u32, +) -> SimpleResult<()> { + let level = level.into_level(); + let level = parse_level(level)?; let config = LogConfig { path: Some(path.into()), directory: None, - level: level.into(), + level, size, out_kind: vec![OutKind::File], roll_count, time_format: Some(DEFAULT_DATE_TIME_FORMAT.to_string()), - filter_module: vec![], }; init_log_conf(config)?; Ok(()) @@ -597,16 +653,16 @@ fn build_config(log: &mut LogConfig) -> SimpleResult { } } - for module_name in &log.filter_module { + for target in &log.level.1 { config_builder = config_builder.logger(LoggerBuilder::build( Logger::builder(), - module_name, - LevelFilter::Off, + &target.name, + target.level, )); } let config = config_builder - .build(root_builder.build(log_level::form_log_level(&log.level))) + .build(root_builder.build(log.level.0)) .map_err(|e| e.to_string())?; Ok(config) } @@ -628,10 +684,6 @@ fn init_default_log(log: &mut LogConfig) { log.roll_count = 10 } - if log.level.is_empty() { - log.level = log_level::DEBUG.to_string() - } - if log.out_kind.is_empty() { log.out_kind .append(&mut vec![OutKind::Console, OutKind::File]) @@ -696,55 +748,3 @@ fn file_appender(log: &LogConfig) -> SimpleResult> { Ok(Box::new(logfile)) } - -pub mod log_level { - use log::LevelFilter; - - pub const TRACE: &str = "trace"; - pub const DEBUG: &str = "debug"; - pub const INFO: &str = "info"; - pub const WARN: &str = "warn"; - pub const ERROR: &str = "error"; - - /// convert log level str to [LevelFilter]. - /// - /// The default log level use [LevelFilter::Debug]. - /// - /// # Examples - /// - /// ```rust - /// fn run() { - /// use simple_log::log_level::form_log_level; - /// use log::LevelFilter; - /// let level = form_log_level("warn"); - /// assert_eq!(level,LevelFilter::Warn); - /// - /// let level = form_log_level("error"); - /// assert_eq!(level,LevelFilter::Error); - /// - /// let level = form_log_level("no"); - /// assert_eq!(level,LevelFilter::Debug); - /// } - /// ``` - /// - pub fn form_log_level(level: &str) -> LevelFilter { - validate_log_level(level).unwrap_or(LevelFilter::Debug) - } - - pub fn validate_log_level(level: &str) -> Result { - match level.to_lowercase().as_str() { - TRACE => Ok(LevelFilter::Trace), - DEBUG => Ok(LevelFilter::Debug), - INFO => Ok(LevelFilter::Info), - WARN => Ok(LevelFilter::Warn), - ERROR => Ok(LevelFilter::Error), - _ => { - let log_levels = format!("{},{},{},{},{}", TRACE, DEBUG, INFO, WARN, ERROR); - Err(format!( - "unknown log_level:{},one of:[{}]", - level, log_levels - )) - } - } - } -} diff --git a/src/level.rs b/src/level.rs new file mode 100644 index 0000000..a92540a --- /dev/null +++ b/src/level.rs @@ -0,0 +1,205 @@ +use crate::inner::InnerLevel; +use core::fmt; +use log::{Level, LevelFilter}; +pub use parser::*; +use serde::de::DeserializeSeed; +use serde::{de, Deserializer}; + +pub(crate) mod parser { + use crate::inner::InnerLevel; + use crate::TargetLevel; + use log::LevelFilter; + use std::str::FromStr; + use winnow::ascii::{alpha1, multispace0}; + use winnow::combinator::{opt, repeat}; + use winnow::token::take_while; + use winnow::{PResult, Parser}; + + /// + /// ```rust + /// use log::LevelFilter; + /// use simple_log::level::parse_level; + /// let input = "off"; + /// assert_eq!(parse_level(input).unwrap(), (LevelFilter::Off, vec![])); + /// + /// let input = "debug"; + /// assert_eq!(parse_level(input).unwrap(), (LevelFilter::Debug, vec![])); + /// + /// let input = "info"; + /// assert_eq!(parse_level(input).unwrap(), (LevelFilter::Info, vec![])); + /// + /// let input = "warn"; + /// assert_eq!(parse_level(input).unwrap(), (LevelFilter::Warn, vec![])); + /// + /// let input = "error"; + /// assert_eq!(parse_level(input).unwrap(), (LevelFilter::Error, vec![])); + /// + /// let input = "off !!!"; + /// assert_eq!(parse_level(input).err().unwrap(), + /// r###"Failed to parse level: + /// off !!! + /// ^ + /// "###); + /// + /// let input = "warning"; + /// assert_eq!(parse_level(input).err().unwrap(), + /// r#"Failed to parse level: + /// warning + /// ^ + /// attempted to convert a string that doesn't match an existing log level"#); + /// + /// + /// let input = "info,"; + /// assert_eq!(parse_level(input).err().unwrap(), + /// r#"Failed to parse level: + /// info, + /// ^ + /// "#); + /// + /// let input = "error,app=off"; + /// assert_eq!(parse_level(input).unwrap(), (LevelFilter::Error, vec![("app", LevelFilter::Off).into()])); + /// + /// let input = "debug,app=error,"; + /// assert_eq!(parse_level(input).unwrap(), (LevelFilter::Debug, vec![("app", LevelFilter::Error).into()])); + /// + /// let input = "debug,app=error,filter_module::app::ctrl=error,app::launch::c123onf=info"; + /// assert_eq!( + /// parse_level(input).unwrap(), + /// (LevelFilter::Debug, vec![ + /// ("app", LevelFilter::Error).into(), + /// ("filter_module::app::ctrl", LevelFilter::Error).into(), + /// ("app::launch::c123onf", LevelFilter::Info).into(), + /// ])); + /// + ///``` + /// + pub fn parse_level(input: &str) -> Result { + match ( + level, + opt((multispace0, ',', repeat(1.., target_level))) + .map(|c| c.map(|(_, _, t)| t).unwrap_or_default()), + ) + .parse(input) + { + Ok((level, targets)) => Ok((level, targets)), + Err(err) => Err(format!("Failed to parse level:\n{}", err)), + } + } + + fn level(input: &mut &str) -> PResult { + alpha1.try_map(LevelFilter::from_str).parse_next(input) + } + + fn target_level(input: &mut &str) -> PResult { + (multispace0, target_name, '=', level, multispace0, opt(',')) + .map(|(_, name, _, level, _, _)| (name, level).into()) + .parse_next(input) + } + + fn target_name<'a>(input: &mut &'a str) -> PResult<&'a str> { + take_while(1.., ('0'..='9', 'A'..='Z', 'a'..='z', ':', '_')).parse_next(input) + } +} + +#[allow(clippy::wrong_self_convention)] +pub trait LevelInto { + fn into_level(&self) -> &str; +} + +impl LevelInto for &str { + fn into_level(&self) -> &str { + self + } +} + +impl LevelInto for String { + fn into_level(&self) -> &str { + self.as_str() + } +} + +impl LevelInto for &String { + fn into_level(&self) -> &str { + self.as_str() + } +} + +impl LevelInto for LevelFilter { + fn into_level(&self) -> &str { + self.as_str() + } +} + +impl LevelInto for Level { + fn into_level(&self) -> &str { + self.as_str() + } +} + +macro_rules! de_from { + ($err:expr) => { + LevelSerde::deserialize($err).map_err(de::Error::custom) + }; +} + +struct LevelSerde; + +impl LevelSerde { + fn deserialize(s: S) -> Result + where + S: Into, + { + let s = s.into(); + parse_level(&s) + } +} + +impl<'de> DeserializeSeed<'de> for LevelSerde { + type Value = InnerLevel; + + fn deserialize(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(self) + } +} + +impl<'de> serde::de::Visitor<'de> for LevelSerde { + type Value = InnerLevel; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("inner level") + } + + fn visit_str(self, s: &str) -> Result + where + E: serde::de::Error, + { + de_from!(s) + } +} + +pub fn deserialize_level<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + deserializer.deserialize_any(LevelSerde) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn quick_log_level1() { + fn quick_log_level(level: S) { + level.into_level(); + } + + quick_log_level("debug"); + quick_log_level("debug".to_string()); + quick_log_level(LevelFilter::Debug); + quick_log_level(Level::Debug); + } +} diff --git a/src/lib.rs b/src/lib.rs index 83e3ab3..209d29d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,10 +4,14 @@ pub extern crate log; pub mod macros; #[cfg(feature = "log_inner")] mod inner; +pub mod level; mod out_kind; + #[cfg(feature = "log_inner")] pub use inner::*; +pub use log::Level; +pub use log::LevelFilter; #[cfg(feature = "target")] pub use simple_log_derive::*; diff --git a/src/macros.rs b/src/macros.rs index 3bf7ad6..432d6a1 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -69,10 +69,10 @@ macro_rules! log_target { #[macro_export] macro_rules! quick { () => { - $crate::quick_log_level($crate::log_level::DEBUG, None).unwrap() + $crate::quick_log_level::<_, &str>($crate::Level::Debug, None).unwrap() }; ($level:expr) => {{ - $crate::quick_log_level($level, None).unwrap() + $crate::quick_log_level::<_, &str>($level, None).unwrap() }}; ($level:expr,$path:expr) => {{ $crate::quick_log_level($level, Some($path)).unwrap() diff --git a/src/out_kind.rs b/src/out_kind.rs index 32edfd4..b417a59 100644 --- a/src/out_kind.rs +++ b/src/out_kind.rs @@ -30,7 +30,12 @@ impl<'de> Deserialize<'de> for OutKind { const KIND_EXPECT: &str = "expect out_kind string or vec:'console','file' or ['console','file']"; -struct KindSerde; +impl> From for OutKind { + fn from(value: S) -> Self { + KindSerde::deserialize(value.as_ref()).unwrap() + } +} +pub struct KindSerde; impl KindSerde { fn deserialize(s: S) -> Result @@ -38,7 +43,7 @@ impl KindSerde { S: Into, { let s = s.into(); - match s.as_str() { + match s.to_ascii_lowercase().as_str() { KIND_FILE => Ok(OutKind::File), KIND_CONSOLE => Ok(OutKind::Console), _ => Err(format!("Invalid state '{}',{}", s, KIND_EXPECT)), @@ -46,7 +51,7 @@ impl KindSerde { } } -impl<'de> serde::de::Visitor<'de> for KindSerde { +impl<'de> de::Visitor<'de> for KindSerde { type Value = Vec; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -55,7 +60,7 @@ impl<'de> serde::de::Visitor<'de> for KindSerde { fn visit_str(self, s: &str) -> Result where - E: serde::de::Error, + E: de::Error, { let kind = de_from!(s)?; Ok(vec![kind]) @@ -71,7 +76,7 @@ impl<'de> serde::de::Visitor<'de> for KindSerde { pub fn deserialize_out_kind<'de, D>(deserializer: D) -> Result, D::Error> where - D: serde::Deserializer<'de>, + D: Deserializer<'de>, { deserializer.deserialize_any(KindSerde) }