ffi: Add basic tracing bindings

This commit is contained in:
Jonas Platte
2023-03-20 10:04:52 +01:00
committed by Jonas Platte
parent 0da8e56a68
commit bb05ac7dac
5 changed files with 152 additions and 0 deletions

1
Cargo.lock generated
View File

@@ -2791,6 +2791,7 @@ dependencies = [
"tokio-stream",
"tracing",
"tracing-android",
"tracing-core",
"tracing-opentelemetry",
"tracing-subscriber",
"uniffi",

View File

@@ -41,6 +41,7 @@ serde_json = "1.0.91"
thiserror = "1.0.38"
tokio = { version = "1.24", default-features = false, features = ["sync"] }
tracing = { version = "0.1.36", default-features = false, features = ["std"] }
tracing-core = "0.1.30"
uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "41e94c0a4834137d8d359b829e2d4b334f5ab5b5" }
uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs", rev = "41e94c0a4834137d8d359b829e2d4b334f5ab5b5" }
vodozemac = { git = "https://github.com/matrix-org/vodozemac", rev = "fb609ca1e4df5a7a818490ae86ac694119e41e71" }

View File

@@ -40,6 +40,7 @@ sanitize-filename-reader-friendly = "2.2.1"
serde_json = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
tracing-core = { workspace = true }
tracing-opentelemetry = { version = "0.18.0" }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }

View File

@@ -32,6 +32,7 @@ pub mod room_member;
pub mod session_verification;
pub mod sliding_sync;
pub mod timeline;
pub mod tracing;
use client::Client;
use client_builder::ClientBuilder;
@@ -128,6 +129,7 @@ mod uniffi_types {
TimelineItemContent, TimelineItemContentKind, VideoInfo, VideoMessageContent,
VirtualTimelineItem,
},
tracing::{LogLevel, Span},
ClientError,
};
}

View File

@@ -0,0 +1,147 @@
use std::{
collections::BTreeMap,
sync::{Arc, Mutex},
};
use once_cell::sync::OnceCell;
use tracing::{callsite::DefaultCallsite, field::FieldSet, Callsite};
use tracing_core::{identify_callsite, metadata::Kind as MetadataKind};
type StaticMetadata = &'static tracing::Metadata<'static>;
#[uniffi::export]
fn log_event(
file: String,
line: u32,
column: u32,
level: LogLevel,
target: String,
message: String,
) {
static METADATA: Mutex<BTreeMap<Location, StaticMetadata>> = Mutex::new(BTreeMap::new());
let loc = Location::new(file, line, column);
let metadata = get_or_init_metadata(&METADATA, loc, level, target, |loc| {
(format!("event {}:{}", loc.file, loc.line), &["message"], MetadataKind::EVENT)
});
let fields = metadata.fields();
let message_field = fields.field("message").unwrap();
#[allow(trivial_casts)] // The compiler is lying, it can't infer this cast
let values = [(&message_field, Some(&message as &dyn tracing::Value))];
// This function is hidden from docs, but we have to use it
// because there is no other way of obtaining a `ValueSet`.
// It's not entirely clear why it is private. See this issue:
// https://github.com/tokio-rs/tracing/issues/2363
let values = fields.value_set(&values);
tracing::Event::dispatch(metadata, &values);
}
#[uniffi::export]
fn make_span(
file: String,
line: u32,
column: u32,
level: LogLevel,
target: String,
name: String,
) -> Arc<Span> {
static METADATA: Mutex<BTreeMap<Location, StaticMetadata>> = Mutex::new(BTreeMap::new());
let loc = Location::new(file, line, column);
let metadata =
get_or_init_metadata(&METADATA, loc, level, target, |_loc| (name, &[], MetadataKind::SPAN));
// This function is hidden from docs, but we have to use it (see above).
let values = metadata.fields().value_set(&[]);
Arc::new(Span(tracing::Span::new(metadata, &values)))
}
type FieldNames = &'static [&'static str];
fn get_or_init_metadata(
mutex: &Mutex<BTreeMap<Location, StaticMetadata>>,
loc: Location,
level: LogLevel,
target: String,
get_details: impl FnOnce(&Location) -> (String, FieldNames, MetadataKind),
) -> StaticMetadata {
mutex.lock().unwrap().entry(loc).or_insert_with_key(|loc| {
let (name, field_names, span_kind) = get_details(loc);
let callsite = Box::leak(Box::new(LateInitCallsite(OnceCell::new())));
let metadata = Box::leak(Box::new(tracing::Metadata::new(
Box::leak(name.into_boxed_str()),
Box::leak(target.into_boxed_str()),
level.to_tracing_level(),
Some(Box::leak(Box::from(loc.file.as_str()))),
Some(loc.line),
None, // module path
FieldSet::new(field_names, identify_callsite!(callsite)),
span_kind,
)));
callsite.0.set(DefaultCallsite::new(metadata)).expect("callsite was not set before");
metadata
})
}
#[derive(uniffi::Object)]
pub struct Span(tracing::Span);
#[uniffi::export]
impl Span {
fn enter(&self) {
self.0.with_subscriber(|(id, dispatch)| dispatch.enter(id));
}
fn exit(&self) {
self.0.with_subscriber(|(id, dispatch)| dispatch.exit(id));
}
}
#[derive(uniffi::Enum)]
pub enum LogLevel {
Error,
Warn,
Info,
Debug,
Trace,
}
impl LogLevel {
fn to_tracing_level(&self) -> tracing::Level {
match self {
LogLevel::Error => tracing::Level::ERROR,
LogLevel::Warn => tracing::Level::WARN,
LogLevel::Info => tracing::Level::INFO,
LogLevel::Debug => tracing::Level::DEBUG,
LogLevel::Trace => tracing::Level::TRACE,
}
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct Location {
file: String,
line: u32,
column: u32,
}
impl Location {
fn new(file: String, line: u32, column: u32) -> Self {
Self { file, line, column }
}
}
struct LateInitCallsite(OnceCell<DefaultCallsite>);
impl Callsite for LateInitCallsite {
fn set_interest(&self, interest: tracing_core::Interest) {
self.0
.get()
.expect("Callsite impl must not be used before initialization")
.set_interest(interest)
}
fn metadata(&self) -> &tracing::Metadata<'_> {
self.0.get().expect("Callsite impl must not be used before initialization").metadata()
}
}