Use logger in a dynamically loaded shared library #66

Closed
opened 2015-12-08 01:07:12 -06:00 by WiSaGaN · 9 comments
WiSaGaN commented 2015-12-08 01:07:12 -06:00 (Migrated from github.com)

Since a dynamically loaded shared library is compiled separately from the executable. The linker would need to resolve the logger in the library to the instance in the executable at run-time when we invoke "dlopen".
Having a way to ask dynamic library not allocate but to look for in run-time would make log usable in dynamically loaded library.

Since a dynamically loaded shared library is compiled separately from the executable. The linker would need to resolve the logger in the library to the instance in the executable at run-time when we invoke "dlopen". Having a way to ask dynamic library not allocate but to look for in run-time would make log usable in dynamically loaded library.
sfackler commented 2015-12-08 01:25:46 -06:00 (Migrated from github.com)

If you compile the main application and the shared library to both be dynamically linked to the log crate, they should work together.

If you compile the main application and the shared library to both be dynamically linked to the log crate, they should work together.
WiSaGaN commented 2015-12-08 01:49:58 -06:00 (Migrated from github.com)

The shared library is compiled separately. It's used with a "dlopen" in executable.

The shared library is compiled separately. It's used with a "dlopen" in executable.
sfackler commented 2015-12-08 11:07:15 -06:00 (Migrated from github.com)

I know. Configure both the main program and the shared library to dynamically rather than statically link to their dependencies and the dynamic linker should make everything work when you dlopen the plugin.

I know. Configure both the main program and the shared library to dynamically rather than statically link to their dependencies and the dynamic linker should make everything work when you dlopen the plugin.
WiSaGaN commented 2015-12-09 07:59:34 -06:00 (Migrated from github.com)

@sfackler say I have executable Foo, and dynamic library Bar, which is loaded by Foo using "dlopen" at run-time. Do you mean I should let Bar link to crate Log dynamically?

@sfackler say I have executable Foo, and dynamic library Bar, which is loaded by Foo using "dlopen" at run-time. Do you mean I should let Bar link to crate Log dynamically?
WiSaGaN commented 2016-05-24 08:05:03 -05:00 (Migrated from github.com)

Use cargo rustc -- -C link-args='-rdynamic' to compile the application with logger. ldd will dynamically link the library logger symbol to the application's location at run-time.
Closing this.

Use `cargo rustc -- -C link-args='-rdynamic'` to compile the application with logger. `ldd` will dynamically link the library logger symbol to the application's location at run-time. Closing this.
aaeATS commented 2020-08-31 19:35:34 -05:00 (Migrated from github.com)

I stumbled across this thinking its the perfect solution for my issue, but I could not get this to work.

I have a main application that initializes a logger, and then plugins to that application that are compiled as shared libraries and loaded at runtime using the libloading crate. Any logging statements using the macros from within these plugins are not picked up by my logger, even when using the compilation notes above. I'm sorry to bring up this issue years later, but any help would be appreciated. Thanks!

I stumbled across this thinking its the perfect solution for my issue, but I could not get this to work. I have a main application that initializes a logger, and then plugins to that application that are compiled as shared libraries and loaded at runtime using the libloading crate. Any logging statements using the macros from within these plugins are not picked up by my logger, even when using the compilation notes above. I'm sorry to bring up this issue years later, but any help would be appreciated. Thanks!
jlgerber commented 2021-03-11 12:24:20 -06:00 (Migrated from github.com)

I have stumbled across this as well.

I have stumbled across this as well.
aaeATS commented 2021-03-11 12:32:48 -06:00 (Migrated from github.com)

I have stumbled across this as well.

For what its worth - I switched to using env_logger crate. Then, in both my main application and my dynamically loaded libraries I call env_logger::builder().format_timestamp(None).init(); at the top, and everything seems to log the way I was originally hoping. Feels clunky, but it works for me.

Good luck

> I have stumbled across this as well. For what its worth - I switched to using env_logger crate. Then, in both my main application and my dynamically loaded libraries I call `env_logger::builder().format_timestamp(None).init();` at the top, and everything seems to log the way I was originally hoping. Feels clunky, but it works for me. Good luck
lazytiger commented 2021-06-17 05:37:56 -05:00 (Migrated from github.com)

Just put this file in your application project.

use log::{LevelFilter, Log, Metadata, Record};

#[repr(C)]
pub struct LogParam {
    pub enabled: extern "C" fn(&Metadata) -> bool,
    pub log: extern "C" fn(&Record),
    pub flush: extern "C" fn(),
    pub level: LevelFilter,
}

struct DLog;

static mut PARAM: Option<LogParam> = None;

pub fn init(param: LogParam) {
    let level = param.level;
    unsafe {
        if PARAM.is_some() {
            eprint!("log should only init once");
            return;
        }
        PARAM.replace(param);
    }
    if let Err(err) = log::set_logger(&LOGGER).map(|_| log::set_max_level(level)) {
        eprint!("set logger failed:{}", err);
    }
}

fn param() -> &'static LogParam {
    unsafe { PARAM.as_ref().unwrap() }
}

impl Log for DLog {
    fn enabled(&self, metadata: &Metadata) -> bool {
        (param().enabled)(metadata)
    }

    fn log(&self, record: &Record) {
        (param().log)(record)
    }

    fn flush(&self) {
        (param().flush)()
    }
}

static LOGGER: DLog = DLog;

#[no_mangle]
extern "C" fn enabled(meta: &Metadata) -> bool {
    log::logger().enabled(meta)
}

#[no_mangle]
extern "C" fn log(record: &Record) {
    log::logger().log(record)
}

#[no_mangle]
extern "C" fn flush() {
    log::logger().flush()
}

pub fn log_param() -> LogParam {
    LogParam {
        enabled,
        log,
        flush,
        level: log::max_level(),
    }
}

And then write a function like this in your dll project to be called in the application.

#[no_mangle]
extern "C" fn init_logger(param: LogParam) {
    init(param);
}

When the application opens the dll, just call the init_logger in the application domain like this

init_logger(log_param());

log_param function called in the application, so the function points to the application function.
init function called in the dll, but the param given from the application, so they are the same.

Just put this file in your application project. ```rust use log::{LevelFilter, Log, Metadata, Record}; #[repr(C)] pub struct LogParam { pub enabled: extern "C" fn(&Metadata) -> bool, pub log: extern "C" fn(&Record), pub flush: extern "C" fn(), pub level: LevelFilter, } struct DLog; static mut PARAM: Option<LogParam> = None; pub fn init(param: LogParam) { let level = param.level; unsafe { if PARAM.is_some() { eprint!("log should only init once"); return; } PARAM.replace(param); } if let Err(err) = log::set_logger(&LOGGER).map(|_| log::set_max_level(level)) { eprint!("set logger failed:{}", err); } } fn param() -> &'static LogParam { unsafe { PARAM.as_ref().unwrap() } } impl Log for DLog { fn enabled(&self, metadata: &Metadata) -> bool { (param().enabled)(metadata) } fn log(&self, record: &Record) { (param().log)(record) } fn flush(&self) { (param().flush)() } } static LOGGER: DLog = DLog; #[no_mangle] extern "C" fn enabled(meta: &Metadata) -> bool { log::logger().enabled(meta) } #[no_mangle] extern "C" fn log(record: &Record) { log::logger().log(record) } #[no_mangle] extern "C" fn flush() { log::logger().flush() } pub fn log_param() -> LogParam { LogParam { enabled, log, flush, level: log::max_level(), } } ``` And then write a function like this in your dll project to be called in the application. ```rust #[no_mangle] extern "C" fn init_logger(param: LogParam) { init(param); } ``` When the application opens the dll, just call the init_logger in the application domain like this ```rust init_logger(log_param()); ``` log_param function called in the application, so the function points to the application function. init function called in the dll, but the param given from the application, so they are the same.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gh-rust-lang/log#66
No description provided.