1#![allow(clippy::needless_doctest_main)]
2
3pub 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
78impl Ref<Logger> {
81 pub fn with_level(mut self, level: LevelFilter) -> Ref<Logger> {
82 self.level = level;
83 self
84 }
85
86 pub fn init(self) {
91 log::set_max_level(self.level);
92 let _ = log::set_boxed_logger(Box::new(self));
93 }
94
95 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}