libddwaf/
log.rs

1//! Access to the in-app WAF's logging facility.
2
3use std::ffi::CStr;
4use std::{error, fmt, slice};
5
6type LogCallback = Box<dyn Fn(Level, &'static CStr, &'static CStr, u32, &[u8])>;
7
8static mut LOG_CB: Option<LogCallback> = None;
9
10/// Sets the log callback function.
11///
12/// # Safety
13///
14/// This function is unsafe because it writes to a static variable without synchronization.
15/// It should only be used during startup.
16pub unsafe fn set_log_cb(
17    cb: impl Fn(Level, &'static CStr, &'static CStr, u32, &[u8]) + 'static,
18    min_level: Level,
19) {
20    unsafe { LOG_CB = Some(Box::new(cb)) };
21    unsafe { libddwaf_sys::ddwaf_set_log_cb(Some(bridge_log_cb), min_level.as_raw()) };
22}
23
24/// Resets the log callback function (to the default of "none").
25///
26/// # Safety
27///
28/// This function is unsafe because it writes to a static variable without synchronization.
29/// It should only be used during startup.
30pub unsafe fn reset_log_cb() {
31    unsafe { libddwaf_sys::ddwaf_set_log_cb(None, Level::Off.as_raw()) };
32    unsafe { LOG_CB = None };
33}
34
35/// Logging levels supported by the WAF.
36#[non_exhaustive]
37#[derive(Debug, Copy, Clone, PartialEq, Eq)]
38pub enum Level {
39    /// Extremely detailed logging.
40    Trace,
41    /// Detailed logging.
42    Debug,
43    /// Informational logging.
44    Info,
45    /// Log only warnings and errors.
46    Warn,
47    /// Log only errors.
48    Error,
49    /// Do not log anything.
50    Off,
51}
52impl Level {
53    const fn as_raw(self) -> libddwaf_sys::DDWAF_LOG_LEVEL {
54        match self {
55            Self::Trace => libddwaf_sys::DDWAF_LOG_TRACE,
56            Self::Debug => libddwaf_sys::DDWAF_LOG_DEBUG,
57            Self::Info => libddwaf_sys::DDWAF_LOG_INFO,
58            Self::Warn => libddwaf_sys::DDWAF_LOG_WARN,
59            Self::Error => libddwaf_sys::DDWAF_LOG_ERROR,
60            Self::Off => libddwaf_sys::DDWAF_LOG_OFF,
61        }
62    }
63}
64impl fmt::Display for Level {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        match self {
67            Self::Trace => write!(f, "TRACE"),
68            Self::Debug => write!(f, "DEBUG"),
69            Self::Info => write!(f, "INFO"),
70            Self::Warn => write!(f, "WARN"),
71            Self::Error => write!(f, "ERROR"),
72            Self::Off => write!(f, "OFF"),
73        }
74    }
75}
76impl TryFrom<libddwaf_sys::DDWAF_LOG_LEVEL> for Level {
77    type Error = UnknownLogLevelError;
78
79    fn try_from(value: libddwaf_sys::DDWAF_LOG_LEVEL) -> Result<Self, UnknownLogLevelError> {
80        match value {
81            libddwaf_sys::DDWAF_LOG_TRACE => Ok(Level::Trace),
82            libddwaf_sys::DDWAF_LOG_DEBUG => Ok(Level::Debug),
83            libddwaf_sys::DDWAF_LOG_INFO => Ok(Level::Info),
84            libddwaf_sys::DDWAF_LOG_WARN => Ok(Level::Warn),
85            libddwaf_sys::DDWAF_LOG_ERROR => Ok(Level::Error),
86            libddwaf_sys::DDWAF_LOG_OFF => Ok(Level::Off),
87            unknown => Err(UnknownLogLevelError { raw: unknown }),
88        }
89    }
90}
91
92/// An error that is produced when encountering an unknown log level value.
93#[derive(Debug)]
94pub struct UnknownLogLevelError {
95    raw: libddwaf_sys::DDWAF_LOG_LEVEL,
96}
97impl fmt::Display for UnknownLogLevelError {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        write!(f, "Unknown log level: 0x{:02X}", self.raw)
100    }
101}
102impl error::Error for UnknownLogLevelError {}
103
104/// Wraps the log callback function (stored in [`LOG_CB`]) to convert the raw pointers provided by the C/C++ library into
105/// somewhat easier to consume types.
106extern "C" fn bridge_log_cb(
107    level: libddwaf_sys::DDWAF_LOG_LEVEL,
108    file: *const std::os::raw::c_char,
109    function: *const std::os::raw::c_char,
110    line: u32,
111    message: *const std::os::raw::c_char,
112    message_len: u64,
113) {
114    unsafe {
115        #[allow(static_mut_refs)]
116        if let Some(cb) = &LOG_CB {
117            let file = CStr::from_ptr(file);
118            let function = CStr::from_ptr(function);
119            let message = if message.is_null() {
120                b"<null>\0"
121            } else {
122                slice::from_raw_parts(message.cast(), message_len.try_into().unwrap_or(usize::MAX))
123            };
124            cb(
125                Level::try_from(level).unwrap_or(Level::Error),
126                file,
127                function,
128                line,
129                message,
130            );
131        }
132    }
133}