From bb05ac7daceff736b2f6856fa3c4464562d64d48 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 20 Mar 2023 10:04:52 +0100 Subject: [PATCH] ffi: Add basic tracing bindings --- Cargo.lock | 1 + Cargo.toml | 1 + bindings/matrix-sdk-ffi/Cargo.toml | 1 + bindings/matrix-sdk-ffi/src/lib.rs | 2 + bindings/matrix-sdk-ffi/src/tracing.rs | 147 +++++++++++++++++++++++++ 5 files changed, 152 insertions(+) create mode 100644 bindings/matrix-sdk-ffi/src/tracing.rs diff --git a/Cargo.lock b/Cargo.lock index ad75081d6..3feb88851 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2791,6 +2791,7 @@ dependencies = [ "tokio-stream", "tracing", "tracing-android", + "tracing-core", "tracing-opentelemetry", "tracing-subscriber", "uniffi", diff --git a/Cargo.toml b/Cargo.toml index d31f1716b..6a569353b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/bindings/matrix-sdk-ffi/Cargo.toml b/bindings/matrix-sdk-ffi/Cargo.toml index e5e84fd99..947c507b3 100644 --- a/bindings/matrix-sdk-ffi/Cargo.toml +++ b/bindings/matrix-sdk-ffi/Cargo.toml @@ -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"] } diff --git a/bindings/matrix-sdk-ffi/src/lib.rs b/bindings/matrix-sdk-ffi/src/lib.rs index 962e562b9..34ad6d808 100644 --- a/bindings/matrix-sdk-ffi/src/lib.rs +++ b/bindings/matrix-sdk-ffi/src/lib.rs @@ -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, }; } diff --git a/bindings/matrix-sdk-ffi/src/tracing.rs b/bindings/matrix-sdk-ffi/src/tracing.rs new file mode 100644 index 000000000..fda6320b0 --- /dev/null +++ b/bindings/matrix-sdk-ffi/src/tracing.rs @@ -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> = 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 { + static METADATA: Mutex> = 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>, + 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); + +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() + } +}