1use tracing_appender::rolling::{RollingFileAppender, Rotation};
2use tracing_core::Subscriber;
3use tracing_subscriber::{
4 field::RecordFields,
5 fmt::{
6 self,
7 format::{DefaultFields, Writer},
8 time::FormatTime,
9 FormatEvent, FormatFields, FormattedFields,
10 },
11 layer::SubscriberExt,
12 registry::LookupSpan,
13 util::SubscriberInitExt,
14 EnvFilter, Layer,
15};
16
17use crate::tracing::LogLevel;
18
19pub fn log_panics() {
20 std::env::set_var("RUST_BACKTRACE", "1");
21
22 log_panics::init();
23}
24
25fn text_layers<S>(config: TracingConfiguration) -> impl Layer<S>
26where
27 S: Subscriber + for<'a> LookupSpan<'a>,
28{
29 struct EventFormatter {
31 display_timestamp: bool,
32 display_level: bool,
33 }
34
35 impl EventFormatter {
36 fn new() -> Self {
37 Self { display_timestamp: true, display_level: true }
38 }
39
40 #[cfg(target_os = "android")]
41 fn for_logcat() -> Self {
42 Self { display_timestamp: false, display_level: false }
44 }
45
46 fn format_timestamp(&self, writer: &mut fmt::format::Writer<'_>) -> std::fmt::Result {
47 if fmt::time::SystemTime.format_time(writer).is_err() {
48 writer.write_str("<unknown time>")?;
49 }
50 Ok(())
51 }
52
53 fn write_filename(
54 &self,
55 writer: &mut fmt::format::Writer<'_>,
56 filename: &str,
57 ) -> std::fmt::Result {
58 const CRATES_IO_PATH_MATCHER: &str = ".cargo/registry/src/index.crates.io";
59 let crates_io_filename = filename
60 .split_once(CRATES_IO_PATH_MATCHER)
61 .and_then(|(_, rest)| rest.split_once('/').map(|(_, rest)| rest));
62
63 if let Some(filename) = crates_io_filename {
64 writer.write_str("<crates.io>/")?;
65 writer.write_str(filename)
66 } else {
67 writer.write_str(filename)
68 }
69 }
70 }
71
72 impl<S, N> FormatEvent<S, N> for EventFormatter
73 where
74 S: Subscriber + for<'a> LookupSpan<'a>,
75 N: for<'a> FormatFields<'a> + 'static,
76 {
77 fn format_event(
78 &self,
79 ctx: &fmt::FmtContext<'_, S, N>,
80 mut writer: fmt::format::Writer<'_>,
81 event: &tracing_core::Event<'_>,
82 ) -> std::fmt::Result {
83 let meta = event.metadata();
84
85 if self.display_timestamp {
86 self.format_timestamp(&mut writer)?;
87 writer.write_char(' ')?;
88 }
89
90 if self.display_level {
91 write!(writer, "{:>5} ", meta.level())?;
93 }
94
95 write!(writer, "{}: ", meta.target())?;
96
97 ctx.format_fields(writer.by_ref(), event)?;
98
99 if let Some(filename) = meta.file() {
100 writer.write_str(" | ")?;
101 self.write_filename(&mut writer, filename)?;
102 if let Some(line_number) = meta.line() {
103 write!(writer, ":{line_number}")?;
104 }
105 }
106
107 if let Some(scope) = ctx.event_scope() {
108 writer.write_str(" | spans: ")?;
109
110 let mut first = true;
111
112 for span in scope.from_root() {
113 if !first {
114 writer.write_str(" > ")?;
115 }
116
117 first = false;
118
119 write!(writer, "{}", span.name())?;
120
121 if let Some(fields) = &span.extensions().get::<FormattedFields<N>>() {
122 if !fields.is_empty() {
123 write!(writer, "{{{fields}}}")?;
124 }
125 }
126 }
127 }
128
129 writeln!(writer)
130 }
131 }
132
133 let file_layer = config.write_to_files.map(|c| {
134 let mut builder = RollingFileAppender::builder()
135 .rotation(Rotation::HOURLY)
136 .filename_prefix(&c.file_prefix);
137
138 if let Some(max_files) = c.max_files {
139 builder = builder.max_log_files(max_files as usize)
140 };
141 if let Some(file_suffix) = c.file_suffix {
142 builder = builder.filename_suffix(file_suffix)
143 }
144
145 let writer = builder.build(&c.path).expect("Failed to create a rolling file appender.");
146
147 #[derive(Default)]
152 struct FieldsFormatterForFiles(DefaultFields);
153
154 impl<'writer> FormatFields<'writer> for FieldsFormatterForFiles {
155 fn format_fields<R: RecordFields>(
156 &self,
157 writer: Writer<'writer>,
158 fields: R,
159 ) -> std::fmt::Result {
160 self.0.format_fields(writer, fields)
161 }
162 }
163
164 fmt::layer()
165 .fmt_fields(FieldsFormatterForFiles::default())
166 .event_format(EventFormatter::new())
167 .with_ansi(false)
171 .with_writer(writer)
172 });
173
174 Layer::and_then(
175 file_layer,
176 config.write_to_stdout_or_system.then(|| {
177 #[derive(Default)]
182 struct FieldsFormatterFormStdoutOrSystem(DefaultFields);
183
184 impl<'writer> FormatFields<'writer> for FieldsFormatterFormStdoutOrSystem {
185 fn format_fields<R: RecordFields>(
186 &self,
187 writer: Writer<'writer>,
188 fields: R,
189 ) -> std::fmt::Result {
190 self.0.format_fields(writer, fields)
191 }
192 }
193
194 #[cfg(not(target_os = "android"))]
195 return fmt::layer()
196 .fmt_fields(FieldsFormatterFormStdoutOrSystem::default())
197 .event_format(EventFormatter::new())
198 .with_ansi(false)
200 .with_writer(std::io::stderr);
201
202 #[cfg(target_os = "android")]
203 return fmt::layer()
204 .fmt_fields(FieldsFormatterFormStdoutOrSystem::default())
205 .event_format(EventFormatter::for_logcat())
206 .with_ansi(false)
208 .with_writer(paranoid_android::AndroidLogMakeWriter::new(
209 "org.matrix.rust.sdk".to_owned(),
210 ));
211 }),
212 )
213}
214
215#[derive(uniffi::Record)]
217pub struct TracingFileConfiguration {
218 path: String,
220
221 file_prefix: String,
223
224 file_suffix: Option<String>,
226
227 max_files: Option<u64>,
232}
233
234#[derive(PartialEq, PartialOrd)]
235enum LogTarget {
236 Hyper,
237 MatrixSdkFfi,
238 MatrixSdk,
239 MatrixSdkClient,
240 MatrixSdkCrypto,
241 MatrixSdkCryptoAccount,
242 MatrixSdkOidc,
243 MatrixSdkHttpClient,
244 MatrixSdkSlidingSync,
245 MatrixSdkBaseSlidingSync,
246 MatrixSdkUiTimeline,
247 MatrixSdkEventCache,
248 MatrixSdkBaseEventCache,
249 MatrixSdkEventCacheStore,
250
251 MatrixSdkCommonStoreLocks,
252 MatrixSdkBaseStoreAmbiguityMap,
253}
254
255impl LogTarget {
256 fn as_str(&self) -> &'static str {
257 match self {
258 LogTarget::Hyper => "hyper",
259 LogTarget::MatrixSdkFfi => "matrix_sdk_ffi",
260 LogTarget::MatrixSdk => "matrix_sdk",
261 LogTarget::MatrixSdkClient => "matrix_sdk::client",
262 LogTarget::MatrixSdkCrypto => "matrix_sdk_crypto",
263 LogTarget::MatrixSdkCryptoAccount => "matrix_sdk_crypto::olm::account",
264 LogTarget::MatrixSdkOidc => "matrix_sdk::oidc",
265 LogTarget::MatrixSdkHttpClient => "matrix_sdk::http_client",
266 LogTarget::MatrixSdkSlidingSync => "matrix_sdk::sliding_sync",
267 LogTarget::MatrixSdkBaseSlidingSync => "matrix_sdk_base::sliding_sync",
268 LogTarget::MatrixSdkUiTimeline => "matrix_sdk_ui::timeline",
269 LogTarget::MatrixSdkEventCache => "matrix_sdk::event_cache",
270 LogTarget::MatrixSdkBaseEventCache => "matrix_sdk_base::event_cache",
271 LogTarget::MatrixSdkEventCacheStore => "matrix_sdk_sqlite::event_cache_store",
272
273 LogTarget::MatrixSdkCommonStoreLocks => "matrix_sdk_common::store_locks",
274 LogTarget::MatrixSdkBaseStoreAmbiguityMap => "matrix_sdk_base::store::ambiguity_map",
275 }
276 }
277}
278
279const DEFAULT_TARGET_LOG_LEVELS: &[(LogTarget, LogLevel)] = &[
280 (LogTarget::Hyper, LogLevel::Warn),
281 (LogTarget::MatrixSdkFfi, LogLevel::Info),
282 (LogTarget::MatrixSdk, LogLevel::Info),
283 (LogTarget::MatrixSdkClient, LogLevel::Trace),
284 (LogTarget::MatrixSdkCrypto, LogLevel::Debug),
285 (LogTarget::MatrixSdkCryptoAccount, LogLevel::Trace),
286 (LogTarget::MatrixSdkOidc, LogLevel::Trace),
287 (LogTarget::MatrixSdkHttpClient, LogLevel::Debug),
288 (LogTarget::MatrixSdkSlidingSync, LogLevel::Info),
289 (LogTarget::MatrixSdkBaseSlidingSync, LogLevel::Info),
290 (LogTarget::MatrixSdkUiTimeline, LogLevel::Info),
291 (LogTarget::MatrixSdkEventCache, LogLevel::Info),
292 (LogTarget::MatrixSdkBaseEventCache, LogLevel::Info),
293 (LogTarget::MatrixSdkEventCacheStore, LogLevel::Info),
294 (LogTarget::MatrixSdkCommonStoreLocks, LogLevel::Warn),
295 (LogTarget::MatrixSdkBaseStoreAmbiguityMap, LogLevel::Warn),
296];
297
298const IMMUTABLE_TARGET_LOG_LEVELS: &[LogTarget] = &[
299 LogTarget::Hyper, LogTarget::MatrixSdk, LogTarget::MatrixSdkFfi, LogTarget::MatrixSdkCommonStoreLocks, LogTarget::MatrixSdkBaseStoreAmbiguityMap, ];
305
306#[derive(uniffi::Record)]
307pub struct TracingConfiguration {
308 log_level: LogLevel,
310
311 extra_targets: Option<Vec<String>>,
314
315 write_to_stdout_or_system: bool,
317
318 write_to_files: Option<TracingFileConfiguration>,
320}
321
322fn build_tracing_filter(config: &TracingConfiguration) -> String {
323 let mut filters = vec!["panic=error".to_owned()];
328
329 DEFAULT_TARGET_LOG_LEVELS.iter().for_each(|(target, level)| {
330 let level = if IMMUTABLE_TARGET_LOG_LEVELS.contains(target) || level > &config.log_level {
333 level.as_str()
334 } else {
335 config.log_level.as_str()
336 };
337
338 filters.push(format!("{}={}", target.as_str(), level));
339 });
340
341 if let Some(extra_targets) = &config.extra_targets {
343 for target in extra_targets {
344 filters.push(format!("{}={}", target, config.log_level.as_str()));
345 }
346 }
347
348 filters.join(",")
349}
350
351#[matrix_sdk_ffi_macros::export]
352pub fn setup_tracing(config: TracingConfiguration) {
353 log_panics();
354
355 tracing_subscriber::registry()
356 .with(EnvFilter::new(build_tracing_filter(&config)))
357 .with(text_layers(config))
358 .init();
359}
360
361#[cfg(test)]
362mod tests {
363 use super::build_tracing_filter;
364
365 #[test]
366 fn test_default_tracing_filter() {
367 let config = super::TracingConfiguration {
368 log_level: super::LogLevel::Error,
369 extra_targets: Some(vec!["super_duper_app".to_owned()]),
370 write_to_stdout_or_system: true,
371 write_to_files: None,
372 };
373
374 let filter = build_tracing_filter(&config);
375
376 assert_eq!(
377 filter,
378 "panic=error,\
379 hyper=warn,\
380 matrix_sdk_ffi=info,\
381 matrix_sdk=info,\
382 matrix_sdk::client=trace,\
383 matrix_sdk_crypto=debug,\
384 matrix_sdk_crypto::olm::account=trace,\
385 matrix_sdk::oidc=trace,\
386 matrix_sdk::http_client=debug,\
387 matrix_sdk::sliding_sync=info,\
388 matrix_sdk_base::sliding_sync=info,\
389 matrix_sdk_ui::timeline=info,\
390 matrix_sdk::event_cache=info,\
391 matrix_sdk_base::event_cache=info,\
392 matrix_sdk_sqlite::event_cache_store=info,\
393 matrix_sdk_common::store_locks=warn,\
394 matrix_sdk_base::store::ambiguity_map=warn,\
395 super_duper_app=error"
396 );
397 }
398
399 #[test]
400 fn test_trace_tracing_filter() {
401 let config = super::TracingConfiguration {
402 log_level: super::LogLevel::Trace,
403 extra_targets: Some(vec!["super_duper_app".to_owned(), "some_other_span".to_owned()]),
404 write_to_stdout_or_system: true,
405 write_to_files: None,
406 };
407
408 let filter = build_tracing_filter(&config);
409
410 assert_eq!(
411 filter,
412 "panic=error,\
413 hyper=warn,\
414 matrix_sdk_ffi=info,\
415 matrix_sdk=info,\
416 matrix_sdk::client=trace,\
417 matrix_sdk_crypto=trace,\
418 matrix_sdk_crypto::olm::account=trace,\
419 matrix_sdk::oidc=trace,\
420 matrix_sdk::http_client=trace,\
421 matrix_sdk::sliding_sync=trace,\
422 matrix_sdk_base::sliding_sync=trace,\
423 matrix_sdk_ui::timeline=trace,\
424 matrix_sdk::event_cache=trace,\
425 matrix_sdk_base::event_cache=trace,\
426 matrix_sdk_sqlite::event_cache_store=trace,\
427 matrix_sdk_common::store_locks=warn,\
428 matrix_sdk_base::store::ambiguity_map=warn,\
429 super_duper_app=trace,\
430 some_other_span=trace"
431 );
432 }
433}