From 57c68ff879d868e5bd5f2f16d81ac62b3fe29689 Mon Sep 17 00:00:00 2001 From: Matthew Yacobucci Date: Mon, 21 Aug 2023 13:52:06 -0600 Subject: [PATCH] feat: Add debug log mask support (#40) * feat: Add debug log mask support NGINX supports multiple debug masks to customize logging. We use Rust's type system to represent these as an enum and ease a developer's usage. They can call the ngx_log_debug_mask with any of the supported enum elements. To aid in keeping in sync with NGINX primitives a unit test will fail when the current FIRST and LAST log masks do not align any longer. --- src/log.rs | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/src/log.rs b/src/log.rs index 37d4ac4..f302a92 100644 --- a/src/log.rs +++ b/src/log.rs @@ -1,3 +1,13 @@ +/// Utility function to provide typed checking of the mask's field state. +#[inline(always)] +pub fn check_mask(mask: DebugMask, log_level: usize) -> bool { + let mask_bits: u32 = mask.into(); + if log_level & mask_bits as usize == 0 { + return false; + } + true +} + /// Write to logger at a specified level. /// /// See [Logging](https://nginx.org/en/docs/dev/development_guide.html#logging) @@ -27,3 +37,171 @@ macro_rules! ngx_log_debug_http { $crate::ngx_log_debug!(log, $($arg)*); } } + +/// Debug masks for use with ngx_log_debug_mask, these represent the only accepted values for the +/// mask. +#[derive(Debug)] +pub enum DebugMask { + /// Aligns to the NGX_LOG_DEBUG_CORE mask. + Core, + /// Aligns to the NGX_LOG_DEBUG_ALLOC mask. + Alloc, + /// Aligns to the NGX_LOG_DEBUG_MUTEX mask. + Mutex, + /// Aligns to the NGX_LOG_DEBUG_EVENT mask. + Event, + /// Aligns to the NGX_LOG_DEBUG_HTTP mask. + Http, + /// Aligns to the NGX_LOG_DEBUG_MAIL mask. + Mail, + /// Aligns to the NGX_LOG_DEBUG_STREAM mask. + Stream, +} + +impl TryFrom for DebugMask { + type Error = u32; + + fn try_from(value: u32) -> Result { + match value { + crate::ffi::NGX_LOG_DEBUG_CORE => Ok(DebugMask::Core), + crate::ffi::NGX_LOG_DEBUG_ALLOC => Ok(DebugMask::Alloc), + crate::ffi::NGX_LOG_DEBUG_MUTEX => Ok(DebugMask::Mutex), + crate::ffi::NGX_LOG_DEBUG_EVENT => Ok(DebugMask::Event), + crate::ffi::NGX_LOG_DEBUG_HTTP => Ok(DebugMask::Http), + crate::ffi::NGX_LOG_DEBUG_MAIL => Ok(DebugMask::Mail), + crate::ffi::NGX_LOG_DEBUG_STREAM => Ok(DebugMask::Stream), + _ => Err(0), + } + } +} + +impl From for u32 { + fn from(value: DebugMask) -> Self { + match value { + DebugMask::Core => crate::ffi::NGX_LOG_DEBUG_CORE, + DebugMask::Alloc => crate::ffi::NGX_LOG_DEBUG_ALLOC, + DebugMask::Mutex => crate::ffi::NGX_LOG_DEBUG_MUTEX, + DebugMask::Event => crate::ffi::NGX_LOG_DEBUG_EVENT, + DebugMask::Http => crate::ffi::NGX_LOG_DEBUG_HTTP, + DebugMask::Mail => crate::ffi::NGX_LOG_DEBUG_MAIL, + DebugMask::Stream => crate::ffi::NGX_LOG_DEBUG_STREAM, + } + } +} + +/// Log with requested debug mask. +/// +/// **NOTE:** This macro supports `DebugMask::Http` (`NGX_LOG_DEBUG_HTTP`), however, if you have +/// access to a Request object via an http handler it can be more convenient and readable to use the +/// `ngx_log_debug_http` macro instead. +/// +/// See https://nginx.org/en/docs/dev/development_guide.html#logging for details and available +/// masks. +#[macro_export] +macro_rules! ngx_log_debug_mask { + ( DebugMask::Core, $log:expr, $($arg:tt)* ) => ({ + let log_level = unsafe { (*$log).log_level }; + if $crate::log::check_mask(DebugMask::Core, log_level) { + let level = $crate::ffi::NGX_LOG_DEBUG as $crate::ffi::ngx_uint_t; + let fmt = ::std::ffi::CString::new("%s").unwrap(); + let c_message = ::std::ffi::CString::new(format!($($arg)*)).unwrap(); + unsafe { + $crate::ffi::ngx_log_error_core(level, $log, 0, fmt.as_ptr(), c_message.as_ptr()); + } + } + }); + ( DebugMask::Alloc, $log:expr, $($arg:tt)* ) => ({ + let log_level = unsafe { (*$log).log_level }; + if $crate::log::check_mask(DebugMask::Alloc, log_level) { + let level = $crate::ffi::NGX_LOG_DEBUG as $crate::ffi::ngx_uint_t; + let fmt = ::std::ffi::CString::new("%s").unwrap(); + let c_message = ::std::ffi::CString::new(format!($($arg)*)).unwrap(); + unsafe { + $crate::ffi::ngx_log_error_core(level, $log, 0, fmt.as_ptr(), c_message.as_ptr()); + } + } + }); + ( DebugMask::Mutex, $log:expr, $($arg:tt)* ) => ({ + let log_level = unsafe { (*$log).log_level }; + if $crate::log::check_mask(DebugMask::Mutex, log_level) { + let level = $crate::ffi::NGX_LOG_DEBUG as $crate::ffi::ngx_uint_t; + let fmt = ::std::ffi::CString::new("%s").unwrap(); + let c_message = ::std::ffi::CString::new(format!($($arg)*)).unwrap(); + unsafe { + $crate::ffi::ngx_log_error_core(level, $log, 0, fmt.as_ptr(), c_message.as_ptr()); + } + } + }); + ( DebugMask::Event, $log:expr, $($arg:tt)* ) => ({ + let log_level = unsafe { (*$log).log_level }; + if $crate::log::check_mask(DebugMask::Event, log_level) { + let level = $crate::ffi::NGX_LOG_DEBUG as $crate::ffi::ngx_uint_t; + let fmt = ::std::ffi::CString::new("%s").unwrap(); + let c_message = ::std::ffi::CString::new(format!($($arg)*)).unwrap(); + unsafe { + $crate::ffi::ngx_log_error_core(level, $log, 0, fmt.as_ptr(), c_message.as_ptr()); + } + } + }); + ( DebugMask::Http, $log:expr, $($arg:tt)* ) => ({ + let log_level = unsafe { (*$log).log_level }; + if $crate::log::check_mask(DebugMask::Http, log_level) { + let level = $crate::ffi::NGX_LOG_DEBUG as $crate::ffi::ngx_uint_t; + let fmt = ::std::ffi::CString::new("%s").unwrap(); + let c_message = ::std::ffi::CString::new(format!($($arg)*)).unwrap(); + unsafe { + $crate::ffi::ngx_log_error_core(level, $log, 0, fmt.as_ptr(), c_message.as_ptr()); + } + } + }); + ( DebugMask::Mail, $log:expr, $($arg:tt)* ) => ({ + let log_level = unsafe { (*$log).log_level }; + if $crate::log::check_mask(DebugMask::Mail, log_level) { + let level = $crate::ffi::NGX_LOG_DEBUG as $crate::ffi::ngx_uint_t; + let fmt = ::std::ffi::CString::new("%s").unwrap(); + let c_message = ::std::ffi::CString::new(format!($($arg)*)).unwrap(); + unsafe { + $crate::ffi::ngx_log_error_core(level, $log, 0, fmt.as_ptr(), c_message.as_ptr()); + } + } + }); + ( DebugMask::Stream, $log:expr, $($arg:tt)* ) => ({ + let log_level = unsafe { (*$log).log_level }; + if $crate::log::check_mask(DebugMask::Stream, log_level) { + let level = $crate::ffi::NGX_LOG_DEBUG as $crate::ffi::ngx_uint_t; + let fmt = ::std::ffi::CString::new("%s").unwrap(); + let c_message = ::std::ffi::CString::new(format!($($arg)*)).unwrap(); + unsafe { + $crate::ffi::ngx_log_error_core(level, $log, 0, fmt.as_ptr(), c_message.as_ptr()); + } + } + }); +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_mask_lower_bound() { + assert!(>::into(DebugMask::Core) == crate::ffi::NGX_LOG_DEBUG_FIRST); + } + #[test] + fn test_mask_upper_bound() { + assert!(>::into(DebugMask::Stream) == crate::ffi::NGX_LOG_DEBUG_LAST); + } + #[test] + fn test_check_mask() { + struct MockLog { + log_level: usize, + } + let mock = MockLog { log_level: 16 }; + + let mut r = check_mask(DebugMask::Core, mock.log_level); + assert!(r == true); + + r = check_mask(DebugMask::Alloc, mock.log_level); + assert!(r == false); + } +}