diff --git a/src/lib.rs b/src/lib.rs index 95f261d78..f89e2f7dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -341,6 +341,8 @@ use std::str::FromStr; #[macro_use] mod macros; +#[doc(hidden)] +pub mod private_api; mod serde; #[cfg(feature = "kv_unstable")] @@ -1464,55 +1466,6 @@ 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 { diff --git a/src/macros.rs b/src/macros.rs index 84143dd37..d60f2dbf7 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -8,6 +8,66 @@ // 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:tt)+) => ({ + let lvl = $lvl; + if lvl <= $crate::STATIC_MAX_LEVEL && lvl <= $crate::max_level() { + match (::std::format_args!($($arg)+), $target, $kvs) { + (args, target, kvs) => if let ::std::option::Option::Some(literal) = ::std::fmt::Arguments::as_str(&args) { + $crate::private_api::log( + ::std::module_path!(), + ::std::file!(), + ::std::line!(), + $lvl, + literal, + target, + kvs, + ); + } else { + $crate::private_api::log( + ::std::module_path!(), + ::std::file!(), + ::std::line!(), + $lvl, + args, + 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!` @@ -27,36 +87,25 @@ /// data.0, data.1, private_data); /// # } /// ``` -#[macro_export(local_inner_macros)] +#[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( - __log_format_args!($($arg)+), - lvl, - &($target, __log_module_path!(), __log_file!(), __log_line!()), - $crate::__private_api::Option::Some(&[$((__log_key!($key), &$value)),+]) - ); - } - }); + (target: $target:expr, $lvl:expr, $($key:tt = $value:expr),+; $($arg:tt)+) => ($crate::__internal_log!( + target: ::std::convert::identity::<&::std::primitive::str>($target), + ::std::convert::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( - __log_format_args!($($arg)+), - lvl, - &($target, __log_module_path!(), __log_file!(), __log_line!()), - $crate::__private_api::Option::None, - ); - } - }); + (target: $target:expr, $lvl:expr, $($arg:tt)+) => ($crate::__internal_log!( + target: ::std::convert::identity::<&::std::primitive::str>($target), + ::std::convert::identity::<$crate::Level>($lvl), + $($arg)+ + )); // log!(Level::Info, "a log event") - ($lvl:expr, $($arg:tt)+) => (log!(target: __log_module_path!(), $lvl, $($arg)+)); + ($lvl:expr, $($arg:tt)+) => ($crate::__internal_log!(::std::convert::identity::<$crate::Level>($lvl), $($arg)+)); } /// Logs a message at the error level. @@ -73,14 +122,18 @@ macro_rules! log { /// error!(target: "app_events", "App Error: {}, Port: {}", err_info, 22); /// # } /// ``` -#[macro_export(local_inner_macros)] +#[macro_export] 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)+) => (log!(target: $target, $crate::Level::Error, $($arg)+)); + (target: $target:expr, $($arg:tt)+) => ($crate::__internal_log!( + target: ::std::convert::identity::<&::std::primitive::str>($target), + $crate::private_api::StaticLevelError, + $($arg)+ + )); // error!("a {} event", "log") - ($($arg:tt)+) => (log!($crate::Level::Error, $($arg)+)) + ($($arg:tt)+) => ($crate::__internal_log!($crate::private_api::StaticLevelError, $($arg)+)) } /// Logs a message at the warn level. @@ -97,14 +150,18 @@ macro_rules! error { /// warn!(target: "input_events", "App received warning: {}", warn_description); /// # } /// ``` -#[macro_export(local_inner_macros)] +#[macro_export] 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)+) => (log!(target: $target, $crate::Level::Warn, $($arg)+)); + (target: $target:expr, $($arg:tt)+) => ($crate::__internal_log!( + target: ::std::convert::identity::<&::std::primitive::str>($target), + $crate::private_api::StaticLevelWarn, + $($arg)+ + )); // warn!("a {} event", "log") - ($($arg:tt)+) => (log!($crate::Level::Warn, $($arg)+)) + ($($arg:tt)+) => ($crate::__internal_log!($crate::private_api::StaticLevelWarn, $($arg)+)) } /// Logs a message at the info level. @@ -123,14 +180,18 @@ macro_rules! warn { /// conn_info.port, conn_info.speed); /// # } /// ``` -#[macro_export(local_inner_macros)] +#[macro_export] 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)+) => (log!(target: $target, $crate::Level::Info, $($arg)+)); + (target: $target:expr, $($arg:tt)+) => ($crate::__internal_log!( + target: ::std::convert::identity::<&::std::primitive::str>($target), + $crate::private_api::StaticLevelInfo, + $($arg)+ + )); // info!("a {} event", "log") - ($($arg:tt)+) => (log!($crate::Level::Info, $($arg)+)) + ($($arg:tt)+) => ($crate::__internal_log!($crate::private_api::StaticLevelInfo, $($arg)+)) } /// Logs a message at the debug level. @@ -148,14 +209,18 @@ macro_rules! info { /// debug!(target: "app_events", "New position: x: {}, y: {}", pos.x, pos.y); /// # } /// ``` -#[macro_export(local_inner_macros)] +#[macro_export] 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)+) => (log!(target: $target, $crate::Level::Debug, $($arg)+)); + (target: $target:expr, $($arg:tt)+) => ($crate::__internal_log!( + target: ::std::convert::identity::<&::std::primitive::str>($target), + $crate::private_api::StaticLevelDebug, + $($arg)+ + )); // debug!("a {} event", "log") - ($($arg:tt)+) => (log!($crate::Level::Debug, $($arg)+)) + ($($arg:tt)+) => ($crate::__internal_log!($crate::private_api::StaticLevelDebug, $($arg)+)) } /// Logs a message at the trace level. @@ -175,14 +240,18 @@ macro_rules! debug { /// if pos.y >= 0.0 { "positive" } else { "negative" }); /// # } /// ``` -#[macro_export(local_inner_macros)] +#[macro_export] 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)+) => (log!(target: $target, $crate::Level::Trace, $($arg)+)); + (target: $target:expr, $($arg:tt)+) => ($crate::__internal_log!( + target: ::std::convert::identity::<&::std::primitive::str>($target), + $crate::private_api::StaticLevelTrace, + $($arg)+ + )); // trace!("a {} event", "log") - ($($arg:tt)+) => (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 @@ -211,7 +280,7 @@ macro_rules! trace { /// # fn expensive_call() -> Data { Data { x: 0, y: 0 } } /// # fn main() {} /// ``` -#[macro_export(local_inner_macros)] +#[macro_export] macro_rules! log_enabled { (target: $target:expr, $lvl:expr) => {{ let lvl = $lvl; @@ -220,49 +289,7 @@ macro_rules! log_enabled { && $crate::__private_api_enabled(lvl, $target) }}; ($lvl:expr) => { - log_enabled!(target: __log_module_path!(), $lvl) - }; -} - -// The log macro above cannot invoke format_args directly because it uses -// local_inner_macros. A format_args invocation there would resolve to -// $crate::format_args which does not exist. Instead invoke format_args here -// outside of local_inner_macros so that it resolves (probably) to -// core::format_args or std::format_args. Same for the several macros that -// follow. -// -// This is a workaround until we drop support for pre-1.30 compilers. At that -// point we can remove use of local_inner_macros, use $crate:: when invoking -// local macros, and invoke format_args directly. -#[doc(hidden)] -#[macro_export] -macro_rules! __log_format_args { - ($($args:tt)*) => { - format_args!($($args)*) - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __log_module_path { - () => { - module_path!() - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __log_file { - () => { - file!() - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __log_line { - () => { - line!() + log_enabled!(target: ::std::module_path!(), $lvl) }; } diff --git a/src/private_api.rs b/src/private_api.rs new file mode 100644 index 000000000..c1d0c62d5 --- /dev/null +++ b/src/private_api.rs @@ -0,0 +1,265 @@ +use self::sealed::{LogArgs, LogKVs, LogLevel, LogTarget}; +use crate::{Level, LevelFilter, Record}; +use std::cmp::Ordering; +use std::fmt::Arguments; + +#[cfg(feature = "kv_unstable")] +pub type LogKvValue = dyn crate::kv::value::ToValue; + +#[cfg(not(feature = "kv_unstable"))] +pub type LogKvValue = str; + +mod sealed { + use crate::Level; + use std::fmt::Arguments; + + pub trait LogLevel { + fn into_log_level(self) -> Level; + } + + pub trait LogArgs { + fn with(self, f: impl FnOnce(Arguments) -> R) -> R; + } + + pub trait LogTarget { + fn with(self, module_path: &'static str, f: impl FnOnce(&str) -> R) -> R; + } + + pub trait LogKVs { + fn with(self, f: impl FnOnce(&[(&str, &super::LogKvValue)]) -> R) -> R; + } +} + +// `LogLevel`. + +impl LogLevel for Level { + 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, +]; + +// `LogArgs`. + +impl LogArgs for Arguments<'_> { + #[inline] + fn with(self, f: impl FnOnce(Arguments) -> R) -> R { + f(self) + } +} + +impl LogArgs for &str { + #[inline] + fn with(self, f: impl FnOnce(Arguments) -> R) -> R { + f(format_args!("{self}")) + } +} + +// `LogTarget`. + +impl LogTarget for &str { + #[inline] + fn with(self, _module_path: &'static str, f: impl FnOnce(&str) -> R) -> R { + f(self) + } +} + +#[derive(Debug)] +pub struct TargetIsModulePath; + +impl LogTarget for TargetIsModulePath { + #[inline] + fn with(self, module_path: &'static str, f: impl FnOnce(&str) -> R) -> R { + f(module_path) + } +} + +// `LogKVs`. + +impl LogKVs for &[(&str, &LogKvValue)] { + #[inline] + fn with(self, f: impl FnOnce(&[(&str, &LogKvValue)]) -> R) -> R { + f(self) + } +} + +#[derive(Debug)] +pub struct EmptyKVs; + +impl LogKVs for EmptyKVs { + #[inline] + fn with(self, f: impl FnOnce(&[(&str, &LogKvValue)]) -> R) -> R { + f(&[]) + } +} + +// Log functions. + +fn log_0( + module_path: &'static str, + file: &'static str, + line: u32, + level: Level, + args: Arguments, + 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: &'static str, + file: &'static str, + line: u32, + level: Level, + args: Arguments, + target: &str, + kvs: K, +) where + K: LogKVs, +{ + kvs.with(|kvs| log_0(module_path, file, line, level, args, target, kvs)); +} + +fn log_2( + module_path: &'static str, + file: &'static str, + line: u32, + level: Level, + args: Arguments, + target: T, + kvs: K, +) where + T: LogTarget, + K: LogKVs, +{ + target.with(module_path, |target| { + log_1( + module_path, + file, + line, + level.into_log_level(), + args, + target, + kvs, + ) + }); +} + +fn log_3( + module_path: &'static str, + file: &'static str, + line: u32, + level: Level, + args: A, + target: T, + kvs: K, +) where + A: LogArgs, + T: LogTarget, + K: LogKVs, +{ + args.with(|args| log_2(module_path, file, line, level, args, target, kvs)); +} + +pub fn log( + module_path: &'static str, + file: &'static str, + line: u32, + level: L, + args: A, + target: T, + kvs: K, +) where + L: LogLevel, + A: LogArgs, + T: LogTarget, + K: LogKVs, +{ + log_3( + module_path, + file, + line, + level.into_log_level(), + args, + target, + kvs, + ) +}