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