Search code examples
rustwasm-pack

"Stream" messages from rust to javascript


I want to implement a logger for the log create, that allows me to stream / asynchronously publish the messages to JavaScript, so they can be displayed to the user.

Currently, I have a very simple setup, that requires JavaScript to "ask" for the logs, which isn't optimal, since I'm not really in a context, where I only want to display the logs, after something is done, but while it's happening.

static LOGGER: Logger = Logger::new();

#[wasm_bindgen]
pub struct Logger {
    logs: Mutex<Vec<String>>,
    level: Mutex<LogLevel>,
}

impl Logger {
    pub fn init_once() {
        static INIT: Once = Once::new();
        INIT.call_once(|| {
            log::set_logger(&LOGGER).unwrap();
            log::set_max_level(log::LevelFilter::max());
        })
    }
}

#[wasm_bindgen]
impl Logger {
    pub fn get_logs() -> Vec<String> {
        let logs = LOGGER
            .logs
            .lock()
            .expect("failed to acquire a lock to logger while getting logs");

        logs.clone()
    }
}

I've tried adding a add_log_listener and remove_log_listener method, but found that this doesn't work, since js_sys::Function is at some point a *mut u* (I'm guessing that some sort of pointer? And for some reason it's *mut?), which doesn't implement Send, which is required by the log::Log trait.

I don't have any particular visions on how the API should work, as long as the logs can be displayd in a asynchrounous manner :)


PS: The displaying is currently being done by the Monaco Editor, so just passing in an HTML-Element and appending to it, isn't that easy. And I'm also not sure if that would be more thread safe. Haven't looked too deep into it. And I really would like the display logic to not necessarily be mixed with my logging logic... if possible.

I have also thought about polling the logger with a setInterval from JavaScript, but that sounds quite hacky. It shouldn't be a problem right now, I think, but as soon as more than one thingy calls get_logs() (and consequently clear_logs()), that wouldn't work anymore.


Solution

  • There are two options:

    • Store the callback in a thread local. Unless you actually use WASM threads (which are unstable anyway), this will be the same.
    • Create a specific thread that owns the callback, and send events to it via channels.

    However, the best strategy actually is to do neither, and instead do what you dismissed as hacky - setInterval(). That is because logs can be emitted very fast, and calling JS from WASM and even more writing to the DOM is very slow, so if you will do that for every log record this will hurt your speed. You only want that at a constant rate, since the human eye cannot tell the difference anyway.