diff --git a/src/lib.rs b/src/lib.rs index 52f8e88..906af2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,9 +59,6 @@ pub mod http; /// The log module. /// /// This module provides an interface into the NGINX logger framework. -/// -/// This module is temporally available only with `std` feature. -#[cfg(feature = "std")] pub mod log; /// Define modules exported by this library. diff --git a/src/log.rs b/src/log.rs index b8b9f43..3eb18e4 100644 --- a/src/log.rs +++ b/src/log.rs @@ -1,3 +1,15 @@ +use core::cmp; +use core::fmt::{self, Write}; +use core::mem::MaybeUninit; + +use crate::ffi::NGX_MAX_ERROR_STR; + +/// Size of the static buffer used to format log messages. +/// +/// Approximates the remaining space in `u_char[NGX_MAX_ERROR_STR]` after writing the standard +/// prefix +pub const LOG_BUFFER_SIZE: usize = NGX_MAX_ERROR_STR as usize - b"1970/01/01 00:00:00 [info] 1#1: ".len(); + /// Utility function to provide typed checking of the mask's field state. #[inline(always)] pub fn check_mask(mask: DebugMask, log_level: usize) -> bool { @@ -8,6 +20,21 @@ pub fn check_mask(mask: DebugMask, log_level: usize) -> bool { true } +/// Format args into a provided buffer +// May produce incomplete UTF-8 sequences. But any writes to `ngx_log_t` already can be truncated, +// so nothing we can do here. +#[inline] +pub fn write_fmt<'a>(buf: &'a mut [MaybeUninit], args: fmt::Arguments<'_>) -> &'a [u8] { + if let Some(str) = args.as_str() { + str.as_bytes() + } else { + let mut buf = LogBuf::from(buf); + // nothing we can or want to do on errors + let _ = buf.write_fmt(args); + buf.filled() + } +} + /// Write to logger at a specified level. /// /// See [Logging](https://nginx.org/en/docs/dev/development_guide.html#logging) @@ -18,8 +45,8 @@ macro_rules! ngx_log_error { let log = $log; let level = $level as $crate::ffi::ngx_uint_t; if level < unsafe { (*log).log_level } { - let message = ::std::format!($($arg)+); - let message = message.as_bytes(); + let mut buf = [const { ::core::mem::MaybeUninit::::uninit() }; $crate::log::LOG_BUFFER_SIZE]; + let message = $crate::log::write_fmt(&mut buf, format_args!($($arg)+)); unsafe { $crate::ffi::ngx_log_error_core(level, log, 0, c"%*s".as_ptr(), message.len(), message.as_ptr()); } @@ -34,8 +61,8 @@ macro_rules! ngx_conf_log_error { let cf: *mut $crate::ffi::ngx_conf_t = $cf; let level = $level as $crate::ffi::ngx_uint_t; if level < unsafe { (*(*cf).log).log_level } { - let message = ::std::format!($($arg)+); - let message = message.as_bytes(); + let mut buf = [const { ::core::mem::MaybeUninit::::uninit() }; $crate::log::LOG_BUFFER_SIZE]; + let message = $crate::log::write_fmt(&mut buf, format_args!($($arg)+)); unsafe { $crate::ffi::ngx_conf_log_error(level, cf, 0, c"%*s".as_ptr(), message.len(), message.as_ptr()); } @@ -50,8 +77,8 @@ macro_rules! ngx_log_debug { let log = $log; if $crate::log::check_mask($mask, unsafe { (*log).log_level }) { let level = $crate::ffi::NGX_LOG_DEBUG as $crate::ffi::ngx_uint_t; - let message = format!($($arg)+); - let message = message.as_bytes(); + let mut buf = [const { ::core::mem::MaybeUninit::::uninit() }; $crate::log::LOG_BUFFER_SIZE]; + let message = $crate::log::write_fmt(&mut buf, format_args!($($arg)+)); unsafe { $crate::ffi::ngx_log_error_core(level, log, 0, c"%*s".as_ptr(), message.len(), message.as_ptr()); } @@ -161,6 +188,52 @@ impl From for u32 { } } +/// Minimal subset of unstable core::io::{BorrowedBuf,BorrowedCursor} +struct LogBuf<'data> { + buf: &'data mut [MaybeUninit], + filled: usize, +} + +impl<'data> LogBuf<'data> { + pub fn filled(&self) -> &'data [u8] { + // SAFETY: valid bytes have been written to self.buf[..self.filled] + unsafe { + let buf = self.buf.get_unchecked(..self.filled); + // inlined MaybeUninit::slice_assume_init_ref + &*(buf as *const [MaybeUninit] as *const [u8]) + } + } + + pub fn append(&mut self, buf: &[u8]) -> &mut Self { + let n = cmp::min(self.buf.len() - self.filled, buf.len()); + unsafe { + // SAFETY: The source buf has at least n bytes + let src = buf.get_unchecked(..n); + // SAFETY: &[u8] and &[MaybeUninit] have the same layout + let src: &[MaybeUninit] = core::mem::transmute(src); + // SAFETY: self.buf has at least n bytes available after self.filled + self.buf + .get_unchecked_mut(self.filled..self.filled + n) + .copy_from_slice(src); + } + self.filled += n; + self + } +} + +impl<'data> From<&'data mut [MaybeUninit]> for LogBuf<'data> { + fn from(buf: &'data mut [MaybeUninit]) -> Self { + Self { buf, filled: 0 } + } +} + +impl fmt::Write for LogBuf<'_> { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.append(s.as_bytes()); + Ok(()) + } +} + #[cfg(test)] mod tests { @@ -187,4 +260,25 @@ mod tests { r = check_mask(DebugMask::Alloc, mock.log_level); assert!(!r); } + + #[test] + fn log_buffer() { + use core::str; + + let mut buf = [const { MaybeUninit::::uninit() }; 32]; + let mut buf = LogBuf::from(&mut buf[..]); + let words = ["Hello", "World"]; + + // normal write + write!(&mut buf, "{} {}!", words[0], words[1]).unwrap(); + assert_eq!(str::from_utf8(buf.filled()), Ok("Hello World!")); + + // overflow results in truncated output + write!(&mut buf, " This is a test, {}", usize::MAX).unwrap(); + assert_eq!(str::from_utf8(buf.filled()), Ok("Hello World! This is a test, 184")); + + // and any following writes are still safe + write!(&mut buf, "test").unwrap(); + assert_eq!(str::from_utf8(buf.filled()), Ok("Hello World! This is a test, 184")); + } }