1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-09 21:20:27 +00:00
Files
browser/apps/desktop/desktop_native/napi/src/logging.rs
2026-02-02 14:13:17 -05:00

132 lines
4.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#[napi]
pub mod logging {
//! `logging` is the interface between the native desktop's usage of the `tracing` crate
//! for logging, to intercept events and write to the JS space.
//!
//! # Example
//!
//! [Elec] 14:34:03.517 [NAPI] [INFO] desktop_core::ssh_agent::platform_ssh_agent: Starting
//! SSH Agent server {socket=/Users/foo/.bitwarden-ssh-agent.sock}
use std::{fmt::Write, sync::OnceLock};
use napi::{
bindgen_prelude::FnArgs,
threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
};
use tracing::Level;
use tracing_subscriber::{
filter::EnvFilter,
fmt::format::{DefaultVisitor, Writer},
layer::SubscriberExt,
util::SubscriberInitExt,
Layer,
};
struct JsLogger(OnceLock<ThreadsafeFunction<FnArgs<(LogLevel, String)>>>);
static JS_LOGGER: JsLogger = JsLogger(OnceLock::new());
#[napi]
pub enum LogLevel {
Trace,
Debug,
Info,
Warn,
Error,
}
impl From<&Level> for LogLevel {
fn from(level: &Level) -> Self {
match *level {
Level::TRACE => LogLevel::Trace,
Level::DEBUG => LogLevel::Debug,
Level::INFO => LogLevel::Info,
Level::WARN => LogLevel::Warn,
Level::ERROR => LogLevel::Error,
}
}
}
// JsLayer lets us intercept events and write them to the JS Logger.
struct JsLayer;
impl<S> Layer<S> for JsLayer
where
S: tracing::Subscriber,
{
// This function builds a log message buffer from the event data and
// calls the JS logger with it.
//
// For example, this log call:
//
// ```
// mod supreme {
// mod module {
// let foo = "bar";
// info!(best_variable_name = %foo, "Foo done it again.");
// }
// }
// ```
//
// , results in the following string:
//
// [INFO] supreme::module: Foo done it again. {best_variable_name=bar}
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
let mut buffer = String::new();
// create the preamble text that precedes the message and vars. e.g.:
// [INFO] desktop_core::ssh_agent::platform_ssh_agent:
let level = event.metadata().level().as_str();
let module_path = event.metadata().module_path().unwrap_or_default();
write!(&mut buffer, "[{level}] {module_path}:")
.expect("Failed to write tracing event to buffer");
let writer = Writer::new(&mut buffer);
// DefaultVisitor adds the message and variables to the buffer
let mut visitor = DefaultVisitor::new(writer, false);
event.record(&mut visitor);
let msg = (event.metadata().level().into(), buffer);
if let Some(logger) = JS_LOGGER.0.get() {
let _ = logger.call(Ok(msg.into()), ThreadsafeFunctionCallMode::NonBlocking);
};
}
}
#[napi]
pub fn init_napi_log(js_log_fn: ThreadsafeFunction<FnArgs<(LogLevel, String)>>) {
let _ = JS_LOGGER.0.set(js_log_fn);
// the log level hierarchy is determined by:
// - if RUST_LOG is detected at runtime
// - if RUST_LOG is provided at compile time
// - default to INFO
let filter = EnvFilter::builder()
.with_default_directive(
option_env!("RUST_LOG")
.unwrap_or("info")
.parse()
.expect("should provide valid log level at compile time."),
)
// parse directives from the RUST_LOG environment variable,
// overriding the default directive for matching targets.
.from_env_lossy();
// With the `tracing-log` feature enabled for the `tracing_subscriber`,
// the registry below will initialize a log compatibility layer, which allows
// the subscriber to consume log::Records as though they were tracing Events.
// https://docs.rs/tracing-subscriber/latest/tracing_subscriber/util/trait.SubscriberInitExt.html#method.init
tracing_subscriber::registry()
.with(filter)
.with(JsLayer)
.init();
}
}