Use UniFFI to export our logging API (gh-5308)

Added a new crate called `rust-log-forwarder` that forwards logs to
consumers. This does basically the same thing as `rc-log`, but it uses
UniFFI. Once our consumer apps swich over to `rust-log-forwarder`, we
can remove the `rc-log` component.
This commit is contained in:
Ben Dean-Kawamura 2023-01-04 10:27:51 -05:00
Родитель cc652b0531
Коммит 8496e93409
16 изменённых файлов: 309 добавлений и 0 удалений

Просмотреть файл

@ -50,6 +50,13 @@ projects:
- name: rustlog - name: rustlog
type: aar type: aar
description: Android hook into the log crate. description: Android hook into the log crate.
rust-log-forwarder:
path: components/support/rust-log-forwarder/android
artifactId: rust-log-forwarder
publications:
- name: rust-log-forwarder
type: aar
description: Forward logs from Rust
httpconfig: httpconfig:
path: components/viaduct/android path: components/viaduct/android
artifactId: httpconfig artifactId: httpconfig

11
Cargo.lock сгенерированный
Просмотреть файл

@ -1792,6 +1792,7 @@ dependencies = [
"places", "places",
"push", "push",
"rc_log_ffi", "rc_log_ffi",
"rust-log-forwarder",
"sync_manager", "sync_manager",
"tabs", "tabs",
"viaduct", "viaduct",
@ -1821,6 +1822,7 @@ dependencies = [
"places", "places",
"push", "push",
"rc_log_ffi", "rc_log_ffi",
"rust-log-forwarder",
"sync15", "sync15",
"tabs", "tabs",
"viaduct", "viaduct",
@ -2923,6 +2925,15 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "rust-log-forwarder"
version = "0.1.0"
dependencies = [
"log",
"parking_lot",
"uniffi",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.21" version = "0.1.21"

Просмотреть файл

@ -23,6 +23,7 @@ members = [
"components/support/rc_crypto/nss/nss_build_common", "components/support/rc_crypto/nss/nss_build_common",
"components/support/rc_crypto/nss/nss_sys", "components/support/rc_crypto/nss/nss_sys",
"components/support/rc_crypto/nss/systest", "components/support/rc_crypto/nss/systest",
"components/support/rust-log-forwarder",
"components/support/sql", "components/support/sql",
"components/support/types", "components/support/types",
"components/support/viaduct-reqwest", "components/support/viaduct-reqwest",

Просмотреть файл

@ -0,0 +1,15 @@
[package]
name = "rust-log-forwarder"
version = "0.1.0"
edition = "2021"
authors = ["application-services@mozilla.com"]
license = "MPL-2.0"
exclude = ["/android", "/ios"]
[dependencies]
log = "0.4"
parking_lot = ">=0.11,<=0.12"
uniffi = "0.23"
[build-dependencies]
uniffi = { version = "0.23", features = ["build"] }

Просмотреть файл

@ -0,0 +1,7 @@
apply from: "$rootDir/build-scripts/component-common.gradle"
apply from: "$rootDir/publish.gradle"
ext.configureUniFFIBindgen("../src/rust_log_forwarder.udl")
ext.dependsOnTheMegazord()
ext.configurePublish()

Просмотреть файл

@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.mozilla.appservices.rust_log_forwarder" />

Просмотреть файл

@ -0,0 +1,8 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
fn main() {
uniffi::generate_scaffolding("./src/rust_log_forwarder.udl").unwrap();
}

Просмотреть файл

@ -0,0 +1,32 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Logger interface for foreign code
//!
//! This is what the application code defines. It's responsible for taking rust log records and
//! feeding them to the application logging system.
pub use log::Level;
/// log::Record, except it exposes it's data as fields rather than methods
#[derive(Debug, PartialEq, Eq)]
pub struct Record {
pub level: Level,
pub target: String,
pub message: String,
}
pub trait Logger: Sync + Send {
fn log(&self, record: Record);
}
impl From<&log::Record<'_>> for Record {
fn from(record: &log::Record) -> Self {
Self {
level: record.level(),
target: record.target().to_string(),
message: record.args().to_string(),
}
}
}

Просмотреть файл

@ -0,0 +1,114 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use std::sync::atomic::{AtomicBool, Ordering};
mod foreign_logger;
mod rust_logger;
pub use foreign_logger::{Level, Logger, Record};
static HAVE_SET_MAX_LEVEL: AtomicBool = AtomicBool::new(false);
/// Set the logger to forward to.
///
/// Pass in None to disable logging.
pub fn set_logger(logger: Option<Box<dyn Logger>>) {
// Set a default max level, if none has already been set
if !HAVE_SET_MAX_LEVEL.load(Ordering::Relaxed) {
set_max_level(Level::Debug);
}
rust_logger::set_foreign_logger(logger)
}
/// Set the maximum log level filter. Records below this level will not be sent to the logger.
pub fn set_max_level(level: Level) {
log::set_max_level(level.to_level_filter());
HAVE_SET_MAX_LEVEL.store(true, Ordering::Relaxed);
}
uniffi::include_scaffolding!("rust_log_forwarder");
#[cfg(test)]
mod test {
use super::*;
use std::sync::{Arc, Mutex};
#[derive(Clone)]
struct TestLogger {
records: Arc<Mutex<Vec<Record>>>,
}
impl TestLogger {
fn new() -> Self {
Self {
records: Arc::new(Mutex::new(Vec::new())),
}
}
fn check_records(&self, correct_records: Vec<Record>) {
assert_eq!(*self.records.lock().unwrap(), correct_records);
}
fn clear_records(&self) {
self.records.lock().unwrap().clear()
}
}
impl Logger for TestLogger {
fn log(&self, record: Record) {
self.records.lock().unwrap().push(record)
}
}
#[test]
fn test_logging() {
let logger = TestLogger::new();
set_logger(Some(Box::new(logger.clone())));
log::info!("Test message");
log::warn!("Test message2");
logger.check_records(vec![
Record {
level: Level::Info,
target: "rust_log_forwarder::test".into(),
message: "Test message".into(),
},
Record {
level: Level::Warn,
target: "rust_log_forwarder::test".into(),
message: "Test message2".into(),
},
]);
logger.clear_records();
set_logger(None);
log::info!("Test message");
log::warn!("Test message2");
logger.check_records(vec![]);
}
#[test]
fn test_max_level() {
set_max_level(Level::Debug);
assert_eq!(log::max_level(), log::Level::Debug);
set_max_level(Level::Warn);
assert_eq!(log::max_level(), log::Level::Warn);
}
#[test]
fn test_max_level_default() {
HAVE_SET_MAX_LEVEL.store(false, Ordering::Relaxed);
let logger = TestLogger::new();
// Calling set_logger should set the level to `Debug' by default
set_logger(Some(Box::new(logger)));
assert_eq!(log::max_level(), log::Level::Debug);
}
#[test]
fn test_max_level_default_ignored_if_set_manually() {
HAVE_SET_MAX_LEVEL.store(false, Ordering::Relaxed);
set_max_level(Level::Warn);
// Calling set_logger should not set the level if it was set manually.
set_logger(Some(Box::new(TestLogger::new())));
assert_eq!(log::max_level(), log::Level::Warn);
}
}

Просмотреть файл

@ -0,0 +1,31 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace rust_log_forwarder {
// Set the logger to forward to.
//
// Pass in null to disable logging.
void set_logger(Logger? logger);
// Set the maximum log level filter. Records below this level will not be sent to the logger.
void set_max_level(Level level);
};
enum Level {
"Error",
"Warn",
"Info",
"Debug",
"Trace",
};
dictionary Record {
Level level;
// The target field from the Rust log crate. Usually the Rust module name, however log! calls can manually override the target name.
string target;
string message;
};
callback interface Logger {
void log(Record record);
};

Просмотреть файл

@ -0,0 +1,65 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Rust Logger implementation
//!
//! This is responsible for taking logs from the rust log crate and forwarding them to a
//! foreign_logger::Logger instance.
use crate::foreign_logger::Logger as ForeignLogger;
use parking_lot::RwLock;
use std::sync::{
atomic::{AtomicBool, Ordering},
Once,
};
// ForeignLogger to forward to
static RUST_LOGGER: Logger = Logger::new();
// Handles calling `log::set_logger`, which can only be called once.
static INIT: Once = Once::new();
struct Logger {
foreign_logger: RwLock<Option<Box<dyn ForeignLogger>>>,
is_enabled: AtomicBool,
}
impl Logger {
const fn new() -> Self {
Self {
foreign_logger: RwLock::new(None),
is_enabled: AtomicBool::new(false),
}
}
fn set_foreign_logger(&self, foreign_logger: Option<Box<dyn ForeignLogger>>) {
self.is_enabled
.store(foreign_logger.is_some(), Ordering::Relaxed);
*self.foreign_logger.write() = foreign_logger;
}
}
impl log::Log for Logger {
fn enabled(&self, _: &log::Metadata<'_>) -> bool {
self.is_enabled.load(Ordering::Relaxed)
}
fn log(&self, record: &log::Record<'_>) {
if let Some(foreign_logger) = &*self.foreign_logger.read() {
foreign_logger.log(record.into())
}
}
fn flush(&self) {}
}
pub fn set_foreign_logger(foreign_logger: Option<Box<dyn ForeignLogger>>) {
INIT.call_once(|| {
// This should be the only component that calls `log::set_logger()`. If not, then
// panic'ing seems reasonable.
log::set_logger(&RUST_LOGGER).expect(
"Failed to initialize rust-log-forwarder::Logger, other log implementation already initialized?",
);
});
RUST_LOGGER.set_foreign_logger(foreign_logger);
}

Просмотреть файл

@ -0,0 +1,8 @@
[bindings.kotlin]
package_name = "mozilla.appservices.rust_log_forwarder"
cdylib_name = "megazord"
[bindings.swift]
ffi_module_name = "MozillaRustComponents"
ffi_module_filename = "rustlogforwarderFFI"
generate_module_map = false

Просмотреть файл

@ -16,6 +16,7 @@ sync_manager = { path = "../../components/sync_manager/" }
places = { path = "../../components/places" } places = { path = "../../components/places" }
push = { path = "../../components/push" } push = { path = "../../components/push" }
rc_log_ffi = { path = "../../components/rc_log" } rc_log_ffi = { path = "../../components/rc_log" }
rust-log-forwarder = { path = "../../components/support/rust-log-forwarder" }
viaduct = { path = "../../components/viaduct" } viaduct = { path = "../../components/viaduct" }
nimbus-sdk = { path = "../../components/nimbus" } nimbus-sdk = { path = "../../components/nimbus" }
autofill = { path = "../../components/autofill" } autofill = { path = "../../components/autofill" }

Просмотреть файл

@ -16,7 +16,10 @@ pub use logins;
pub use nimbus; pub use nimbus;
pub use places; pub use places;
pub use push; pub use push;
// TODO: Drop this dependency once android-components switches to using `rust_log_forwarder` for
// log forwarding.
pub use rc_log_ffi; pub use rc_log_ffi;
pub use rust_log_forwarder;
pub use sync_manager; pub use sync_manager;
pub use tabs; pub use tabs;
pub use viaduct; pub use viaduct;

Просмотреть файл

@ -10,6 +10,7 @@ crate-type = ["staticlib"]
[dependencies] [dependencies]
rc_log_ffi = { path = "../../components/rc_log" } rc_log_ffi = { path = "../../components/rc_log" }
rust-log-forwarder = { path = "../../components/support/rust-log-forwarder" }
viaduct = { path = "../../components/viaduct" } viaduct = { path = "../../components/viaduct" }
viaduct-reqwest = { path = "../../components/support/viaduct-reqwest" } viaduct-reqwest = { path = "../../components/support/viaduct-reqwest" }
nimbus-sdk = { path = "../../components/nimbus" } nimbus-sdk = { path = "../../components/nimbus" }

Просмотреть файл

@ -13,7 +13,10 @@ pub use logins;
pub use nimbus; pub use nimbus;
pub use places; pub use places;
pub use push; pub use push;
// TODO: Drop this dependency once firefox-ios switches to using `rust_log_forwarder` for log
// forwarding.
pub use rc_log_ffi; pub use rc_log_ffi;
pub use rust_log_forwarder;
pub use sync15; pub use sync15;
pub use tabs; pub use tabs;
pub use viaduct_reqwest; pub use viaduct_reqwest;