use core::cmp;
use core::fmt::{self, Write};
use core::mem::MaybeUninit;
use core::ptr::NonNull;
use crate::ffi::{self, ngx_err_t, ngx_log_t, ngx_uint_t, NGX_MAX_ERROR_STR};
pub const DEBUG: bool = cfg!(ngx_feature = "debug");
pub const LOG_BUFFER_SIZE: usize =
NGX_MAX_ERROR_STR as usize - b"1970/01/01 00:00:00 [info] 1#1: ".len();
#[inline(always)]
pub fn ngx_cycle_log() -> NonNull<ngx_log_t> {
NonNull::new(unsafe { (*nginx_sys::ngx_cycle).log }).expect("global logger")
}
#[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
}
#[inline]
pub fn write_fmt<'a>(buf: &'a mut [MaybeUninit<u8>], args: fmt::Arguments<'_>) -> &'a [u8] {
if let Some(str) = args.as_str() {
str.as_bytes()
} else {
let mut buf = LogBuf::from(buf);
let _ = buf.write_fmt(args);
buf.filled()
}
}
#[inline]
pub unsafe fn log_error(level: ngx_uint_t, log: *mut ngx_log_t, err: ngx_err_t, buf: &[u8]) {
unsafe {
#[cfg(ngx_feature = "have_variadic_macros")]
ffi::ngx_log_error_core(level, log, err, c"%*s".as_ptr(), buf.len(), buf.as_ptr());
#[cfg(not(ngx_feature = "have_variadic_macros"))]
ffi::ngx_log_error(level, log, err, c"%*s".as_ptr(), buf.len(), buf.as_ptr());
}
}
#[inline]
pub unsafe fn log_debug(log: *mut ngx_log_t, err: ngx_err_t, buf: &[u8]) {
unsafe {
#[cfg(ngx_feature = "have_variadic_macros")]
ffi::ngx_log_error_core(
ffi::NGX_LOG_DEBUG as _,
log,
err,
c"%*s".as_ptr(),
buf.len(),
buf.as_ptr(),
);
#[cfg(not(ngx_feature = "have_variadic_macros"))]
ffi::ngx_log_debug_core(log, err, c"%*s".as_ptr(), buf.len(), buf.as_ptr());
}
}
#[macro_export]
macro_rules! ngx_log_error {
( $level:expr, $log:expr, $($arg:tt)+ ) => {
let log = $log;
let level = $level as $crate::ffi::ngx_uint_t;
if level <= unsafe { (*log).log_level } {
let mut buf =
[const { ::core::mem::MaybeUninit::<u8>::uninit() }; $crate::log::LOG_BUFFER_SIZE];
let message = $crate::log::write_fmt(&mut buf, format_args!($($arg)+));
unsafe { $crate::log::log_error(level, log, 0, message) };
}
}
}
#[macro_export]
macro_rules! ngx_conf_log_error {
( $level:expr, $cf:expr, $($arg:tt)+ ) => {
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 mut buf =
[const { ::core::mem::MaybeUninit::<u8>::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()
);
}
}
}
}
#[macro_export]
macro_rules! ngx_log_debug {
( mask: $mask:expr, $log:expr, $($arg:tt)+ ) => {
let log = $log;
if $crate::log::DEBUG && $crate::log::check_mask($mask, unsafe { (*log).log_level }) {
let mut buf =
[const { ::core::mem::MaybeUninit::<u8>::uninit() }; $crate::log::LOG_BUFFER_SIZE];
let message = $crate::log::write_fmt(&mut buf, format_args!($($arg)+));
unsafe { $crate::log::log_debug(log, 0, message) };
}
};
( $log:expr, $($arg:tt)+ ) => {
$crate::ngx_log_debug!(mask: $crate::log::DebugMask::All, $log, $($arg)+);
}
}
#[macro_export]
macro_rules! ngx_log_debug_http {
( $request:expr, $($arg:tt)+ ) => {
let log = unsafe { (*$request.connection()).log };
$crate::ngx_log_debug!(mask: $crate::log::DebugMask::Http, log, $($arg)+);
}
}
#[macro_export]
macro_rules! ngx_log_debug_mask {
( DebugMask::Core, $log:expr, $($arg:tt)+ ) => {
$crate::ngx_log_debug!(mask: $crate::log::DebugMask::Core, $log, $($arg)+);
};
( DebugMask::Alloc, $log:expr, $($arg:tt)+ ) => {
$crate::ngx_log_debug!(mask: $crate::log::DebugMask::Alloc, $log, $($arg)+);
};
( DebugMask::Mutex, $log:expr, $($arg:tt)+ ) => {
$crate::ngx_log_debug!(mask: $crate::log::DebugMask::Mutex, $log, $($arg)+);
};
( DebugMask::Event, $log:expr, $($arg:tt)+ ) => {
$crate::ngx_log_debug!(mask: $crate::log::DebugMask::Event, $log, $($arg)+);
};
( DebugMask::Http, $log:expr, $($arg:tt)+ ) => {
$crate::ngx_log_debug!(mask: $crate::log::DebugMask::Http, $log, $($arg)+);
};
( DebugMask::Mail, $log:expr, $($arg:tt)+ ) => {
$crate::ngx_log_debug!(mask: $crate::log::DebugMask::Mail, $log, $($arg)+);
};
( DebugMask::Stream, $log:expr, $($arg:tt)+ ) => {
$crate::ngx_log_debug!(mask: $crate::log::DebugMask::Stream, $log, $($arg)+);
};
}
#[derive(Debug)]
pub enum DebugMask {
Core,
Alloc,
Mutex,
Event,
Http,
Mail,
Stream,
All,
}
impl TryFrom<u32> for DebugMask {
type Error = u32;
fn try_from(value: u32) -> Result<Self, Self::Error> {
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),
crate::ffi::NGX_LOG_DEBUG_ALL => Ok(DebugMask::All),
_ => Err(0),
}
}
}
impl From<DebugMask> 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,
DebugMask::All => crate::ffi::NGX_LOG_DEBUG_ALL,
}
}
}
struct LogBuf<'data> {
buf: &'data mut [MaybeUninit<u8>],
filled: usize,
}
impl<'data> LogBuf<'data> {
pub fn filled(&self) -> &'data [u8] {
unsafe {
let buf = self.buf.get_unchecked(..self.filled);
&*(buf as *const [MaybeUninit<u8>] as *const [u8])
}
}
pub fn append(&mut self, buf: &[u8]) -> &mut Self {
let n = cmp::min(self.buf.len() - self.filled, buf.len());
unsafe {
let src = buf.get_unchecked(..n);
let src: &[MaybeUninit<u8>] = core::mem::transmute(src);
self.buf
.get_unchecked_mut(self.filled..self.filled + n)
.copy_from_slice(src);
}
self.filled += n;
self
}
}
impl<'data> From<&'data mut [MaybeUninit<u8>]> for LogBuf<'data> {
fn from(buf: &'data mut [MaybeUninit<u8>]) -> 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 {
use super::*;
#[test]
fn test_mask_lower_bound() {
assert!(<DebugMask as Into<u32>>::into(DebugMask::Core) == crate::ffi::NGX_LOG_DEBUG_FIRST);
}
#[test]
fn test_mask_upper_bound() {
assert!(
<DebugMask as Into<u32>>::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);
r = check_mask(DebugMask::Alloc, mock.log_level);
assert!(!r);
}
#[test]
fn log_buffer() {
use core::str;
let mut buf = [const { MaybeUninit::<u8>::uninit() }; 32];
let mut buf = LogBuf::from(&mut buf[..]);
let words = ["Hello", "World"];
write!(&mut buf, "{} {}!", words[0], words[1]).unwrap();
assert_eq!(str::from_utf8(buf.filled()), Ok("Hello World!"));
write!(&mut buf, " This is a test, {}", u64::MAX).unwrap();
assert_eq!(
str::from_utf8(buf.filled()),
Ok("Hello World! This is a test, 184")
);
write!(&mut buf, "test").unwrap();
assert_eq!(
str::from_utf8(buf.filled()),
Ok("Hello World! This is a test, 184")
);
}
}