Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support changing loggers at runtime WPB-11541 #747

Merged
merged 3 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ idb = "0.6"
indexmap = "2"
itertools = "0.13"
log = { version = "0.4", features = ["kv"] }
log-reload = "0.1.0"
mls-crypto-provider = { path = "mls-provider" }
pem = "3.0"
rand = { version = "0.8", features = ["getrandom"] }
Expand Down
1 change: 1 addition & 0 deletions crypto-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ async-trait.workspace = true
tls_codec.workspace = true
async-lock.workspace = true
log.workspace = true
log-reload.workspace = true

# see https://github.com/RustCrypto/hashes/issues/404
[target.'cfg(not(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "x86")))'.dependencies]
Expand Down
42 changes: 36 additions & 6 deletions crypto-ffi/bindings/js/CoreCrypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -898,19 +898,44 @@ export enum CoreCryptoLogLevel {
}

/**
* Initializes the global logger for Core Crypto and registers the callback. Can be called only once
* Initializes the global logger for Core Crypto and registers the callback.
*
* **NOTE:** you must call this after `await CoreCrypto.init(params)` or `await CoreCrypto.deferredInit(params)`.
*
* @deprecated use {@link CoreCrypto.setLogger} instead.
*
* @param logger - the interface to be called when something is going to be logged
* @param level - the max level that should be logged
*
* NOTE: you must call this after `await CoreCrypto.init(params)` or `await CoreCrypto.deferredInit(params)`.
coriolinus marked this conversation as resolved.
Show resolved Hide resolved
**/
export function initLogger(
logger: CoreCryptoLogger,
level: CoreCryptoLogLevel,
ctx: unknown = null
): void {
const wasmLogger = new CoreCryptoWasmLogger(logger.log, ctx);
CoreCrypto.setLogger(wasmLogger, level);
CoreCrypto.setLogger(wasmLogger);
CoreCrypto.setMaxLogLevel(level);
}

/**
* Initializes the global logger for Core Crypto and registers the callback.
*
* **NOTE:** you must call this after `await CoreCrypto.init(params)` or `await CoreCrypto.deferredInit(params)`.
*
* @param logger - the interface to be called when something is going to be logged
**/
export function setLogger(logger: CoreCryptoLogger, ctx: unknown = null): void {
const wasmLogger = new CoreCryptoWasmLogger(logger.log, ctx);
CoreCrypto.setLogger(wasmLogger);
}

/**
* Sets maximum log level for logs forwarded to the logger, defaults to `Warn`.
*
* @param level - the max level that should be logged
*/
export function setMaxLogLevel(level: CoreCryptoLogLevel): void {
CoreCrypto.setMaxLogLevel(level);
}

/**
Expand Down Expand Up @@ -947,9 +972,14 @@ export class CoreCrypto {
}
}

static setLogger(logger: CoreCryptoWasmLogger, level: CoreCryptoLogLevel) {
static setLogger(logger: CoreCryptoWasmLogger) {
this.#assertModuleLoaded();
CoreCryptoFfi.set_logger(logger);
}

static setMaxLogLevel(level: CoreCryptoLogLevel) {
this.#assertModuleLoaded();
CoreCryptoFfi.set_logger(logger, level);
CoreCryptoFfi.set_max_log_level(level);
}

/**
Expand Down
95 changes: 71 additions & 24 deletions crypto-ffi/bindings/js/test/CoreCrypto.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1582,7 +1582,8 @@ test("logs are forwarded when logger is registered", async () => {
Ciphersuite,
CredentialType,
CoreCryptoLogLevel,
initLogger,
setLogger,
setMaxLogLevel,
} = await import("./corecrypto.js");

const ciphersuite =
Expand All @@ -1596,14 +1597,12 @@ test("logs are forwarded when logger is registered", async () => {
});

const logs = [];
initLogger(
{
log: (level, json_msg) => {
logs.push(json_msg);
},
setLogger({
log: (level, json_msg) => {
logs.push(json_msg);
},
CoreCryptoLogLevel.Debug
);
});
setMaxLogLevel(CoreCryptoLogLevel.Debug);

const encoder = new TextEncoder();
const conversationId = encoder.encode("invalidConversation");
Expand All @@ -1628,7 +1627,8 @@ test("logs are not forwarded when logger is registered, but log level is too hig
Ciphersuite,
CredentialType,
CoreCryptoLogLevel,
initLogger,
setLogger,
setMaxLogLevel,
} = await import("./corecrypto.js");

const ciphersuite =
Expand All @@ -1642,14 +1642,12 @@ test("logs are not forwarded when logger is registered, but log level is too hig
});

const logs = [];
initLogger(
{
log: (level, json_msg) => {
logs.push(json_msg);
},
setLogger({
log: (level, json_msg) => {
logs.push(json_msg);
},
CoreCryptoLogLevel.Warn
);
});
setMaxLogLevel(CoreCryptoLogLevel.Warn);

const encoder = new TextEncoder();
const conversationId = encoder.encode("invalidConversation");
Expand Down Expand Up @@ -1681,7 +1679,8 @@ test("errors thrown by logger are reported as errors", async () => {
Ciphersuite,
CredentialType,
CoreCryptoLogLevel,
initLogger,
setLogger,
setMaxLogLevel,
} = await import("./corecrypto.js");

const ciphersuite =
Expand All @@ -1694,14 +1693,12 @@ test("errors thrown by logger are reported as errors", async () => {
clientId: "test",
});

initLogger(
{
log: () => {
throw Error("test error");
},
setLogger({
log: () => {
throw Error("test error");
},
CoreCryptoLogLevel.Debug
);
});
setMaxLogLevel(CoreCryptoLogLevel.Debug);

const encoder = new TextEncoder();
const conversationId = encoder.encode("invalidConversation");
Expand All @@ -1724,3 +1721,53 @@ test("errors thrown by logger are reported as errors", async () => {
await page.close();
await ctx.close();
});

test("logger can be replaced", async () => {
const [ctx, page] = await initBrowser();

const logs = await page.evaluate(async () => {
const {
CoreCrypto,
Ciphersuite,
CredentialType,
CoreCryptoLogLevel,
setLogger,
setMaxLogLevel,
} = await import("./corecrypto.js");

const ciphersuite =
Ciphersuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
const credentialType = CredentialType.Basic;
const cc = await CoreCrypto.init({
databaseName: "is invalid",
key: "test",
ciphersuites: [ciphersuite],
clientId: "test",
});

const logs = [];
setLogger({
log: () => {
throw Error("Initial logger should not be active");
},
});
setLogger({
log: (level, json_msg) => {
logs.push(json_msg);
},
});
setMaxLogLevel(CoreCryptoLogLevel.Debug);

const encoder = new TextEncoder();
const conversationId = encoder.encode("invalidConversation");
await cc.createConversation(conversationId, credentialType);

await cc.wipe();
return logs;
});

expect(logs.length).toBeGreaterThan(0);

await page.close();
await ctx.close();
});
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,32 @@ suspend fun <R> CoreCryptoCentral.transaction(block: suspend (context: CoreCrypt

/**
* Initializes the logging inside Core Crypto. Not required to be called and by default there will be no logging.
* Can only be called once, otherwise errors will be thrown.
*
* @param logger a callback to implement the platform specific logging. It will receive the string with the log text from Core Crypto
* @param level the max level that should be logged. By default it will be OFF
* @param level the max level that should be logged
**/
fun initLogger(logger: CoreCryptoLogger, level: CoreCryptoLogLevel = CoreCryptoLogLevel.OFF) {
setLogger(logger, level)
@Deprecated("Use setLogger and setMaxLogLevel instead")
fun initLogger(logger: CoreCryptoLogger, level: CoreCryptoLogLevel) {
com.wire.crypto.setLoggerOnly(logger)
com.wire.crypto.setMaxLogLevel(level)
}

/**
* Initializes the logging inside Core Crypto. Not required to be called and by default there will be no logging.
*
* @param logger a callback to implement the platform specific logging. It will receive the string with the log text from Core Crypto
**/
fun setLogger(logger: CoreCryptoLogger) {
com.wire.crypto.setLoggerOnly(logger)
}

/**
* Set maximum log level of logs which are forwarded to the [CoreCryptoLogger].
*
* @param level the max level that should be logged, by default it will be WARN
*/
fun setMaxLogLevel(level: CoreCryptoLogLevel) {
com.wire.crypto.setMaxLogLevel(level)
}


Expand Down
46 changes: 40 additions & 6 deletions crypto-ffi/src/generic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
// along with this program. If not, see http://www.gnu.org/licenses/.

use log::{Level, LevelFilter, Metadata, Record};
use log_reload::ReloadLog;
use std::collections::HashMap;
use std::ops::DerefMut;
use std::ops::{Deref, DerefMut};
use std::sync::{Arc, LazyLock, Once};
use tls_codec::{Deserialize, Serialize};

use crate::UniffiCustomTypeConverter;
Expand Down Expand Up @@ -734,13 +736,38 @@ pub trait CoreCryptoCallbacks: std::fmt::Debug + Send + Sync {
) -> bool;
}

static INIT_LOGGER: Once = Once::new();
static LOGGER: LazyLock<ReloadLog<CoreCryptoLoggerWrapper>> = LazyLock::new(|| {
ReloadLog::new(CoreCryptoLoggerWrapper {
logger: Arc::new(DummyLogger {}),
})
});

/// Initializes the logger
///
/// NOTE: in a future release we will remove `level` argument.
#[uniffi::export]
pub fn set_logger(logger: Arc<dyn CoreCryptoLogger>, level: CoreCryptoLogLevel) {
set_logger_only(logger);
set_max_log_level(level);
}

/// Initializes the logger
/// WARNING: This is a global setting. Calling it twice will cause errors.
#[uniffi::export]
pub fn set_logger(logger: std::sync::Arc<dyn CoreCryptoLogger>, level: CoreCryptoLogLevel) {
let logger = CoreCryptoLoggerWrapper { logger };
log::set_boxed_logger(Box::new(logger)).unwrap();
log::set_max_level(LevelFilter::from(level));
pub fn set_logger_only(logger: Arc<dyn CoreCryptoLogger>) {
// unwrapping poisoned lock error which shouldn't happen since we don't panic while replacing the logger
LOGGER.handle().replace(CoreCryptoLoggerWrapper { logger }).unwrap();

INIT_LOGGER.call_once(|| {
log::set_logger(LOGGER.deref()).unwrap();
log::set_max_level(LevelFilter::Warn);
});
}

/// Set maximum log level forwarded to the logger
#[uniffi::export]
pub fn set_max_log_level(level: CoreCryptoLogLevel) {
log::set_max_level(level.into());
}

/// This trait is used to provide a callback mechanism to hook up the rerspective platform logging system
Expand All @@ -750,6 +777,13 @@ pub trait CoreCryptoLogger: std::fmt::Debug + Send + Sync {
/// whenever it needs to log a message.
fn log(&self, level: CoreCryptoLogLevel, json_msg: String);
}
#[derive(Debug)]
struct DummyLogger {}

impl CoreCryptoLogger for DummyLogger {
#[allow(unused_variables)]
fn log(&self, level: CoreCryptoLogLevel, json_msg: String) {}
}

#[derive(Clone)]
struct CoreCryptoLoggerWrapper {
Expand Down
Loading
Loading