binaryninja-api icon indicating copy to clipboard operation
binaryninja-api copied to clipboard

[Rust API] Improve logger documentation

Open saruman9 opened this issue 9 months ago • 2 comments

Version and Platform (required):

  • Binary Ninja Version: 5.0.7290-stable
  • Edition: Commercial
  • OS: macOS
  • OS Version: 14.5
  • CPU Architecture: Apple M-series (ARM64)

Bug Description: When using the Rust API to develop a plugin, I attempted to enable logging in headless mode using:

binaryninja::logger::Logger::new("Test").init();

However, no log output is produced, even when explicitly setting the log level. This makes it difficult to debug plugins in headless mode.

Most existing Rust plugin examples use print* macros instead of the log crate, which suggests that logger.init() may not be supported in this context — or perhaps I’m missing a required step.

Steps To Reproduce:

  1. Create a headless plugin using the Binary Ninja Rust API.
  2. Call binaryninja::logger::Logger::new("Test").init() and attempt to log a message using log::info!, warn!, etc.
  3. Run the plugin in headless mode using cargo run.
  4. Observe that no output is produced in the console.

Expected Behavior: I expected that logger.init() would configure logging correctly and output log messages to the console when running in headless mode.

Screenshots/Video Recording: N/A

Binary: Not applicable — this issue concerns plugin development using the Rust API.

Additional Information: Possibly related to issue #5101 and issue #1990.

Is this the intended behavior? Should I be using a third-party logging solution instead of the built-in Binary Ninja logger when working in headless mode?

saruman9 avatar May 22 '25 09:05 saruman9

The logger constructs a log crate compatible sink so that when you have log::info!("test") the logs are output to the binary ninja logger. What you want is to register a LogListener, using register_listener.

So to answer

Should I be using a third-party logging solution instead of the built-in Binary Ninja logger when working in headless mode?

Yea, you will want to probably do something like:

use binaryninja::headless::Session;
use binaryninja::logger::{register_listener, Level, LogListener, Logger};

struct MyLogListener;

impl LogListener for MyLogListener {
    fn log(&self, session: usize, level: Level, msg: &str, logger_name: &str, tid: usize) {
        println!("[{:?}] {}: {}", level, logger_name, msg);
    }

    fn level(&self) -> Level {
        Level::DebugLog
    }
}

#[test]
fn test_log_listener() {
    // Registering the listener before i initialize the session to get the initialization logs as well.
    let guard = register_listener(MyLogListener);
    let _session = Session::new().expect("Failed to initialize session");
    
    // Also show that our logs through `log` crate are now being printed.
    Logger::new("Test").init();
    log::info!("test");
    
    // Remember the guard will close the listener once it goes out of scope! (or we manually drop it like here)
    drop(guard);
}

I am going to add this as an example, clearly the documentation (or lack thereof) is lacking!

emesare avatar May 22 '25 10:05 emesare

Thanks for the quick reply and the solution — I tried it out, and everything works as expected now. BTW, I ended up using tracing with a custom implementation of the LogListener trait.

saruman9 avatar May 22 '25 11:05 saruman9