ngx/
log.rs

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