简体   繁体   中英

Java/Rust logging bridge

I have a Java program that uses JNI to call a Rust library. In the rust code, I have calls to info! which do not appear in the output. I am 99% positive this is because I am not initializing the Rust logging system.

When I had a similar problem in Python code calling Rust code, there was a module that I added which did the work to connect the Rust logging system to the python logger.

Is there a similar bridge for Rust and Java?

I am using slf4j in Java and log::info in Rust.

I searched for an bridge but the closest I found was how to call Rust from Java.

The log library provides only a mechanism for passing log messages to a global hook. Something must call log::set_logger() with a function that does something with the messages. There are a variety of libraries you can use for this purpose ; if you want to hook up your log logging to slf4j you will need to find or write code that does that combination specifically. (I don't know much of anything about JNI+Rust so I can't say whether one exists already.)

  1. Create your own logger (don't use env_logger or others):
use std::sync::Arc;
use log::{LevelFilter, Level};
pub type LogCallbackType = Arc<dyn Fn(Level, String) + Send + Sync>;
pub struct SimpleLogger {
  callback: LogCallbackType,
  level: LevelFilter,
}
  1. Provide a function to bind a callback to your logger
impl SimpleLogger {
  pub fn init(level: LevelFilter, callback: LogCallbackType) {
    let message = std::format!("Initialize logging to {}", &level);
    callback(Level::Debug, message);

    log::set_boxed_logger(Box::new(SimpleLogger { callback, level }))
      .map(|()| log::set_max_level(level));
  }
  1. Externalize your function to other languages
use std::os::raw::c_char;
pub type NativeString = *const c_char;
pub type ExternLogCallbackType = Option<extern "C" fn(u32, NativeString)>;

#[no_mangle]
pub extern fn init_logging(level: u32, callback: ExternLogCallbackType) {
  if !callback.is_none() {
    let callback = callback.unwrap();
    let level_filter = SimpleLogger::u32_to_level_filter(level);
    SimpleLogger::init(level_filter, Arc::new(move |level, msg| {
      callback(level as u32, msg.as_ptr());
    }))
  }
}
  1. Prepare the JNA stuff
public static class LogCallback implements Callback {
  public static final Logger LOGGER = LoggerFactory.getLogger("myrustliblog");

  public void callback(int level, String message) {
    switch (level) {
      case 0:
        break;
      case 1:
        LOGGER.error(message);
        break;
      case 2:
        LOGGER.warn(message);
        break;
      case 3:
        LOGGER.info(message);
        break;
      case 4:
        LOGGER.debug(message);
        break;
      case 5:
        LOGGER.trace(message);
        break;
      default:
        LOGGER.trace(message);
        break;
    }
  }
}

public interface MyNativeInterface extends Library {
   //...other stuff ...

   String init_logging(int level, LogCallback callback);
   //...
}
  1. Finally Initialize logging from Java:
static final LogCallback LOG_CALLBACK = new LogCallback();

public static void initLogging(org.slf4j.event.Level level) {
    int iLevel = 0;
    switch (level) {
      case ERROR:
        iLevel = 1;
        break;
      case WARN:
        iLevel = 2;
        break;
      case INFO:
        iLevel = 3;
        break;
      case DEBUG:
        iLevel = 4;
        break;
      case TRACE:
        iLevel = 5;
        break;
    }
    _myInterfaceJnaInstance.init_logging(iLevel, LOG_CALLBACK);
  }

Notes:

  1. I omitted result handling in Rust. You should be able to do it
  2. This is from my running code, it works. But you might need to do a few tweaks for memory leaks (ie deallocate the strings after being logged).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM