diff --git a/src/__private_api.rs b/src/__private_api.rs new file mode 100644 index 000000000..fcc5358fc --- /dev/null +++ b/src/__private_api.rs @@ -0,0 +1,282 @@ +use self::sealed::{LogArgs, LogKVs, LogLevel, LogTarget}; +use crate::{Level, LevelFilter, Metadata, Record}; +use std::cmp::Ordering; +pub use std::convert::identity; +use std::fmt::Arguments; +use std::option::Option; +pub use std::primitive::str; +pub use std::{file, format_args, line, module_path, stringify}; + +#[cfg(feature = "kv_unstable")] +pub type LogKvValue<'a> = dyn crate::kv::value::ToValue + 'a; + +#[cfg(not(feature = "kv_unstable"))] +pub type LogKvValue<'a> = str; + +mod sealed { + use crate::Level; + use std::fmt::Arguments; + + pub trait LogArgs { + fn with(self, f: impl FnOnce(Arguments)); + } + + pub trait LogLevel { + fn into_log_level(self) -> Level; + } + + pub trait LogTarget { + fn with(self, module_path: &'static str, f: impl FnOnce(&str)); + } + + pub trait LogKVs { + fn with(self, f: impl FnOnce(&[(&str, &super::LogKvValue)])); + } +} + +// `LogArgs`. + +impl LogArgs for &str { + #[inline] + fn with(self, f: impl FnOnce(Arguments)) { + f(format_args!("{self}")) + } +} + +impl LogArgs for Arguments<'_> { + fn with(self, f: impl FnOnce(Arguments)) { + f(self) + } +} + +// `LogLevel`. + +impl LogLevel for Level { + #[inline] + fn into_log_level(self) -> Level { + self + } +} + +macro_rules! define_static_levels { + ($($ty:ident => $lvl:ident,)*) => { + $( + #[derive(Debug)] + pub struct $ty; + + impl LogLevel for $ty { + #[inline] + fn into_log_level(self) -> Level { + Level::$lvl + } + } + + impl PartialEq for $ty { + #[inline] + fn eq(&self, other: &LevelFilter) -> bool { + Level::$lvl.eq(other) + } + } + + impl PartialOrd for $ty { + #[inline] + fn partial_cmp(&self, other: &LevelFilter) -> Option { + Level::$lvl.partial_cmp(other) + } + + #[inline] + fn lt(&self, other: &LevelFilter) -> bool { + Level::$lvl.lt(other) + } + + #[inline] + fn le(&self, other: &LevelFilter) -> bool { + Level::$lvl.le(other) + } + + #[inline] + fn gt(&self, other: &LevelFilter) -> bool { + Level::$lvl.gt(other) + } + + #[inline] + fn ge(&self, other: &LevelFilter) -> bool { + Level::$lvl.ge(other) + } + } + )* + }; +} + +define_static_levels![ + StaticLevelError => Error, + StaticLevelWarn => Warn, + StaticLevelInfo => Info, + StaticLevelDebug => Debug, + StaticLevelTrace => Trace, +]; + +// `LogTarget`. + +impl LogTarget for &str { + #[inline] + fn with(self, _module_path: &'static str, f: impl FnOnce(&str)) { + f(self) + } +} + +#[derive(Debug)] +pub struct TargetIsModulePath; + +impl LogTarget for TargetIsModulePath { + #[inline] + fn with(self, module_path: &'static str, f: impl FnOnce(&str)) { + f(module_path) + } +} + +// `LogKVs`. + +impl LogKVs for &[(&str, &LogKvValue<'_>)] { + #[inline] + fn with(self, f: impl FnOnce(&[(&str, &LogKvValue)])) { + f(self) + } +} + +#[derive(Debug)] +pub struct EmptyKVs; + +impl LogKVs for EmptyKVs { + #[inline] + fn with(self, f: impl FnOnce(&[(&str, &LogKvValue)])) { + f(&[]) + } +} + +// Log functions. + +fn log_0( + &(module_path, file): &'static (&'static str, &'static str), + line: u32, + args: Arguments, + level: Level, + target: &str, + kvs: &[(&str, &LogKvValue)], +) { + #[cfg(not(feature = "kv_unstable"))] + if !kvs.is_empty() { + panic!( + "key-value support is experimental and must be enabled using the `kv_unstable` feature" + ); + } + + let mut builder = Record::builder(); + + builder + .args(args) + .level(level) + .target(target) + .module_path_static(Some(module_path)) + .file_static(Some(file)) + .line(Some(line)); + + #[cfg(feature = "kv_unstable")] + builder.key_values(&kvs); + + crate::logger().log(&builder.build()); +} + +fn log_1( + module_path_and_file: &'static (&'static str, &'static str), + line: u32, + args: Arguments, + level: Level, + target: &str, + kvs: K, +) where + K: LogKVs, +{ + kvs.with(|kvs| log_0(module_path_and_file, line, args, level, target, kvs)); +} + +fn log_2( + module_path_and_file: &'static (&'static str, &'static str), + line: u32, + args: Arguments, + level: Level, + target: T, + kvs: K, +) where + T: LogTarget, + K: LogKVs, +{ + target.with(module_path_and_file.0, |target| { + log_1( + module_path_and_file, + line, + args, + level.into_log_level(), + target, + kvs, + ) + }); +} + +pub fn log_3( + module_path_and_file: &'static (&'static str, &'static str), + line: u32, + args: Arguments, + level: L, + target: T, + kvs: K, +) where + L: LogLevel, + T: LogTarget, + K: LogKVs, +{ + log_2( + module_path_and_file, + line, + args, + level.into_log_level(), + target, + kvs, + ) +} + +pub fn log( + module_path_and_file: &'static (&'static str, &'static str), + line: u32, + args: A, + level: L, + target: T, + kvs: K, +) where + A: LogArgs, + L: LogLevel, + T: LogTarget, + K: LogKVs, +{ + args.with(|args| log_3(module_path_and_file, line, args, level, target, kvs)) +} + +pub fn enabled(level: Level, target: &str) -> bool { + crate::logger().enabled(&Metadata::builder().level(level).target(target).build()) +} + +pub const fn is_literal(s: &str) -> bool { + let s = s.as_bytes(); + let n = s.len(); + let mut i = 0; + + while i < n { + if matches!(s[i], b'{' | b'}') { + return false; + } + + i += 1; + } + + true +} diff --git a/src/lib.rs b/src/lib.rs index f19f00f99..c8df4b21c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1466,65 +1466,7 @@ pub fn logger() -> &'static dyn Log { // WARNING: this is not part of the crate's public API and is subject to change at any time #[doc(hidden)] -#[cfg(not(feature = "kv_unstable"))] -pub fn __private_api_log( - args: fmt::Arguments, - level: Level, - &(target, module_path, file, line): &(&str, &'static str, &'static str, u32), - kvs: Option<&[(&str, &str)]>, -) { - if kvs.is_some() { - panic!( - "key-value support is experimental and must be enabled using the `kv_unstable` feature" - ) - } - - logger().log( - &Record::builder() - .args(args) - .level(level) - .target(target) - .module_path_static(Some(module_path)) - .file_static(Some(file)) - .line(Some(line)) - .build(), - ); -} - -// WARNING: this is not part of the crate's public API and is subject to change at any time -#[doc(hidden)] -#[cfg(feature = "kv_unstable")] -pub fn __private_api_log( - args: fmt::Arguments, - level: Level, - &(target, module_path, file, line): &(&str, &'static str, &'static str, u32), - kvs: Option<&[(&str, &dyn kv::ToValue)]>, -) { - logger().log( - &Record::builder() - .args(args) - .level(level) - .target(target) - .module_path_static(Some(module_path)) - .file_static(Some(file)) - .line(Some(line)) - .key_values(&kvs) - .build(), - ); -} - -// WARNING: this is not part of the crate's public API and is subject to change at any time -#[doc(hidden)] -pub fn __private_api_enabled(level: Level, target: &str) -> bool { - logger().enabled(&Metadata::builder().level(level).target(target).build()) -} - -// WARNING: this is not part of the crate's public API and is subject to change at any time -#[doc(hidden)] -pub mod __private_api { - pub use std::option::Option; - pub use std::{file, format_args, line, module_path, stringify}; -} +pub mod __private_api; /// The statically resolved maximum log level. /// diff --git a/src/macros.rs b/src/macros.rs index a66c92a4e..f659687a4 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -8,6 +8,87 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#[doc(hidden)] +#[macro_export] +macro_rules! __internal_log { + (@helper $target:expr, $lvl:expr, $kvs:expr, $arg:literal $(,)?) => ({ + let lvl = $lvl; + if lvl <= $crate::STATIC_MAX_LEVEL && lvl <= $crate::max_level() { + const IS_LITERAL: bool = $crate::__private_api::is_literal($arg); + + if IS_LITERAL { + $crate::__private_api::log( + &( + $crate::__private_api::module_path!(), + $crate::__private_api::file!(), + ), + $crate::__private_api::line!(), + $arg, + $lvl, + $target, + $kvs, + ); + } else { + $crate::__private_api::log( + &( + $crate::__private_api::module_path!(), + $crate::__private_api::file!(), + ), + $crate::__private_api::line!(), + $crate::__private_api::format_args!($arg), + $lvl, + $target, + $kvs, + ); + } + } + }); + + (@helper $target:expr, $lvl:expr, $kvs:expr, $($arg:tt)+) => ({ + let lvl = $lvl; + if lvl <= $crate::STATIC_MAX_LEVEL && lvl <= $crate::max_level() { + $crate::__private_api::log( + &( + $crate::__private_api::module_path!(), + $crate::__private_api::file!(), + ), + $crate::__private_api::line!(), + $crate::__private_api::format_args!($($arg)+), + $lvl, + $target, + $kvs, + ); + } + }); + + // log!(target: "my_target", Level::Info, key1 = 42, key2 = true; "a {} event", "log"); + (target: $target:expr, $lvl:expr, $($key:tt = $value:expr),+; $($arg:tt)+) => ($crate::__internal_log!( + @helper + $target, + $lvl, + &[ + $(($crate::__log_key!($key), &$value as &$crate::__private_api::LogKvValue)),+ + ] as &[_], + $($arg)+ + )); + + // log!(target: "my_target", Level::Info, "a {} event", "log"); + (target: $target:expr, $lvl:expr, $($arg:tt)+) => ($crate::__internal_log!( + @helper + $target, + $lvl, + $crate::__private_api::EmptyKVs, + $($arg)+ + )); + + // log!(Level::Info, "a log event") + ($lvl:expr, $($arg:tt)+) => ($crate::__internal_log!( + target: $crate::__private_api::TargetIsModulePath, + $lvl, + $($arg)+ + )); +} + /// The standard logging macro. /// /// This macro will generically log with the specified `Level` and `format!` @@ -30,33 +111,25 @@ #[macro_export] macro_rules! log { // log!(target: "my_target", Level::Info, key1 = 42, key2 = true; "a {} event", "log"); - (target: $target:expr, $lvl:expr, $($key:tt = $value:expr),+; $($arg:tt)+) => ({ - let lvl = $lvl; - if lvl <= $crate::STATIC_MAX_LEVEL && lvl <= $crate::max_level() { - $crate::__private_api_log( - $crate::__private_api::format_args!($($arg)+), - lvl, - &($target, $crate::__private_api::module_path!(), $crate::__private_api::file!(), $crate::__private_api::line!()), - $crate::__private_api::Option::Some(&[$(($crate::__log_key!($key), &$value)),+]) - ); - } - }); + (target: $target:expr, $lvl:expr, $($key:tt = $value:expr),+; $($arg:tt)+) => ($crate::__internal_log!( + target: $crate::__private_api::identity::<&$crate::__private_api::str>($target), + $crate::__private_api::identity::<$crate::Level>($lvl), + $($key = $value),+; + $($arg)+ + )); // log!(target: "my_target", Level::Info, "a {} event", "log"); - (target: $target:expr, $lvl:expr, $($arg:tt)+) => ({ - let lvl = $lvl; - if lvl <= $crate::STATIC_MAX_LEVEL && lvl <= $crate::max_level() { - $crate::__private_api_log( - $crate::__private_api::format_args!($($arg)+), - lvl, - &($target, $crate::__private_api::module_path!(), $crate::__private_api::file!(), $crate::__private_api::line!()), - $crate::__private_api::Option::None, - ); - } - }); + (target: $target:expr, $lvl:expr, $($arg:tt)+) => ($crate::__internal_log!( + target: $crate::__private_api::identity::<&$crate::__private_api::str>($target), + $crate::__private_api::identity::<$crate::Level>($lvl), + $($arg)+ + )); // log!(Level::Info, "a log event") - ($lvl:expr, $($arg:tt)+) => ($crate::log!(target: $crate::__private_api::module_path!(), $lvl, $($arg)+)); + ($lvl:expr, $($arg:tt)+) => ($crate::__internal_log!( + $crate::__private_api::identity::<$crate::Level>($lvl), + $($arg)+ + )); } /// Logs a message at the error level. @@ -77,10 +150,17 @@ macro_rules! log { macro_rules! error { // error!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log") // error!(target: "my_target", "a {} event", "log") - (target: $target:expr, $($arg:tt)+) => ($crate::log!(target: $target, $crate::Level::Error, $($arg)+)); + (target: $target:expr, $($arg:tt)+) => ($crate::__internal_log!( + target: $crate::__private_api::identity::<&$crate::__private_api::str>($target), + $crate::__private_api::StaticLevelError, + $($arg)+ + )); // error!("a {} event", "log") - ($($arg:tt)+) => ($crate::log!($crate::Level::Error, $($arg)+)) + ($($arg:tt)+) => ($crate::__internal_log!( + $crate::__private_api::StaticLevelError, + $($arg)+ + )) } /// Logs a message at the warn level. @@ -101,10 +181,17 @@ macro_rules! error { macro_rules! warn { // warn!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log") // warn!(target: "my_target", "a {} event", "log") - (target: $target:expr, $($arg:tt)+) => ($crate::log!(target: $target, $crate::Level::Warn, $($arg)+)); + (target: $target:expr, $($arg:tt)+) => ($crate::__internal_log!( + target: $crate::__private_api::identity::<&$crate::__private_api::str>($target), + $crate::__private_api::StaticLevelWarn, + $($arg)+ + )); // warn!("a {} event", "log") - ($($arg:tt)+) => ($crate::log!($crate::Level::Warn, $($arg)+)) + ($($arg:tt)+) => ($crate::__internal_log!( + $crate::__private_api::StaticLevelWarn, + $($arg)+ + )) } /// Logs a message at the info level. @@ -127,10 +214,17 @@ macro_rules! warn { macro_rules! info { // info!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log") // info!(target: "my_target", "a {} event", "log") - (target: $target:expr, $($arg:tt)+) => ($crate::log!(target: $target, $crate::Level::Info, $($arg)+)); + (target: $target:expr, $($arg:tt)+) => ($crate::__internal_log!( + target: $crate::__private_api::identity::<&$crate::__private_api::str>($target), + $crate::__private_api::StaticLevelInfo, + $($arg)+ + )); // info!("a {} event", "log") - ($($arg:tt)+) => ($crate::log!($crate::Level::Info, $($arg)+)) + ($($arg:tt)+) => ($crate::__internal_log!( + $crate::__private_api::StaticLevelInfo, + $($arg)+ + )) } /// Logs a message at the debug level. @@ -152,10 +246,17 @@ macro_rules! info { macro_rules! debug { // debug!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log") // debug!(target: "my_target", "a {} event", "log") - (target: $target:expr, $($arg:tt)+) => ($crate::log!(target: $target, $crate::Level::Debug, $($arg)+)); + (target: $target:expr, $($arg:tt)+) => ($crate::__internal_log!( + target: $crate::__private_api::identity::<&$crate::__private_api::str>($target), + $crate::__private_api::StaticLevelDebug, + $($arg)+ + )); // debug!("a {} event", "log") - ($($arg:tt)+) => ($crate::log!($crate::Level::Debug, $($arg)+)) + ($($arg:tt)+) => ($crate::__internal_log!( + $crate::__private_api::StaticLevelDebug, + $($arg)+ + )) } /// Logs a message at the trace level. @@ -179,10 +280,17 @@ macro_rules! debug { macro_rules! trace { // trace!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log") // trace!(target: "my_target", "a {} event", "log") - (target: $target:expr, $($arg:tt)+) => ($crate::log!(target: $target, $crate::Level::Trace, $($arg)+)); + (target: $target:expr, $($arg:tt)+) => ($crate::__internal_log!( + target: $crate::__private_api::identity::<&$crate::__private_api::str>($target), + $crate::__private_api::StaticLevelTrace, + $($arg)+ + )); // trace!("a {} event", "log") - ($($arg:tt)+) => ($crate::log!($crate::Level::Trace, $($arg)+)) + ($($arg:tt)+) => ($crate::__internal_log!( + $crate::__private_api::StaticLevelTrace, + $($arg)+ + )) } /// Determines if a message logged at the specified level in that module will @@ -217,7 +325,7 @@ macro_rules! log_enabled { let lvl = $lvl; lvl <= $crate::STATIC_MAX_LEVEL && lvl <= $crate::max_level() - && $crate::__private_api_enabled(lvl, $target) + && $crate::__private_api::enabled(lvl, $target) }}; ($lvl:expr) => { $crate::log_enabled!(target: $crate::__private_api::module_path!(), $lvl)