1#[cfg(feature = "sentry")]
2use std::borrow::ToOwned;
3use std::{
4 collections::BTreeMap,
5 sync::{Arc, Mutex},
6};
7
8use once_cell::sync::OnceCell;
9use tracing::{callsite::DefaultCallsite, debug, error, field::FieldSet, Callsite};
10use tracing_core::{identify_callsite, metadata::Kind as MetadataKind};
11
12#[matrix_sdk_ffi_macros::export]
25fn log_event(file: String, line: Option<u32>, level: LogLevel, target: String, message: String) {
26 static CALLSITES: Mutex<BTreeMap<MetadataId, &'static DefaultCallsite>> =
27 Mutex::new(BTreeMap::new());
28
29 let id = MetadataId { file, line, level, target, name: None };
30 let callsite = get_or_init_metadata(&CALLSITES, id, &["message"], MetadataKind::EVENT);
31
32 if span_or_event_enabled(callsite) {
33 let metadata = callsite.metadata();
34 let fields = metadata.fields();
35 let message_field = fields.field("message").unwrap();
36 #[allow(trivial_casts)] let values = [(&message_field, Some(&message as &dyn tracing::Value))];
38
39 let values = fields.value_set(&values);
44 tracing::Event::dispatch(metadata, &values);
45 }
46}
47
48type FieldNames = &'static [&'static str];
49
50fn get_or_init_metadata(
51 mutex: &Mutex<BTreeMap<MetadataId, &'static DefaultCallsite>>,
52 id: MetadataId,
53 field_names: FieldNames,
54 meta_kind: MetadataKind,
55) -> &'static DefaultCallsite {
56 mutex.lock().unwrap().entry(id).or_insert_with_key(|id| {
57 let callsite = Box::leak(Box::new(LateInitCallsite(OnceCell::new())));
58 let metadata = Box::leak(Box::new(tracing::Metadata::new(
59 Box::leak(
60 id.name
61 .clone()
62 .unwrap_or_else(|| match id.line {
63 Some(line) => format!("event {}:{line}", id.file),
64 None => format!("event {}", id.file),
65 })
66 .into_boxed_str(),
67 ),
68 Box::leak(id.target.as_str().into()),
69 id.level.to_tracing_level(),
70 Some(Box::leak(Box::from(id.file.as_str()))),
71 id.line,
72 None, FieldSet::new(field_names, identify_callsite!(callsite)),
74 meta_kind,
75 )));
76 callsite.0.try_insert(DefaultCallsite::new(metadata)).expect("callsite was not set before")
77 })
78}
79
80fn span_or_event_enabled(callsite: &'static DefaultCallsite) -> bool {
81 use tracing::{
82 dispatcher,
83 level_filters::{LevelFilter, STATIC_MAX_LEVEL},
84 };
85
86 let meta = callsite.metadata();
87 let level = *meta.level();
88
89 if level > STATIC_MAX_LEVEL || level > LevelFilter::current() {
90 false
91 } else {
92 let interest = callsite.interest();
93 interest.is_always()
94 || !interest.is_never() && dispatcher::get_default(|default| default.enabled(meta))
95 }
96}
97
98#[derive(uniffi::Object)]
99pub struct Span(tracing::Span);
100
101pub(crate) const BRIDGE_SPAN_NAME: &str = "<sdk_bridge_span>";
102
103#[matrix_sdk_ffi_macros::export]
104impl Span {
105 #[uniffi::constructor]
130 pub fn new(
131 file: String,
132 line: Option<u32>,
133 level: LogLevel,
134 target: String,
135 name: String,
136 bridge_trace_id: Option<String>,
137 ) -> Arc<Self> {
138 static CALLSITES: Mutex<BTreeMap<MetadataId, &'static DefaultCallsite>> =
139 Mutex::new(BTreeMap::new());
140
141 let loc = MetadataId { file, line, level, target, name: Some(name) };
142
143 let bridge_trace_id = if cfg!(feature = "sentry") { bridge_trace_id } else { None };
145
146 let callsite = if cfg!(feature = "sentry") {
147 get_or_init_metadata(&CALLSITES, loc, &["sentry", "sentry.trace"], MetadataKind::SPAN)
148 } else {
149 get_or_init_metadata(&CALLSITES, loc, &[], MetadataKind::SPAN)
150 };
151
152 let metadata = callsite.metadata();
153
154 let span = if span_or_event_enabled(callsite) {
155 let fields = metadata.fields();
157
158 if let Some(parent_trace_id) = bridge_trace_id {
159 debug!("Adding fields | sentry:true, sentry.trace={parent_trace_id}");
160 let sentry_field = fields.field("sentry").unwrap();
161 let sentry_trace_field = fields.field("sentry.trace").unwrap();
162 #[allow(trivial_casts)] let values = [
164 (&sentry_field, Some(&true as &dyn tracing::Value)),
165 (&sentry_trace_field, Some(&parent_trace_id as &dyn tracing::Value)),
166 ];
167 tracing::Span::new(metadata, &fields.value_set(&values))
168 } else {
169 tracing::Span::new(metadata, &fields.value_set(&[]))
170 }
171 } else {
172 tracing::Span::none()
173 };
174
175 Arc::new(Self(span))
176 }
177
178 #[uniffi::constructor]
179 pub fn current() -> Arc<Self> {
180 Arc::new(Self(tracing::Span::current()))
181 }
182
183 fn enter(&self) {
184 self.0.with_subscriber(|(id, dispatch)| dispatch.enter(id));
185 }
186
187 fn exit(&self) {
188 self.0.with_subscriber(|(id, dispatch)| dispatch.exit(id));
189 }
190
191 fn is_none(&self) -> bool {
192 self.0.is_none()
193 }
194
195 #[uniffi::constructor]
200 pub fn new_bridge_span(target: String, parent_trace_id: Option<String>) -> Arc<Self> {
201 if cfg!(feature = "sentry") {
202 Self::new(
203 "Bridge".to_owned(),
204 None,
205 LogLevel::Info,
206 target,
207 BRIDGE_SPAN_NAME.to_owned(),
208 parent_trace_id,
209 )
210 } else {
211 error!("Sentry is not enabled!");
212 Arc::new(Self(tracing::Span::none()))
213 }
214 }
215}
216
217#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, uniffi::Enum)]
218pub enum LogLevel {
219 Error,
220 Warn,
221 Info,
222 Debug,
223 Trace,
224}
225
226impl LogLevel {
227 fn to_tracing_level(self) -> tracing::Level {
228 match self {
229 LogLevel::Error => tracing::Level::ERROR,
230 LogLevel::Warn => tracing::Level::WARN,
231 LogLevel::Info => tracing::Level::INFO,
232 LogLevel::Debug => tracing::Level::DEBUG,
233 LogLevel::Trace => tracing::Level::TRACE,
234 }
235 }
236
237 pub(crate) fn as_str(&self) -> &'static str {
238 match self {
239 LogLevel::Error => "error",
240 LogLevel::Warn => "warn",
241 LogLevel::Info => "info",
242 LogLevel::Debug => "debug",
243 LogLevel::Trace => "trace",
244 }
245 }
246}
247
248#[derive(PartialEq, Eq, PartialOrd, Ord)]
249struct MetadataId {
250 file: String,
251 line: Option<u32>,
252 level: LogLevel,
253 target: String,
254 name: Option<String>,
255}
256
257struct LateInitCallsite(OnceCell<DefaultCallsite>);
258
259impl Callsite for LateInitCallsite {
260 fn set_interest(&self, interest: tracing_core::Interest) {
261 self.0
262 .get()
263 .expect("Callsite impl must not be used before initialization")
264 .set_interest(interest)
265 }
266
267 fn metadata(&self) -> &tracing::Metadata<'_> {
268 self.0.get().expect("Callsite impl must not be used before initialization").metadata()
269 }
270}