binaryninja/
logger.rs

1#![allow(clippy::needless_doctest_main)]
2
3//! To use logging in your script, do something like:
4//!
5//! ```no-test
6//! use binaryninja::logger::Logger;
7//! use log::{info, LevelFilter};
8//!
9//! fn main() {
10//!     Logger::default().init();
11//!     info!("The logger has been initialized!");
12//!     // Your code here...
13//! }
14//! ```
15//!
16//! or
17//!
18//!```no-test
19//! use binaryninja::logger::Logger;
20//! use log::{info, LevelFilter};
21//!
22//! #[no_mangle]
23//! pub extern "C" fn CorePluginInit() -> bool {
24//!     Logger::new("My Plugin").with_level(LevelFilter::Warn).init();
25//!     info!("The logger has been initialized!");
26//!     // Your code here...
27//!     true
28//! }
29//! ```
30
31pub use binaryninjacore_sys::BNLogLevel as Level;
32use binaryninjacore_sys::{
33    BNFreeLogger, BNLogCreateLogger, BNLogListener, BNLogger, BNLoggerGetName,
34    BNLoggerGetSessionId, BNNewLoggerReference, BNUpdateLogListeners,
35};
36
37use crate::rc::{Ref, RefCountable};
38use crate::string::{raw_to_string, BnString, IntoCStr};
39use log;
40use log::LevelFilter;
41use std::ffi::CString;
42use std::os::raw::{c_char, c_void};
43use std::ptr::NonNull;
44
45const LOGGER_DEFAULT_SESSION_ID: usize = 0;
46
47#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
48pub struct Logger {
49    handle: NonNull<BNLogger>,
50    level: LevelFilter,
51}
52
53impl Logger {
54    pub fn new(name: &str) -> Ref<Logger> {
55        Self::new_with_session(name, LOGGER_DEFAULT_SESSION_ID)
56    }
57
58    pub fn new_with_session(name: &str, session_id: usize) -> Ref<Logger> {
59        let name_raw = CString::new(name).unwrap();
60        let handle = unsafe { BNLogCreateLogger(name_raw.as_ptr(), session_id) };
61        unsafe {
62            Ref::new(Logger {
63                handle: NonNull::new(handle).unwrap(),
64                level: LevelFilter::Debug,
65            })
66        }
67    }
68
69    pub fn name(&self) -> String {
70        unsafe { BnString::into_string(BNLoggerGetName(self.handle.as_ptr())) }
71    }
72
73    pub fn session_id(&self) -> usize {
74        unsafe { BNLoggerGetSessionId(self.handle.as_ptr()) }
75    }
76}
77
78// NOTE: Due to the ref counted core object, we must impl on the ref counted object.
79// NOTE: If we wanted to be less specific than we would need Ref to impl Copy
80impl Ref<Logger> {
81    pub fn with_level(mut self, level: LevelFilter) -> Ref<Logger> {
82        self.level = level;
83        self
84    }
85
86    /// Calling this will set the global logger to `self`.
87    ///
88    /// NOTE: There is no guarantee that logs will be sent to BinaryNinja as another log sink
89    /// may have already been initialized beforehand.
90    pub fn init(self) {
91        log::set_max_level(self.level);
92        let _ = log::set_boxed_logger(Box::new(self));
93    }
94
95    /// Send a log to the logger instance, if you instead want to use the `log` crate and its facilities,
96    /// you should use [`Ref<Logger>::init`] to initialize the `log` compatible logger.
97    pub fn send_log(&self, level: Level, msg: &str) {
98        use binaryninjacore_sys::BNLog;
99        if let Ok(msg) = CString::new(msg) {
100            let logger_name = self.name().to_cstr();
101            unsafe {
102                BNLog(
103                    self.session_id(),
104                    level,
105                    logger_name.as_ptr(),
106                    0,
107                    c"%s".as_ptr(),
108                    msg.as_ptr(),
109                )
110            }
111        }
112    }
113}
114
115impl Default for Ref<Logger> {
116    fn default() -> Self {
117        Logger::new("Default")
118    }
119}
120
121impl ToOwned for Logger {
122    type Owned = Ref<Self>;
123
124    fn to_owned(&self) -> Self::Owned {
125        unsafe { RefCountable::inc_ref(self) }
126    }
127}
128
129unsafe impl RefCountable for Logger {
130    unsafe fn inc_ref(logger: &Self) -> Ref<Self> {
131        Ref::new(Self {
132            handle: NonNull::new(BNNewLoggerReference(logger.handle.as_ptr())).unwrap(),
133            level: logger.level,
134        })
135    }
136
137    unsafe fn dec_ref(logger: &Self) {
138        BNFreeLogger(logger.handle.as_ptr());
139    }
140}
141
142impl log::Log for Ref<Logger> {
143    fn enabled(&self, _metadata: &log::Metadata) -> bool {
144        true
145    }
146
147    fn log(&self, record: &log::Record) {
148        use self::Level::*;
149        let level = match record.level() {
150            log::Level::Error => ErrorLog,
151            log::Level::Warn => WarningLog,
152            log::Level::Info => InfoLog,
153            log::Level::Debug | log::Level::Trace => DebugLog,
154        };
155        self.send_log(level, &format!("{}", record.args()));
156    }
157
158    fn flush(&self) {}
159}
160
161unsafe impl Send for Logger {}
162unsafe impl Sync for Logger {}
163
164pub trait LogListener: 'static + Sync {
165    fn log(&self, session: usize, level: Level, msg: &str, logger_name: &str, tid: usize);
166    fn level(&self) -> Level;
167    fn close(&self) {}
168
169    fn log_with_stack_trace(
170        &self,
171        session: usize,
172        level: Level,
173        _stack_trace: &str,
174        msg: &str,
175        logger_name: &str,
176        tid: usize,
177    ) {
178        self.log(session, level, msg, logger_name, tid);
179    }
180}
181
182pub struct LogGuard<L: LogListener> {
183    ctxt: *mut L,
184}
185
186impl<L: LogListener> Drop for LogGuard<L> {
187    fn drop(&mut self) {
188        use binaryninjacore_sys::BNUnregisterLogListener;
189
190        let mut bn_obj = BNLogListener {
191            context: self.ctxt as *mut _,
192            log: Some(cb_log::<L>),
193            logWithStackTrace: Some(cb_log_with_stack_trace::<L>),
194            close: Some(cb_close::<L>),
195            getLogLevel: Some(cb_level::<L>),
196        };
197
198        unsafe {
199            BNUnregisterLogListener(&mut bn_obj);
200            BNUpdateLogListeners();
201
202            let _listener = Box::from_raw(self.ctxt);
203        }
204    }
205}
206
207pub fn register_listener<L: LogListener>(listener: L) -> LogGuard<L> {
208    use binaryninjacore_sys::BNRegisterLogListener;
209
210    let raw = Box::into_raw(Box::new(listener));
211    let mut bn_obj = BNLogListener {
212        context: raw as *mut _,
213        log: Some(cb_log::<L>),
214        logWithStackTrace: Some(cb_log_with_stack_trace::<L>),
215        close: Some(cb_close::<L>),
216        getLogLevel: Some(cb_level::<L>),
217    };
218
219    unsafe {
220        BNRegisterLogListener(&mut bn_obj);
221        BNUpdateLogListeners();
222    }
223
224    LogGuard { ctxt: raw }
225}
226
227extern "C" fn cb_log<L>(
228    ctxt: *mut c_void,
229    session: usize,
230    level: Level,
231    msg: *const c_char,
232    logger_name: *const c_char,
233    tid: usize,
234) where
235    L: LogListener,
236{
237    ffi_wrap!("LogListener::log", unsafe {
238        let listener = &*(ctxt as *const L);
239        let msg_str = raw_to_string(msg).unwrap();
240        let logger_name_str = raw_to_string(logger_name).unwrap();
241        listener.log(session, level, &msg_str, &logger_name_str, tid);
242    })
243}
244
245extern "C" fn cb_log_with_stack_trace<L>(
246    ctxt: *mut c_void,
247    session: usize,
248    level: Level,
249    stack_trace: *const c_char,
250    msg: *const c_char,
251    logger_name: *const c_char,
252    tid: usize,
253) where
254    L: LogListener,
255{
256    ffi_wrap!("LogListener::log_with_stack_trace", unsafe {
257        let listener = &*(ctxt as *const L);
258        let stack_trace_str = raw_to_string(stack_trace).unwrap();
259        let msg_str = raw_to_string(msg).unwrap();
260        let logger_name_str = raw_to_string(logger_name).unwrap();
261        listener.log_with_stack_trace(
262            session,
263            level,
264            &stack_trace_str,
265            &msg_str,
266            &logger_name_str,
267            tid,
268        );
269    })
270}
271
272extern "C" fn cb_close<L>(ctxt: *mut c_void)
273where
274    L: LogListener,
275{
276    ffi_wrap!("LogListener::close", unsafe {
277        let listener = &*(ctxt as *const L);
278        listener.close();
279    })
280}
281
282extern "C" fn cb_level<L>(ctxt: *mut c_void) -> Level
283where
284    L: LogListener,
285{
286    ffi_wrap!("LogListener::log", unsafe {
287        let listener = &*(ctxt as *const L);
288        listener.level()
289    })
290}