memflow/plugins/
logger.rs

1/// The plugin logger is just a thin wrapper which redirects all
2/// logging functions from the callee to the caller
3use crate::cglue::{
4    ext::{DisplayBaseRef, DisplayRef},
5    COption, CSliceRef, Opaquable,
6};
7
8use log::{Level, LevelFilter, SetLoggerError};
9
10use core::ffi::c_void;
11
12use std::sync::atomic::{AtomicPtr, Ordering};
13
14/// FFI-Safe representation of log::Metadata
15#[repr(C)]
16pub struct Metadata<'a> {
17    level: Level,
18    target: CSliceRef<'a, u8>,
19}
20
21/// FFI-Safe representation of log::Record
22#[repr(C)]
23pub struct Record<'a> {
24    metadata: Metadata<'a>,
25    message: DisplayRef<'a>,
26    module_path: COption<CSliceRef<'a, u8>>,
27    file: COption<CSliceRef<'a, u8>>,
28    line: COption<u32>,
29    //#[cfg(feature = "kv_unstable")]
30    //key_values: KeyValues<'a>,
31}
32
33/// A logger which just forwards all logging calls over the FFI
34/// from the callee to the caller (i.e. from the plugin to the main process).
35#[repr(C)]
36pub struct PluginLogger {
37    max_level: LevelFilter,
38    enabled: extern "C" fn(metadata: &Metadata) -> bool,
39    log: extern "C" fn(record: &Record) -> (),
40    flush: extern "C" fn() -> (),
41    on_level_change: AtomicPtr<c_void>,
42}
43
44impl PluginLogger {
45    /// Creates a new PluginLogger.
46    ///
47    /// # Remarks:
48    ///
49    /// This function has to be called on the caller side
50    /// (i.e. from memflow itself in the main process).
51    pub fn new() -> Self {
52        Self {
53            max_level: log::max_level(),
54            enabled: mf_log_enabled,
55            log: mf_log_log,
56            flush: mf_log_flush,
57            on_level_change: AtomicPtr::new(std::ptr::null_mut()),
58        }
59    }
60
61    /// Initializes the logger and sets up the logger in the log crate.
62    ///
63    /// # Remarks:
64    ///
65    /// This function has to be invoked on the callee side.
66    /// (i.e. in the plugin)
67    pub fn init(&'static self) -> Result<(), SetLoggerError> {
68        // Explicitly typecheck the signature so that we do not mess anything up
69        let val: SetMaxLevelFn = mf_log_set_max_level;
70        self.on_level_change
71            .store(val as *const c_void as *mut c_void, Ordering::SeqCst);
72        log::set_max_level(self.max_level);
73        log::set_logger(self)?;
74        Ok(())
75    }
76
77    /// Updates the log level on the plugin end from local end
78    pub fn on_level_change(&self, new_level: LevelFilter) {
79        let val = self.on_level_change.load(Ordering::Relaxed);
80        if let Some(on_change) =
81            unsafe { std::mem::transmute::<*mut c_void, Option<SetMaxLevelFn>>(val) }
82        {
83            on_change(new_level);
84        }
85    }
86}
87
88impl Default for PluginLogger {
89    fn default() -> Self {
90        PluginLogger::new()
91    }
92}
93
94fn display_obj<'a, T: 'a + core::fmt::Display>(obj: &'a T) -> DisplayRef<'a> {
95    let obj: DisplayBaseRef<T> = From::from(obj);
96    obj.into_opaque()
97}
98
99impl log::Log for PluginLogger {
100    fn enabled(&self, metadata: &log::Metadata) -> bool {
101        let m = Metadata {
102            level: metadata.level(),
103            target: metadata.target().into(),
104        };
105        (self.enabled)(&m)
106    }
107
108    fn log(&self, record: &log::Record) {
109        let message = display_obj(record.args());
110        let r = Record {
111            metadata: Metadata {
112                level: record.metadata().level(),
113                target: record.metadata().target().into(),
114            },
115            message,
116            module_path: record.module_path().map(|s| s.into()).into(),
117            file: record.file().map(|s| s.into()).into(),
118            line: record.line().into(),
119        };
120        (self.log)(&r)
121    }
122
123    fn flush(&self) {
124        (self.flush)()
125    }
126}
127
128type SetMaxLevelFn = extern "C" fn(LevelFilter);
129
130/// FFI function which is being invoked from the main executable to the plugin library.
131extern "C" fn mf_log_set_max_level(level: LevelFilter) {
132    log::set_max_level(level);
133}
134
135/// FFI function which is being invoked from the plugin library to the main executable.
136extern "C" fn mf_log_enabled(metadata: &Metadata) -> bool {
137    log::logger().enabled(
138        &log::Metadata::builder()
139            .level(metadata.level)
140            .target(unsafe { metadata.target.into_str() })
141            .build(),
142    )
143}
144
145/// FFI function which is being invoked from the plugin library to the main executable.
146extern "C" fn mf_log_log(record: &Record) {
147    log::logger().log(
148        &log::Record::builder()
149            .metadata(
150                log::Metadata::builder()
151                    .level(record.metadata.level)
152                    .target(unsafe { record.metadata.target.into_str() })
153                    .build(),
154            )
155            .args(format_args!("{}", record.message))
156            .module_path(match &record.module_path {
157                COption::Some(s) => Some(unsafe { s.into_str() }),
158                COption::None => None,
159            })
160            .file(match &record.file {
161                COption::Some(s) => Some(unsafe { s.into_str() }),
162                COption::None => None,
163            })
164            .line(match &record.line {
165                COption::Some(l) => Some(*l),
166                COption::None => None,
167            })
168            .build(),
169    )
170}
171
172/// FFI function which is being invoked from the plugin library to the main executable.
173extern "C" fn mf_log_flush() {
174    log::logger().flush()
175}