1use std::{
2 collections::BTreeMap,
3 sync::{Arc, Mutex},
4};
5
6use once_cell::sync::OnceCell;
7use tracing::{callsite::DefaultCallsite, field::FieldSet, Callsite};
8use tracing_core::{identify_callsite, metadata::Kind as MetadataKind};
9
10#[matrix_sdk_ffi_macros::export]
23fn log_event(file: String, line: Option<u32>, level: LogLevel, target: String, message: String) {
24 static CALLSITES: Mutex<BTreeMap<MetadataId, &'static DefaultCallsite>> =
25 Mutex::new(BTreeMap::new());
26
27 let id = MetadataId { file, line, level, target, name: None };
28 let callsite = get_or_init_metadata(&CALLSITES, id, &["message"], MetadataKind::EVENT);
29
30 if span_or_event_enabled(callsite) {
31 let metadata = callsite.metadata();
32 let fields = metadata.fields();
33 let message_field = fields.field("message").unwrap();
34 #[allow(trivial_casts)] let values = [(&message_field, Some(&message as &dyn tracing::Value))];
36
37 let values = fields.value_set(&values);
42 tracing::Event::dispatch(metadata, &values);
43 }
44}
45
46type FieldNames = &'static [&'static str];
47
48fn get_or_init_metadata(
49 mutex: &Mutex<BTreeMap<MetadataId, &'static DefaultCallsite>>,
50 id: MetadataId,
51 field_names: FieldNames,
52 meta_kind: MetadataKind,
53) -> &'static DefaultCallsite {
54 mutex.lock().unwrap().entry(id).or_insert_with_key(|id| {
55 let callsite = Box::leak(Box::new(LateInitCallsite(OnceCell::new())));
56 let metadata = Box::leak(Box::new(tracing::Metadata::new(
57 Box::leak(
58 id.name
59 .clone()
60 .unwrap_or_else(|| match id.line {
61 Some(line) => format!("event {}:{line}", id.file),
62 None => format!("event {}", id.file),
63 })
64 .into_boxed_str(),
65 ),
66 Box::leak(id.target.as_str().into()),
67 id.level.to_tracing_level(),
68 Some(Box::leak(Box::from(id.file.as_str()))),
69 id.line,
70 None, FieldSet::new(field_names, identify_callsite!(callsite)),
72 meta_kind,
73 )));
74 callsite.0.try_insert(DefaultCallsite::new(metadata)).expect("callsite was not set before")
75 })
76}
77
78fn span_or_event_enabled(callsite: &'static DefaultCallsite) -> bool {
79 use tracing::{
80 dispatcher,
81 level_filters::{LevelFilter, STATIC_MAX_LEVEL},
82 };
83
84 let meta = callsite.metadata();
85 let level = *meta.level();
86
87 if level > STATIC_MAX_LEVEL || level > LevelFilter::current() {
88 false
89 } else {
90 let interest = callsite.interest();
91 interest.is_always()
92 || !interest.is_never() && dispatcher::get_default(|default| default.enabled(meta))
93 }
94}
95
96#[derive(uniffi::Object)]
97pub struct Span(tracing::Span);
98
99#[matrix_sdk_ffi_macros::export]
100impl Span {
101 #[uniffi::constructor]
126 pub fn new(
127 file: String,
128 line: Option<u32>,
129 level: LogLevel,
130 target: String,
131 name: String,
132 ) -> Arc<Self> {
133 static CALLSITES: Mutex<BTreeMap<MetadataId, &'static DefaultCallsite>> =
134 Mutex::new(BTreeMap::new());
135
136 let loc = MetadataId { file, line, level, target, name: Some(name) };
137 let callsite = get_or_init_metadata(&CALLSITES, loc, &[], MetadataKind::SPAN);
138 let metadata = callsite.metadata();
139
140 let span = if span_or_event_enabled(callsite) {
141 let values = metadata.fields().value_set(&[]);
143 tracing::Span::new(metadata, &values)
144 } else {
145 tracing::Span::none()
146 };
147
148 Arc::new(Self(span))
149 }
150
151 #[uniffi::constructor]
152 pub fn current() -> Arc<Self> {
153 Arc::new(Self(tracing::Span::current()))
154 }
155
156 fn enter(&self) {
157 self.0.with_subscriber(|(id, dispatch)| dispatch.enter(id));
158 }
159
160 fn exit(&self) {
161 self.0.with_subscriber(|(id, dispatch)| dispatch.exit(id));
162 }
163
164 fn is_none(&self) -> bool {
165 self.0.is_none()
166 }
167}
168
169#[derive(PartialEq, Eq, PartialOrd, Ord, uniffi::Enum)]
170pub enum LogLevel {
171 Error,
172 Warn,
173 Info,
174 Debug,
175 Trace,
176}
177
178impl LogLevel {
179 fn to_tracing_level(&self) -> tracing::Level {
180 match self {
181 LogLevel::Error => tracing::Level::ERROR,
182 LogLevel::Warn => tracing::Level::WARN,
183 LogLevel::Info => tracing::Level::INFO,
184 LogLevel::Debug => tracing::Level::DEBUG,
185 LogLevel::Trace => tracing::Level::TRACE,
186 }
187 }
188
189 pub(crate) fn as_str(&self) -> &'static str {
190 match self {
191 LogLevel::Error => "error",
192 LogLevel::Warn => "warn",
193 LogLevel::Info => "info",
194 LogLevel::Debug => "debug",
195 LogLevel::Trace => "trace",
196 }
197 }
198}
199
200#[derive(PartialEq, Eq, PartialOrd, Ord)]
201struct MetadataId {
202 file: String,
203 line: Option<u32>,
204 level: LogLevel,
205 target: String,
206 name: Option<String>,
207}
208
209struct LateInitCallsite(OnceCell<DefaultCallsite>);
210
211impl Callsite for LateInitCallsite {
212 fn set_interest(&self, interest: tracing_core::Interest) {
213 self.0
214 .get()
215 .expect("Callsite impl must not be used before initialization")
216 .set_interest(interest)
217 }
218
219 fn metadata(&self) -> &tracing::Metadata<'_> {
220 self.0.get().expect("Callsite impl must not be used before initialization").metadata()
221 }
222}