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