binaryninja/
logger.rs

1//! Core [`Logger`] implementation, see [`crate::tracing`] for typical plugin and headless usage.
2//!
3//! This module defines the core logger model, which is typically not used directly and instead is used
4//! via the [`crate::tracing`] implementations. If you require a custom [`LogListener`] or need to log
5//! directly to the core instead of through `tracing` macros, that is what this module is useful for.
6use crate::file_metadata::SessionId;
7use crate::rc::{Ref, RefCountable};
8use crate::string::{raw_to_string, BnString, IntoCStr};
9use binaryninjacore_sys::*;
10use std::ffi::CString;
11use std::os::raw::{c_char, c_void};
12use std::ptr::NonNull;
13
14// Used for documentation purposes.
15#[allow(unused_imports)]
16use crate::binary_view::BinaryView;
17
18pub use binaryninjacore_sys::BNLogLevel as BnLogLevel;
19
20pub const LOGGER_DEFAULT_SESSION_ID: SessionId = SessionId(0);
21
22/// Send a global log message **to the core**.
23///
24/// Prefer [`bn_log_with_session`] when a [`SessionId`] is available, via binary views file metadata.
25pub fn bn_log(logger: &str, level: BnLogLevel, msg: &str) {
26    bn_log_with_session(LOGGER_DEFAULT_SESSION_ID, logger, level, msg);
27}
28
29/// Send a session-scoped log message **to the core**.
30///
31/// The [`SessionId`] is how you attribute the log to a specific [`BinaryView`]. Without passing
32/// a session, logs will be shown in the UI globally, which you should not do if you can avoid it.
33pub fn bn_log_with_session(session_id: SessionId, logger: &str, level: BnLogLevel, msg: &str) {
34    if let Ok(msg) = CString::new(msg) {
35        let logger_name = logger.to_cstr();
36        unsafe {
37            BNLog(
38                session_id.0,
39                level,
40                logger_name.as_ptr(),
41                0,
42                c"%s".as_ptr(),
43                msg.as_ptr(),
44            )
45        }
46    }
47}
48
49#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
50pub struct Logger {
51    handle: NonNull<BNLogger>,
52}
53
54impl Logger {
55    /// Create a logger with the given name.
56    pub fn new(name: &str) -> Ref<Logger> {
57        Self::new_with_session(name, LOGGER_DEFAULT_SESSION_ID)
58    }
59
60    /// Create a logger scoped with the specific [`SessionId`], hiding the logs when the session
61    /// is not active in the UI.
62    ///
63    /// # Example
64    ///
65    /// Typically, you will want to retrieve the [`SessionId`] from the [`BinaryView`] file metadata
66    ///
67    /// ```no_run
68    /// # use binaryninja::binary_view::BinaryView;
69    /// # use binaryninja::logger::Logger;
70    /// # let bv: BinaryView = todo!();
71    /// Logger::new_with_session("MyLogger", bv.file().session_id());
72    /// ```
73    pub fn new_with_session(name: &str, session_id: SessionId) -> Ref<Logger> {
74        let name_raw = CString::new(name).unwrap();
75        let handle = unsafe { BNLogCreateLogger(name_raw.as_ptr(), session_id.0) };
76        unsafe {
77            Ref::new(Logger {
78                handle: NonNull::new(handle).unwrap(),
79            })
80        }
81    }
82
83    /// Name of the logger instance.
84    pub fn name(&self) -> String {
85        unsafe { BnString::into_string(BNLoggerGetName(self.handle.as_ptr())) }
86    }
87
88    /// The [`SessionId`] associated with the logger instance.
89    ///
90    /// The [`SessionId`] is how the core knows to associate logs with a specific opened binary,
91    /// hiding other sessions (binaries) logs when not active in the UI.
92    pub fn session_id(&self) -> Option<SessionId> {
93        let raw = unsafe { BNLoggerGetSessionId(self.handle.as_ptr()) };
94        match raw {
95            0 => None,
96            _ => Some(SessionId(raw)),
97        }
98    }
99
100    /// Send a log to the logger instance.
101    ///
102    /// If you do not have a [`Logger`] you may call [`bn_log`] or [`bn_log_with_session`].
103    pub fn log(&self, level: BnLogLevel, msg: &str) {
104        let session = self.session_id().unwrap_or(LOGGER_DEFAULT_SESSION_ID);
105        bn_log_with_session(session, &self.name(), level, msg);
106    }
107}
108
109impl Default for Ref<Logger> {
110    fn default() -> Self {
111        Logger::new("Default")
112    }
113}
114
115impl ToOwned for Logger {
116    type Owned = Ref<Self>;
117
118    fn to_owned(&self) -> Self::Owned {
119        unsafe { RefCountable::inc_ref(self) }
120    }
121}
122
123unsafe impl RefCountable for Logger {
124    unsafe fn inc_ref(logger: &Self) -> Ref<Self> {
125        Ref::new(Self {
126            handle: NonNull::new(BNNewLoggerReference(logger.handle.as_ptr())).unwrap(),
127        })
128    }
129
130    unsafe fn dec_ref(logger: &Self) {
131        BNFreeLogger(logger.handle.as_ptr());
132    }
133}
134
135unsafe impl Send for Logger {}
136unsafe impl Sync for Logger {}
137
138/// Register a [`LogListener`] that will receive log messages **from the core**.
139///
140/// This is typically used in headless usage. It can also be used to temporarily log core
141/// messages to something like a file while some analysis is occurring, once the [`LogGuard`] is
142/// dropped, the listener will be unregistered.
143#[must_use]
144pub fn register_log_listener<L: LogListener>(listener: L) -> LogGuard<L> {
145    use binaryninjacore_sys::BNRegisterLogListener;
146
147    let raw = Box::into_raw(Box::new(listener));
148    let mut bn_obj = BNLogListener {
149        context: raw as *mut _,
150        log: Some(cb_log::<L>),
151        logWithStackTrace: Some(cb_log_with_stack_trace::<L>),
152        close: Some(cb_close::<L>),
153        getLogLevel: Some(cb_level::<L>),
154    };
155
156    unsafe {
157        BNRegisterLogListener(&mut bn_obj);
158        BNUpdateLogListeners();
159    }
160
161    LogGuard { ctxt: raw }
162}
163
164/// The context associated with a log message received from the core as part of a [`LogListener`].
165#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
166pub struct LogContext<'a> {
167    /// The optional [`SessionId`] associated with the log message.
168    ///
169    /// This will correspond with a [`BinaryView`] file's [`SessionId`].
170    pub session_id: Option<SessionId>,
171    /// The thread ID associated with the log message.
172    pub thread_id: usize,
173    /// The optional stack trace associated with the log message.
174    pub stack_trace: Option<&'a str>,
175    /// The target [`Logger`] for the log message.
176    pub logger_name: &'a str,
177}
178
179/// The trait implemented by objects that wish to receive log messages from the core.
180///
181/// Currently, we supply one implementation of this trait called [`crate::tracing::TracingLogListener`]
182/// which will send the core logs to the registered tracing subscriber.
183pub trait LogListener: 'static + Sync {
184    /// Called when a log message is received from the core.
185    ///
186    /// Logs will only be sent that are above the desired [`LogListener::level`].
187    fn log(&self, ctx: &LogContext, lvl: BnLogLevel, msg: &str);
188
189    /// The desired minimum log level, any logs below this level will be ignored.
190    ///
191    /// For example, returning [`BnLogLevel::InfoLog`] will result in [`BnLogLevel::DebugLog`] logs
192    /// being ignored by this listener.
193    fn level(&self) -> BnLogLevel;
194
195    /// Called when the listener is unregistered.
196    fn close(&self) {}
197}
198
199pub struct LogGuard<L: LogListener> {
200    ctxt: *mut L,
201}
202
203impl<L: LogListener> Drop for LogGuard<L> {
204    fn drop(&mut self) {
205        use binaryninjacore_sys::BNUnregisterLogListener;
206
207        let mut bn_obj = BNLogListener {
208            context: self.ctxt as *mut _,
209            log: Some(cb_log::<L>),
210            logWithStackTrace: Some(cb_log_with_stack_trace::<L>),
211            close: Some(cb_close::<L>),
212            getLogLevel: Some(cb_level::<L>),
213        };
214
215        unsafe {
216            BNUnregisterLogListener(&mut bn_obj);
217            BNUpdateLogListeners();
218
219            let _listener = Box::from_raw(self.ctxt);
220        }
221    }
222}
223
224extern "C" fn cb_log<L>(
225    ctxt: *mut c_void,
226    session: usize,
227    level: BnLogLevel,
228    msg: *const c_char,
229    logger_name: *const c_char,
230    tid: usize,
231) where
232    L: LogListener,
233{
234    ffi_wrap!("LogListener::log", unsafe {
235        let listener = &*(ctxt as *const L);
236        let msg_str = raw_to_string(msg).unwrap();
237        let logger_name_str = raw_to_string(logger_name).unwrap();
238        let session_id = match session {
239            0 => None,
240            _ => Some(SessionId(session)),
241        };
242        let ctx = LogContext {
243            session_id,
244            thread_id: tid,
245            stack_trace: None,
246            logger_name: &logger_name_str,
247        };
248        listener.log(&ctx, level, &msg_str);
249    })
250}
251
252extern "C" fn cb_log_with_stack_trace<L>(
253    ctxt: *mut c_void,
254    session: usize,
255    level: BnLogLevel,
256    stack_trace: *const c_char,
257    msg: *const c_char,
258    logger_name: *const c_char,
259    tid: usize,
260) where
261    L: LogListener,
262{
263    ffi_wrap!("LogListener::log_with_stack_trace", unsafe {
264        let listener = &*(ctxt as *const L);
265        let stack_trace_str = raw_to_string(stack_trace).unwrap();
266        let msg_str = raw_to_string(msg).unwrap();
267        let logger_name_str = raw_to_string(logger_name).unwrap();
268        let session_id = match session {
269            0 => None,
270            _ => Some(SessionId(session)),
271        };
272        let ctx = LogContext {
273            session_id,
274            thread_id: tid,
275            stack_trace: Some(&stack_trace_str),
276            logger_name: &logger_name_str,
277        };
278        listener.log(&ctx, level, &msg_str);
279    })
280}
281
282extern "C" fn cb_close<L>(ctxt: *mut c_void)
283where
284    L: LogListener,
285{
286    ffi_wrap!("LogListener::close", unsafe {
287        let listener = &*(ctxt as *const L);
288        listener.close();
289    })
290}
291
292extern "C" fn cb_level<L>(ctxt: *mut c_void) -> BnLogLevel
293where
294    L: LogListener,
295{
296    ffi_wrap!("LogListener::log", unsafe {
297        let listener = &*(ctxt as *const L);
298        listener.level()
299    })
300}