ngx/
log.rs

1use core::cmp;
2use core::fmt::{self, Write};
3use core::mem::MaybeUninit;
4
5use crate::ffi::{self, ngx_err_t, ngx_log_t, ngx_uint_t, 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
11pub const LOG_BUFFER_SIZE: usize = NGX_MAX_ERROR_STR as usize - b"1970/01/01 00:00:00 [info] 1#1: ".len();
12
13/// Utility function to provide typed checking of the mask's field state.
14#[inline(always)]
15pub fn check_mask(mask: DebugMask, log_level: usize) -> bool {
16    let mask_bits: u32 = mask.into();
17    if log_level & mask_bits as usize == 0 {
18        return false;
19    }
20    true
21}
22
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]
27pub 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
38/// Writes the provided buffer to the nginx logger at a specified level.
39///
40/// # Safety
41/// Requires a valid log pointer.
42#[inline]
43pub unsafe fn log_error(level: ngx_uint_t, log: *mut ngx_log_t, err: ngx_err_t, buf: &[u8]) {
44    unsafe {
45        #[cfg(ngx_feature = "have_variadic_macros")]
46        ffi::ngx_log_error_core(level, log, err, c"%*s".as_ptr(), buf.len(), buf.as_ptr());
47        #[cfg(not(ngx_feature = "have_variadic_macros"))]
48        ffi::ngx_log_error(level, log, err, c"%*s".as_ptr(), buf.len(), buf.as_ptr());
49    }
50}
51
52/// Writes the provided buffer to the nginx logger at the debug level.
53///
54/// # Safety
55/// Requires a valid log pointer.
56#[inline]
57pub unsafe fn log_debug(log: *mut ngx_log_t, err: ngx_err_t, buf: &[u8]) {
58    unsafe {
59        #[cfg(ngx_feature = "have_variadic_macros")]
60        ffi::ngx_log_error_core(
61            ffi::NGX_LOG_DEBUG as _,
62            log,
63            err,
64            c"%*s".as_ptr(),
65            buf.len(),
66            buf.as_ptr(),
67        );
68        #[cfg(not(ngx_feature = "have_variadic_macros"))]
69        ffi::ngx_log_debug_core(log, err, c"%*s".as_ptr(), buf.len(), buf.as_ptr());
70    }
71}
72
73/// Write to logger at a specified level.
74///
75/// See [Logging](https://nginx.org/en/docs/dev/development_guide.html#logging)
76/// for available log levels.
77#[macro_export]
78macro_rules! ngx_log_error {
79    ( $level:expr, $log:expr, $($arg:tt)+ ) => {
80        let log = $log;
81        let level = $level as $crate::ffi::ngx_uint_t;
82        if level < unsafe { (*log).log_level } {
83            let mut buf = [const { ::core::mem::MaybeUninit::<u8>::uninit() }; $crate::log::LOG_BUFFER_SIZE];
84            let message = $crate::log::write_fmt(&mut buf, format_args!($($arg)+));
85            unsafe { $crate::log::log_error(level, log, 0, message) };
86        }
87    }
88}
89
90/// Write to logger with the context of currently processed configuration file.
91#[macro_export]
92macro_rules! ngx_conf_log_error {
93    ( $level:expr, $cf:expr, $($arg:tt)+ ) => {
94        let cf: *mut $crate::ffi::ngx_conf_t = $cf;
95        let level = $level as $crate::ffi::ngx_uint_t;
96        if level < unsafe { (*(*cf).log).log_level } {
97            let mut buf = [const { ::core::mem::MaybeUninit::<u8>::uninit() }; $crate::log::LOG_BUFFER_SIZE];
98            let message = $crate::log::write_fmt(&mut buf, format_args!($($arg)+));
99            unsafe {
100                $crate::ffi::ngx_conf_log_error(level, cf, 0, c"%*s".as_ptr(), message.len(), message.as_ptr());
101            }
102        }
103    }
104}
105
106/// Write to logger at debug level.
107#[macro_export]
108macro_rules! ngx_log_debug {
109    ( mask: $mask:expr, $log:expr, $($arg:tt)+ ) => {
110        let log = $log;
111        if $crate::log::check_mask($mask, unsafe { (*log).log_level }) {
112            let mut buf = [const { ::core::mem::MaybeUninit::<u8>::uninit() }; $crate::log::LOG_BUFFER_SIZE];
113            let message = $crate::log::write_fmt(&mut buf, format_args!($($arg)+));
114            unsafe { $crate::log::log_debug(log, 0, message) };
115        }
116    };
117    ( $log:expr, $($arg:tt)+ ) => {
118        $crate::ngx_log_debug!(mask: $crate::log::DebugMask::All, $log, $($arg)+);
119    }
120}
121
122/// Log to request connection log at level [`NGX_LOG_DEBUG_HTTP`].
123///
124/// [`NGX_LOG_DEBUG_HTTP`]: https://nginx.org/en/docs/dev/development_guide.html#logging
125#[macro_export]
126macro_rules! ngx_log_debug_http {
127    ( $request:expr, $($arg:tt)+ ) => {
128        let log = unsafe { (*$request.connection()).log };
129        $crate::ngx_log_debug!(mask: $crate::log::DebugMask::Http, log, $($arg)+);
130    }
131}
132
133/// Log with requested debug mask.
134///
135/// **NOTE:** This macro supports [`DebugMask::Http`] (`NGX_LOG_DEBUG_HTTP`), however, if you have
136/// access to a Request object via an http handler it can be more convenient and readable to use
137/// the [`ngx_log_debug_http`] macro instead.
138///
139/// See <https://nginx.org/en/docs/dev/development_guide.html#logging> for details and available
140/// masks.
141#[macro_export]
142macro_rules! ngx_log_debug_mask {
143    ( DebugMask::Core, $log:expr, $($arg:tt)+ ) => {
144        $crate::ngx_log_debug!(mask: $crate::log::DebugMask::Core, $log, $($arg)+);
145    };
146    ( DebugMask::Alloc, $log:expr, $($arg:tt)+ ) => {
147        $crate::ngx_log_debug!(mask: $crate::log::DebugMask::Alloc, $log, $($arg)+);
148    };
149    ( DebugMask::Mutex, $log:expr, $($arg:tt)+ ) => {
150        $crate::ngx_log_debug!(mask: $crate::log::DebugMask::Mutex, $log, $($arg)+);
151    };
152    ( DebugMask::Event, $log:expr, $($arg:tt)+ ) => {
153        $crate::ngx_log_debug!(mask: $crate::log::DebugMask::Event, $log, $($arg)+);
154    };
155    ( DebugMask::Http, $log:expr, $($arg:tt)+ ) => {
156        $crate::ngx_log_debug!(mask: $crate::log::DebugMask::Http, $log, $($arg)+);
157    };
158    ( DebugMask::Mail, $log:expr, $($arg:tt)+ ) => {
159        $crate::ngx_log_debug!(mask: $crate::log::DebugMask::Mail, $log, $($arg)+);
160    };
161    ( DebugMask::Stream, $log:expr, $($arg:tt)+ ) => {
162        $crate::ngx_log_debug!(mask: $crate::log::DebugMask::Stream, $log, $($arg)+);
163    };
164}
165
166/// Debug masks for use with [`ngx_log_debug_mask`], these represent the only accepted values for
167/// the mask.
168#[derive(Debug)]
169pub enum DebugMask {
170    /// Aligns to the NGX_LOG_DEBUG_CORE mask.
171    Core,
172    /// Aligns to the NGX_LOG_DEBUG_ALLOC mask.
173    Alloc,
174    /// Aligns to the NGX_LOG_DEBUG_MUTEX mask.
175    Mutex,
176    /// Aligns to the NGX_LOG_DEBUG_EVENT mask.
177    Event,
178    /// Aligns to the NGX_LOG_DEBUG_HTTP mask.
179    Http,
180    /// Aligns to the NGX_LOG_DEBUG_MAIL mask.
181    Mail,
182    /// Aligns to the NGX_LOG_DEBUG_STREAM mask.
183    Stream,
184    /// Aligns to the NGX_LOG_DEBUG_ALL mask.
185    All,
186}
187
188impl TryFrom<u32> for DebugMask {
189    type Error = u32;
190
191    fn try_from(value: u32) -> Result<Self, Self::Error> {
192        match value {
193            crate::ffi::NGX_LOG_DEBUG_CORE => Ok(DebugMask::Core),
194            crate::ffi::NGX_LOG_DEBUG_ALLOC => Ok(DebugMask::Alloc),
195            crate::ffi::NGX_LOG_DEBUG_MUTEX => Ok(DebugMask::Mutex),
196            crate::ffi::NGX_LOG_DEBUG_EVENT => Ok(DebugMask::Event),
197            crate::ffi::NGX_LOG_DEBUG_HTTP => Ok(DebugMask::Http),
198            crate::ffi::NGX_LOG_DEBUG_MAIL => Ok(DebugMask::Mail),
199            crate::ffi::NGX_LOG_DEBUG_STREAM => Ok(DebugMask::Stream),
200            crate::ffi::NGX_LOG_DEBUG_ALL => Ok(DebugMask::All),
201            _ => Err(0),
202        }
203    }
204}
205
206impl From<DebugMask> for u32 {
207    fn from(value: DebugMask) -> Self {
208        match value {
209            DebugMask::Core => crate::ffi::NGX_LOG_DEBUG_CORE,
210            DebugMask::Alloc => crate::ffi::NGX_LOG_DEBUG_ALLOC,
211            DebugMask::Mutex => crate::ffi::NGX_LOG_DEBUG_MUTEX,
212            DebugMask::Event => crate::ffi::NGX_LOG_DEBUG_EVENT,
213            DebugMask::Http => crate::ffi::NGX_LOG_DEBUG_HTTP,
214            DebugMask::Mail => crate::ffi::NGX_LOG_DEBUG_MAIL,
215            DebugMask::Stream => crate::ffi::NGX_LOG_DEBUG_STREAM,
216            DebugMask::All => crate::ffi::NGX_LOG_DEBUG_ALL,
217        }
218    }
219}
220
221/// Minimal subset of unstable core::io::{BorrowedBuf,BorrowedCursor}
222struct LogBuf<'data> {
223    buf: &'data mut [MaybeUninit<u8>],
224    filled: usize,
225}
226
227impl<'data> LogBuf<'data> {
228    pub fn filled(&self) -> &'data [u8] {
229        // SAFETY: valid bytes have been written to self.buf[..self.filled]
230        unsafe {
231            let buf = self.buf.get_unchecked(..self.filled);
232            // inlined MaybeUninit::slice_assume_init_ref
233            &*(buf as *const [MaybeUninit<u8>] as *const [u8])
234        }
235    }
236
237    pub fn append(&mut self, buf: &[u8]) -> &mut Self {
238        let n = cmp::min(self.buf.len() - self.filled, buf.len());
239        unsafe {
240            // SAFETY: The source buf has at least n bytes
241            let src = buf.get_unchecked(..n);
242            // SAFETY: &[u8] and &[MaybeUninit<u8>] have the same layout
243            let src: &[MaybeUninit<u8>] = core::mem::transmute(src);
244            // SAFETY: self.buf has at least n bytes available after self.filled
245            self.buf
246                .get_unchecked_mut(self.filled..self.filled + n)
247                .copy_from_slice(src);
248        }
249        self.filled += n;
250        self
251    }
252}
253
254impl<'data> From<&'data mut [MaybeUninit<u8>]> for LogBuf<'data> {
255    fn from(buf: &'data mut [MaybeUninit<u8>]) -> Self {
256        Self { buf, filled: 0 }
257    }
258}
259
260impl fmt::Write for LogBuf<'_> {
261    fn write_str(&mut self, s: &str) -> fmt::Result {
262        self.append(s.as_bytes());
263        Ok(())
264    }
265}
266
267#[cfg(test)]
268mod tests {
269
270    use super::*;
271
272    #[test]
273    fn test_mask_lower_bound() {
274        assert!(<DebugMask as Into<u32>>::into(DebugMask::Core) == crate::ffi::NGX_LOG_DEBUG_FIRST);
275    }
276    #[test]
277    fn test_mask_upper_bound() {
278        assert!(<DebugMask as Into<u32>>::into(DebugMask::Stream) == crate::ffi::NGX_LOG_DEBUG_LAST);
279    }
280    #[test]
281    fn test_check_mask() {
282        struct MockLog {
283            log_level: usize,
284        }
285        let mock = MockLog { log_level: 16 };
286
287        let mut r = check_mask(DebugMask::Core, mock.log_level);
288        assert!(r);
289
290        r = check_mask(DebugMask::Alloc, mock.log_level);
291        assert!(!r);
292    }
293
294    #[test]
295    fn log_buffer() {
296        use core::str;
297
298        let mut buf = [const { MaybeUninit::<u8>::uninit() }; 32];
299        let mut buf = LogBuf::from(&mut buf[..]);
300        let words = ["Hello", "World"];
301
302        // normal write
303        write!(&mut buf, "{} {}!", words[0], words[1]).unwrap();
304        assert_eq!(str::from_utf8(buf.filled()), Ok("Hello World!"));
305
306        // overflow results in truncated output
307        write!(&mut buf, " This is a test, {}", usize::MAX).unwrap();
308        assert_eq!(str::from_utf8(buf.filled()), Ok("Hello World! This is a test, 184"));
309
310        // and any following writes are still safe
311        write!(&mut buf, "test").unwrap();
312        assert_eq!(str::from_utf8(buf.filled()), Ok("Hello World! This is a test, 184"));
313    }
314}