1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-13 14:53:33 +00:00

Support for logging from NAPI (#14335)

* Support for log to electron console from NAPI

* Fix test mock
This commit is contained in:
Daniel García
2025-04-21 12:21:00 +02:00
committed by GitHub
parent 8de42caf04
commit 86b0a6aa35
6 changed files with 102 additions and 0 deletions

View File

@@ -987,6 +987,7 @@ dependencies = [
"base64", "base64",
"desktop_core", "desktop_core",
"hex", "hex",
"log",
"napi", "napi",
"napi-build", "napi-build",
"napi-derive", "napi-derive",

View File

@@ -18,6 +18,7 @@ base64 = { workspace = true }
hex = { workspace = true } hex = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
desktop_core = { path = "../core" } desktop_core = { path = "../core" }
log = { workspace = true }
napi = { workspace = true, features = ["async"] } napi = { workspace = true, features = ["async"] }
napi-derive = { workspace = true } napi-derive = { workspace = true }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }

View File

@@ -185,3 +185,13 @@ export declare namespace crypto {
export declare namespace passkey_authenticator { export declare namespace passkey_authenticator {
export function register(): void export function register(): void
} }
export declare namespace logging {
export const enum LogLevel {
Trace = 0,
Debug = 1,
Info = 2,
Warn = 3,
Error = 4
}
export function initNapiLog(jsLogFn: (err: Error | null, arg0: LogLevel, arg1: string) => any): void
}

View File

@@ -807,3 +807,61 @@ pub mod passkey_authenticator {
}) })
} }
} }
#[napi]
pub mod logging {
use log::{Level, Metadata, Record};
use napi::threadsafe_function::{
ErrorStrategy::CalleeHandled, ThreadsafeFunction, ThreadsafeFunctionCallMode,
};
use std::sync::OnceLock;
struct JsLogger(OnceLock<ThreadsafeFunction<(LogLevel, String), CalleeHandled>>);
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,
}
}
}
#[napi]
pub fn init_napi_log(js_log_fn: ThreadsafeFunction<(LogLevel, String), CalleeHandled>) {
let _ = JS_LOGGER.0.set(js_log_fn);
let _ = log::set_logger(&JS_LOGGER);
log::set_max_level(log::LevelFilter::Debug);
}
impl log::Log for JsLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= log::max_level()
}
fn log(&self, record: &Record) {
if !self.enabled(record.metadata()) {
return;
}
let Some(logger) = self.0.get() else {
return;
};
let msg = (record.level().into(), record.args().to_string());
let _ = logger.call(Ok(msg), ThreadsafeFunctionCallMode::NonBlocking);
}
fn flush(&self) {}
}
}

View File

@@ -7,6 +7,7 @@ import log from "electron-log/main";
import { LogLevelType } from "@bitwarden/common/platform/enums/log-level-type.enum"; import { LogLevelType } from "@bitwarden/common/platform/enums/log-level-type.enum";
import { ConsoleLogService as BaseLogService } from "@bitwarden/common/platform/services/console-log.service"; import { ConsoleLogService as BaseLogService } from "@bitwarden/common/platform/services/console-log.service";
import { logging } from "@bitwarden/desktop-napi";
import { isDev } from "../../utils"; import { isDev } from "../../utils";
@@ -30,6 +31,29 @@ export class ElectronLogMainService extends BaseLogService {
ipcMain.handle("ipc.log", (_event, { level, message, optionalParams }) => { ipcMain.handle("ipc.log", (_event, { level, message, optionalParams }) => {
this.write(level, message, ...optionalParams); this.write(level, message, ...optionalParams);
}); });
logging.initNapiLog((error, level, message) => this.writeNapiLog(level, message));
}
private writeNapiLog(level: logging.LogLevel, message: string) {
let levelType: LogLevelType;
switch (level) {
case logging.LogLevel.Debug:
levelType = LogLevelType.Debug;
break;
case logging.LogLevel.Warn:
levelType = LogLevelType.Warning;
break;
case logging.LogLevel.Error:
levelType = LogLevelType.Error;
break;
default:
levelType = LogLevelType.Info;
break;
}
this.write(levelType, "[NAPI] " + message);
} }
write(level: LogLevelType, message?: any, ...optionalParams: any[]) { write(level: LogLevelType, message?: any, ...optionalParams: any[]) {

View File

@@ -5,6 +5,14 @@ jest.mock("electron", () => ({
ipcMain: { handle: jest.fn(), on: jest.fn() }, ipcMain: { handle: jest.fn(), on: jest.fn() },
})); }));
jest.mock("@bitwarden/desktop-napi", () => {
return {
logging: {
initNapiLog: jest.fn(),
},
};
});
describe("ElectronLogMainService", () => { describe("ElectronLogMainService", () => {
it("sets dev based on electron method", () => { it("sets dev based on electron method", () => {
process.env.ELECTRON_IS_DEV = "1"; process.env.ELECTRON_IS_DEV = "1";