Skip to content

Commit 37d5c9c

Browse files
committed
feat: format logs to fixed buffer to remove the last allocation
1 parent 6b3c6e9 commit 37d5c9c

File tree

2 files changed

+79
-9
lines changed

2 files changed

+79
-9
lines changed

src/lib.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,6 @@ pub mod http;
5959
/// The log module.
6060
///
6161
/// This module provides an interface into the NGINX logger framework.
62-
///
63-
/// This module is temporally available only with `std` feature.
64-
#[cfg(feature = "std")]
6562
pub mod log;
6663

6764
/// Define modules exported by this library.

src/log.rs

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
use core::cmp;
2+
use core::fmt::{self, Write};
3+
use core::mem::MaybeUninit;
4+
5+
use crate::ffi::NGX_MAX_ERROR_STR;
6+
7+
/// Size of the static buffer used to format log messages.
8+
///
9+
/// Approximates the remaining space in `u_char[NGX_MAX_ERROR_STR]` after writing the standard
10+
/// prefix
11+
pub const LOG_BUFFER_SIZE: usize = NGX_MAX_ERROR_STR as usize - b"1970/01/01 00:00:00 [info] 1#1: ".len();
12+
113
/// Utility function to provide typed checking of the mask's field state.
214
#[inline(always)]
315
pub fn check_mask(mask: DebugMask, log_level: usize) -> bool {
@@ -8,6 +20,21 @@ pub fn check_mask(mask: DebugMask, log_level: usize) -> bool {
820
true
921
}
1022

23+
/// Format args into a provided buffer
24+
// May produce incomplete UTF-8 sequences. But any writes to `ngx_log_t` already can be truncated,
25+
// so nothing we can do here.
26+
#[inline]
27+
pub fn write_fmt<'a>(buf: &'a mut [MaybeUninit<u8>], args: fmt::Arguments<'_>) -> &'a [u8] {
28+
if let Some(str) = args.as_str() {
29+
str.as_bytes()
30+
} else {
31+
let mut buf = LogBuf::from(buf);
32+
// nothing we can or want to do on errors
33+
let _ = buf.write_fmt(args);
34+
buf.filled()
35+
}
36+
}
37+
1138
/// Write to logger at a specified level.
1239
///
1340
/// See [Logging](https://nginx.org/en/docs/dev/development_guide.html#logging)
@@ -18,8 +45,8 @@ macro_rules! ngx_log_error {
1845
let log = $log;
1946
let level = $level as $crate::ffi::ngx_uint_t;
2047
if level < unsafe { (*log).log_level } {
21-
let message = ::std::format!($($arg)+);
22-
let message = message.as_bytes();
48+
let mut buf = [const { ::core::mem::MaybeUninit::<u8>::uninit() }; $crate::log::LOG_BUFFER_SIZE];
49+
let message = $crate::log::write_fmt(&mut buf, format_args!($($arg)+));
2350
unsafe {
2451
$crate::ffi::ngx_log_error_core(level, log, 0, c"%*s".as_ptr(), message.len(), message.as_ptr());
2552
}
@@ -34,8 +61,8 @@ macro_rules! ngx_conf_log_error {
3461
let cf: *mut $crate::ffi::ngx_conf_t = $cf;
3562
let level = $level as $crate::ffi::ngx_uint_t;
3663
if level < unsafe { (*(*cf).log).log_level } {
37-
let message = ::std::format!($($arg)+);
38-
let message = message.as_bytes();
64+
let mut buf = [const { ::core::mem::MaybeUninit::<u8>::uninit() }; $crate::log::LOG_BUFFER_SIZE];
65+
let message = $crate::log::write_fmt(&mut buf, format_args!($($arg)+));
3966
unsafe {
4067
$crate::ffi::ngx_conf_log_error(level, cf, 0, c"%*s".as_ptr(), message.len(), message.as_ptr());
4168
}
@@ -50,8 +77,8 @@ macro_rules! ngx_log_debug {
5077
let log = $log;
5178
if $crate::log::check_mask($mask, unsafe { (*log).log_level }) {
5279
let level = $crate::ffi::NGX_LOG_DEBUG as $crate::ffi::ngx_uint_t;
53-
let message = format!($($arg)+);
54-
let message = message.as_bytes();
80+
let mut buf = [const { ::core::mem::MaybeUninit::<u8>::uninit() }; $crate::log::LOG_BUFFER_SIZE];
81+
let message = $crate::log::write_fmt(&mut buf, format_args!($($arg)+));
5582
unsafe {
5683
$crate::ffi::ngx_log_error_core(level, log, 0, c"%*s".as_ptr(), message.len(), message.as_ptr());
5784
}
@@ -161,6 +188,52 @@ impl From<DebugMask> for u32 {
161188
}
162189
}
163190

191+
/// Minimal subset of unstable core::io::{BorrowedBuf,BorrowedCursor}
192+
struct LogBuf<'data> {
193+
buf: &'data mut [MaybeUninit<u8>],
194+
filled: usize,
195+
}
196+
197+
impl<'data> LogBuf<'data> {
198+
pub fn filled(&self) -> &'data [u8] {
199+
// SAFETY: valid bytes have been written to self.buf[..self.filled]
200+
unsafe {
201+
let buf = self.buf.get_unchecked(..self.filled);
202+
// inlined MaybeUninit::slice_assume_init_ref
203+
&*(buf as *const [MaybeUninit<u8>] as *const [u8])
204+
}
205+
}
206+
207+
pub fn append(&mut self, buf: &[u8]) -> &mut Self {
208+
let n = cmp::min(self.buf.len() - self.filled, buf.len());
209+
unsafe {
210+
// SAFETY: The source buf has at least n bytes
211+
let src = buf.get_unchecked(..n);
212+
// SAFETY: &[u8] and &[MaybeUninit<u8>] have the same layout
213+
let src: &[MaybeUninit<u8>] = core::mem::transmute(src);
214+
// SAFETY: self.buf has at least n bytes available after self.filled
215+
self.buf
216+
.get_unchecked_mut(self.filled..self.filled + n)
217+
.copy_from_slice(src);
218+
}
219+
self.filled += n;
220+
self
221+
}
222+
}
223+
224+
impl<'data> From<&'data mut [MaybeUninit<u8>]> for LogBuf<'data> {
225+
fn from(buf: &'data mut [MaybeUninit<u8>]) -> Self {
226+
Self { buf, filled: 0 }
227+
}
228+
}
229+
230+
impl fmt::Write for LogBuf<'_> {
231+
fn write_str(&mut self, s: &str) -> fmt::Result {
232+
self.append(s.as_bytes());
233+
Ok(())
234+
}
235+
}
236+
164237
#[cfg(test)]
165238
mod tests {
166239

0 commit comments

Comments
 (0)