matrix_sdk_ffi/
platform.rs

1use std::sync::OnceLock;
2#[cfg(feature = "sentry")]
3use std::sync::{atomic::AtomicBool, Arc};
4
5#[cfg(feature = "sentry")]
6use tracing::warn;
7use tracing_appender::rolling::{RollingFileAppender, Rotation};
8use tracing_core::Subscriber;
9use tracing_subscriber::{
10    field::RecordFields,
11    fmt::{
12        self,
13        format::{DefaultFields, Writer},
14        time::FormatTime,
15        FormatEvent, FormatFields, FormattedFields,
16    },
17    layer::{Layered, SubscriberExt as _},
18    registry::LookupSpan,
19    reload::{self, Handle},
20    util::SubscriberInitExt as _,
21    EnvFilter, Layer, Registry,
22};
23
24use crate::{error::ClientError, tracing::LogLevel};
25
26// Adjusted version of tracing_subscriber::fmt::Format
27struct EventFormatter {
28    display_timestamp: bool,
29    display_level: bool,
30}
31
32impl EventFormatter {
33    fn new() -> Self {
34        Self { display_timestamp: true, display_level: true }
35    }
36
37    #[cfg(target_os = "android")]
38    fn for_logcat() -> Self {
39        // Level and time are already captured by logcat separately
40        Self { display_timestamp: false, display_level: false }
41    }
42
43    fn format_timestamp(&self, writer: &mut fmt::format::Writer<'_>) -> std::fmt::Result {
44        if fmt::time::SystemTime.format_time(writer).is_err() {
45            writer.write_str("<unknown time>")?;
46        }
47        Ok(())
48    }
49
50    fn write_filename(
51        &self,
52        writer: &mut fmt::format::Writer<'_>,
53        filename: &str,
54    ) -> std::fmt::Result {
55        const CRATES_IO_PATH_MATCHER: &str = ".cargo/registry/src/index.crates.io";
56        let crates_io_filename = filename
57            .split_once(CRATES_IO_PATH_MATCHER)
58            .and_then(|(_, rest)| rest.split_once('/').map(|(_, rest)| rest));
59
60        if let Some(filename) = crates_io_filename {
61            writer.write_str("<crates.io>/")?;
62            writer.write_str(filename)
63        } else {
64            writer.write_str(filename)
65        }
66    }
67}
68
69impl<S, N> FormatEvent<S, N> for EventFormatter
70where
71    S: Subscriber + for<'a> LookupSpan<'a>,
72    N: for<'a> FormatFields<'a> + 'static,
73{
74    fn format_event(
75        &self,
76        ctx: &fmt::FmtContext<'_, S, N>,
77        mut writer: fmt::format::Writer<'_>,
78        event: &tracing_core::Event<'_>,
79    ) -> std::fmt::Result {
80        let meta = event.metadata();
81
82        if self.display_timestamp {
83            self.format_timestamp(&mut writer)?;
84            writer.write_char(' ')?;
85        }
86
87        if self.display_level {
88            // For info and warn, add a padding space to the left
89            write!(writer, "{:>5} ", meta.level())?;
90        }
91
92        write!(writer, "{}: ", meta.target())?;
93
94        ctx.format_fields(writer.by_ref(), event)?;
95
96        if let Some(filename) = meta.file() {
97            writer.write_str(" | ")?;
98            self.write_filename(&mut writer, filename)?;
99            if let Some(line_number) = meta.line() {
100                write!(writer, ":{line_number}")?;
101            }
102        }
103
104        if let Some(scope) = ctx.event_scope() {
105            writer.write_str(" | spans: ")?;
106
107            let mut first = true;
108
109            for span in scope.from_root() {
110                if !first {
111                    writer.write_str(" > ")?;
112                }
113
114                first = false;
115
116                write!(writer, "{}", span.name())?;
117
118                if let Some(fields) = &span.extensions().get::<FormattedFields<N>>() {
119                    if !fields.is_empty() {
120                        write!(writer, "{{{fields}}}")?;
121                    }
122                }
123            }
124        }
125
126        writeln!(writer)
127    }
128}
129
130// Another fields formatter is necessary because of this bug
131// https://github.com/tokio-rs/tracing/issues/1372. Using a new
132// formatter for the fields forces to record them in different span
133// extensions, and thus remove the duplicated fields in the span.
134#[derive(Default)]
135struct FieldsFormatterForFiles(DefaultFields);
136
137impl<'writer> FormatFields<'writer> for FieldsFormatterForFiles {
138    fn format_fields<R: RecordFields>(
139        &self,
140        writer: Writer<'writer>,
141        fields: R,
142    ) -> std::fmt::Result {
143        self.0.format_fields(writer, fields)
144    }
145}
146
147type ReloadHandle = Handle<
148    tracing_subscriber::fmt::Layer<
149        Layered<EnvFilter, Registry>,
150        FieldsFormatterForFiles,
151        EventFormatter,
152        RollingFileAppender,
153    >,
154    Layered<EnvFilter, Registry>,
155>;
156
157fn text_layers(
158    config: TracingConfiguration,
159) -> (impl Layer<Layered<EnvFilter, Registry>>, Option<ReloadHandle>) {
160    let (file_layer, reload_handle) = config
161        .write_to_files
162        .map(|c| {
163            let layer = make_file_layer(c);
164            reload::Layer::new(layer)
165        })
166        .unzip();
167
168    let layers = Layer::and_then(
169        file_layer,
170        config.write_to_stdout_or_system.then(|| {
171            // Another fields formatter is necessary because of this bug
172            // https://github.com/tokio-rs/tracing/issues/1372. Using a new
173            // formatter for the fields forces to record them in different span
174            // extensions, and thus remove the duplicated fields in the span.
175            #[derive(Default)]
176            struct FieldsFormatterFormStdoutOrSystem(DefaultFields);
177
178            impl<'writer> FormatFields<'writer> for FieldsFormatterFormStdoutOrSystem {
179                fn format_fields<R: RecordFields>(
180                    &self,
181                    writer: Writer<'writer>,
182                    fields: R,
183                ) -> std::fmt::Result {
184                    self.0.format_fields(writer, fields)
185                }
186            }
187
188            #[cfg(not(target_os = "android"))]
189            return fmt::layer()
190                .fmt_fields(FieldsFormatterFormStdoutOrSystem::default())
191                .event_format(EventFormatter::new())
192                // See comment above.
193                .with_ansi(false)
194                .with_writer(std::io::stderr);
195
196            #[cfg(target_os = "android")]
197            return fmt::layer()
198                .fmt_fields(FieldsFormatterFormStdoutOrSystem::default())
199                .event_format(EventFormatter::for_logcat())
200                // See comment above.
201                .with_ansi(false)
202                .with_writer(paranoid_android::AndroidLogMakeWriter::new(
203                    "org.matrix.rust.sdk".to_owned(),
204                ));
205        }),
206    );
207
208    (layers, reload_handle)
209}
210
211fn make_file_layer(
212    file_configuration: TracingFileConfiguration,
213) -> tracing_subscriber::fmt::Layer<
214    Layered<EnvFilter, Registry, Registry>,
215    FieldsFormatterForFiles,
216    EventFormatter,
217    RollingFileAppender,
218> {
219    let mut builder = RollingFileAppender::builder()
220        .rotation(Rotation::HOURLY)
221        .filename_prefix(&file_configuration.file_prefix);
222
223    if let Some(max_files) = file_configuration.max_files {
224        builder = builder.max_log_files(max_files as usize)
225    }
226    if let Some(file_suffix) = file_configuration.file_suffix {
227        builder = builder.filename_suffix(file_suffix)
228    }
229
230    let writer =
231        builder.build(&file_configuration.path).expect("Failed to create a rolling file appender.");
232
233    fmt::layer()
234        .fmt_fields(FieldsFormatterForFiles::default())
235        .event_format(EventFormatter::new())
236        // EventFormatter doesn't support ANSI colors anyways, but the
237        // default field formatter does, which is unhelpful for iOS +
238        // Android logs, but enabled by default.
239        .with_ansi(false)
240        .with_writer(writer)
241}
242
243/// Configuration to save logs to (rotated) log-files.
244#[derive(uniffi::Record)]
245pub struct TracingFileConfiguration {
246    /// Base location for all the log files.
247    path: String,
248
249    /// Prefix for the log files' names.
250    file_prefix: String,
251
252    /// Optional suffix for the log file's names.
253    file_suffix: Option<String>,
254
255    /// Maximum number of rotated files.
256    ///
257    /// If not set, there's no max limit, i.e. the number of log files is
258    /// unlimited.
259    max_files: Option<u64>,
260}
261
262#[derive(PartialEq, PartialOrd)]
263enum LogTarget {
264    // External crates.
265    Hyper,
266
267    // FFI modules.
268    MatrixSdkFfi,
269
270    // SDK base modules.
271    MatrixSdkBaseEventCache,
272    MatrixSdkBaseSlidingSync,
273    MatrixSdkBaseStoreAmbiguityMap,
274
275    // SDK common modules.
276    MatrixSdkCommonStoreLocks,
277
278    // SDK modules.
279    MatrixSdk,
280    MatrixSdkClient,
281    MatrixSdkCrypto,
282    MatrixSdkCryptoAccount,
283    MatrixSdkEventCache,
284    MatrixSdkEventCacheStore,
285    MatrixSdkHttpClient,
286    MatrixSdkOidc,
287    MatrixSdkSendQueue,
288    MatrixSdkSlidingSync,
289
290    // SDK UI modules.
291    MatrixSdkUiTimeline,
292    MatrixSdkUiNotificationClient,
293}
294
295impl LogTarget {
296    fn as_str(&self) -> &'static str {
297        match self {
298            LogTarget::Hyper => "hyper",
299            LogTarget::MatrixSdkFfi => "matrix_sdk_ffi",
300            LogTarget::MatrixSdkBaseEventCache => "matrix_sdk_base::event_cache",
301            LogTarget::MatrixSdkBaseSlidingSync => "matrix_sdk_base::sliding_sync",
302            LogTarget::MatrixSdkBaseStoreAmbiguityMap => "matrix_sdk_base::store::ambiguity_map",
303            LogTarget::MatrixSdkCommonStoreLocks => "matrix_sdk_common::store_locks",
304            LogTarget::MatrixSdk => "matrix_sdk",
305            LogTarget::MatrixSdkClient => "matrix_sdk::client",
306            LogTarget::MatrixSdkCrypto => "matrix_sdk_crypto",
307            LogTarget::MatrixSdkCryptoAccount => "matrix_sdk_crypto::olm::account",
308            LogTarget::MatrixSdkOidc => "matrix_sdk::oidc",
309            LogTarget::MatrixSdkHttpClient => "matrix_sdk::http_client",
310            LogTarget::MatrixSdkSlidingSync => "matrix_sdk::sliding_sync",
311            LogTarget::MatrixSdkEventCache => "matrix_sdk::event_cache",
312            LogTarget::MatrixSdkSendQueue => "matrix_sdk::send_queue",
313            LogTarget::MatrixSdkEventCacheStore => "matrix_sdk_sqlite::event_cache_store",
314            LogTarget::MatrixSdkUiTimeline => "matrix_sdk_ui::timeline",
315            LogTarget::MatrixSdkUiNotificationClient => "matrix_sdk_ui::notification_client",
316        }
317    }
318}
319
320const DEFAULT_TARGET_LOG_LEVELS: &[(LogTarget, LogLevel)] = &[
321    (LogTarget::Hyper, LogLevel::Warn),
322    (LogTarget::MatrixSdkFfi, LogLevel::Info),
323    (LogTarget::MatrixSdk, LogLevel::Info),
324    (LogTarget::MatrixSdkClient, LogLevel::Trace),
325    (LogTarget::MatrixSdkCrypto, LogLevel::Debug),
326    (LogTarget::MatrixSdkCryptoAccount, LogLevel::Trace),
327    (LogTarget::MatrixSdkOidc, LogLevel::Trace),
328    (LogTarget::MatrixSdkHttpClient, LogLevel::Debug),
329    (LogTarget::MatrixSdkSlidingSync, LogLevel::Info),
330    (LogTarget::MatrixSdkBaseSlidingSync, LogLevel::Info),
331    (LogTarget::MatrixSdkUiTimeline, LogLevel::Info),
332    (LogTarget::MatrixSdkSendQueue, LogLevel::Info),
333    (LogTarget::MatrixSdkEventCache, LogLevel::Info),
334    (LogTarget::MatrixSdkBaseEventCache, LogLevel::Info),
335    (LogTarget::MatrixSdkEventCacheStore, LogLevel::Info),
336    (LogTarget::MatrixSdkCommonStoreLocks, LogLevel::Warn),
337    (LogTarget::MatrixSdkBaseStoreAmbiguityMap, LogLevel::Warn),
338    (LogTarget::MatrixSdkUiNotificationClient, LogLevel::Info),
339];
340
341const IMMUTABLE_LOG_TARGETS: &[LogTarget] = &[
342    LogTarget::Hyper,                          // Too verbose
343    LogTarget::MatrixSdk,                      // Too generic
344    LogTarget::MatrixSdkFfi,                   // Too verbose
345    LogTarget::MatrixSdkCommonStoreLocks,      // Too verbose
346    LogTarget::MatrixSdkBaseStoreAmbiguityMap, // Too verbose
347];
348
349/// A log pack can be used to set the trace log level for a group of multiple
350/// log targets at once, for debugging purposes.
351#[derive(uniffi::Enum)]
352pub enum TraceLogPacks {
353    /// Enables all the logs relevant to the event cache.
354    EventCache,
355    /// Enables all the logs relevant to the send queue.
356    SendQueue,
357    /// Enables all the logs relevant to the timeline.
358    Timeline,
359    /// Enables all the logs relevant to the notification client.
360    NotificationClient,
361}
362
363impl TraceLogPacks {
364    // Note: all the log targets returned here must be part of
365    // `DEFAULT_TARGET_LOG_LEVELS`.
366    fn targets(&self) -> &[LogTarget] {
367        match self {
368            TraceLogPacks::EventCache => &[
369                LogTarget::MatrixSdkEventCache,
370                LogTarget::MatrixSdkBaseEventCache,
371                LogTarget::MatrixSdkEventCacheStore,
372            ],
373            TraceLogPacks::SendQueue => &[LogTarget::MatrixSdkSendQueue],
374            TraceLogPacks::Timeline => &[LogTarget::MatrixSdkUiTimeline],
375            TraceLogPacks::NotificationClient => &[LogTarget::MatrixSdkUiNotificationClient],
376        }
377    }
378}
379
380#[cfg(feature = "sentry")]
381struct SentryLoggingCtx {
382    /// The Sentry client guard, which keeps the Sentry context alive.
383    _guard: sentry::ClientInitGuard,
384
385    /// Whether the Sentry layer is enabled or not, at a global level.
386    enabled: Arc<AtomicBool>,
387}
388
389struct LoggingCtx {
390    reload_handle: Option<ReloadHandle>,
391    #[cfg(feature = "sentry")]
392    sentry: Option<SentryLoggingCtx>,
393}
394
395static LOGGING: OnceLock<LoggingCtx> = OnceLock::new();
396
397#[derive(uniffi::Record)]
398pub struct TracingConfiguration {
399    /// The desired log level.
400    log_level: LogLevel,
401
402    /// All the log packs, that will be set to `TRACE` when they're enabled.
403    trace_log_packs: Vec<TraceLogPacks>,
404
405    /// Additional targets that the FFI client would like to use.
406    ///
407    /// This can include, for instance, the target names for created
408    /// [`crate::tracing::Span`]. These targets will use the global log level by
409    /// default.
410    extra_targets: Vec<String>,
411
412    /// Whether to log to stdout, or in the logcat on Android.
413    write_to_stdout_or_system: bool,
414
415    /// If set, configures rotated log files where to write additional logs.
416    write_to_files: Option<TracingFileConfiguration>,
417
418    /// If set, the Sentry DSN to use for error reporting.
419    #[cfg(feature = "sentry")]
420    sentry_dsn: Option<String>,
421}
422
423impl TracingConfiguration {
424    /// Sets up the tracing configuration and return a [`Logger`] instance
425    /// holding onto it.
426    #[cfg_attr(not(feature = "sentry"), allow(unused_mut))]
427    fn build(mut self) -> LoggingCtx {
428        // Show full backtraces, if we run into panics.
429        std::env::set_var("RUST_BACKTRACE", "1");
430
431        // Log panics.
432        log_panics::init();
433
434        let env_filter = build_tracing_filter(&self);
435
436        let logging_ctx;
437        #[cfg(feature = "sentry")]
438        {
439            // Prepare the Sentry layer, if a DSN is provided.
440            let (sentry_layer, sentry_logging_ctx) =
441                if let Some(sentry_dsn) = self.sentry_dsn.take() {
442                    // Initialize the Sentry client with the given options.
443                    let sentry_guard = sentry::init((
444                        sentry_dsn,
445                        sentry::ClientOptions {
446                            traces_sample_rate: 0.0,
447                            attach_stacktrace: true,
448                            release: Some(env!("VERGEN_GIT_SHA").into()),
449                            ..sentry::ClientOptions::default()
450                        },
451                    ));
452
453                    let sentry_enabled = Arc::new(AtomicBool::new(true));
454
455                    // Add a Sentry layer to the tracing subscriber.
456                    //
457                    // Pass custom event and span filters, which will ignore anything, if the Sentry
458                    // support has been globally disabled, or if the statement doesn't include a
459                    // `sentry` field set to `true`.
460                    let sentry_layer = sentry_tracing::layer()
461                        .event_filter({
462                            let enabled = sentry_enabled.clone();
463
464                            move |metadata| {
465                                if enabled.load(std::sync::atomic::Ordering::SeqCst)
466                                    && metadata.fields().field("sentry").is_some()
467                                {
468                                    sentry_tracing::default_event_filter(metadata)
469                                } else {
470                                    // Ignore the event.
471                                    sentry_tracing::EventFilter::Ignore
472                                }
473                            }
474                        })
475                        .span_filter({
476                            let enabled = sentry_enabled.clone();
477
478                            move |metadata| {
479                                if enabled.load(std::sync::atomic::Ordering::SeqCst) {
480                                    sentry_tracing::default_span_filter(metadata)
481                                } else {
482                                    // Ignore, if sentry is globally disabled.
483                                    false
484                                }
485                            }
486                        });
487
488                    (
489                        Some(sentry_layer),
490                        Some(SentryLoggingCtx { _guard: sentry_guard, enabled: sentry_enabled }),
491                    )
492                } else {
493                    (None, None)
494                };
495            let (text_layers, reload_handle) = crate::platform::text_layers(self);
496
497            tracing_subscriber::registry()
498                .with(tracing_subscriber::EnvFilter::new(&env_filter))
499                .with(text_layers)
500                .with(sentry_layer)
501                .init();
502            logging_ctx = LoggingCtx { reload_handle, sentry: sentry_logging_ctx };
503        }
504        #[cfg(not(feature = "sentry"))]
505        {
506            let (text_layers, reload_handle) = crate::platform::text_layers(self);
507            tracing_subscriber::registry()
508                .with(tracing_subscriber::EnvFilter::new(&env_filter))
509                .with(text_layers)
510                .init();
511            logging_ctx = LoggingCtx { reload_handle };
512        }
513
514        // Log the log levels 🧠.
515        tracing::info!(env_filter, "Logging has been set up");
516
517        logging_ctx
518    }
519}
520
521fn build_tracing_filter(config: &TracingConfiguration) -> String {
522    // We are intentionally not setting a global log level because we don't want to
523    // risk third party crates logging sensitive information.
524    // As such we need to make sure that panics will be properly logged.
525    // On 2025-01-08, `log_panics` uses the `panic` target, at the error log level.
526    let mut filters = vec!["panic=error".to_owned()];
527
528    let global_level = config.log_level;
529
530    DEFAULT_TARGET_LOG_LEVELS.iter().for_each(|(target, default_level)| {
531        let level = if IMMUTABLE_LOG_TARGETS.contains(target) {
532            // If the target is immutable, keep the log level.
533            *default_level
534        } else if config.trace_log_packs.iter().any(|pack| pack.targets().contains(target)) {
535            // If a log pack includes that target, set the associated log level to TRACE.
536            LogLevel::Trace
537        } else if *default_level > global_level {
538            // If the default level is more verbose than the global level, keep the default.
539            *default_level
540        } else {
541            // Otherwise, use the global level.
542            global_level
543        };
544
545        filters.push(format!("{}={}", target.as_str(), level.as_str()));
546    });
547
548    // Finally append the extra targets requested by the client.
549    for target in &config.extra_targets {
550        filters.push(format!("{}={}", target, config.log_level.as_str()));
551    }
552
553    filters.join(",")
554}
555
556/// Sets up logs and the tokio runtime for the current application.
557///
558/// If `use_lightweight_tokio_runtime` is set to true, this will set up a
559/// lightweight tokio runtime, for processes that have memory limitations (like
560/// the NSE process on iOS). Otherwise, this can remain false, in which case a
561/// multithreaded tokio runtime will be set up.
562#[matrix_sdk_ffi_macros::export]
563pub fn init_platform(
564    config: TracingConfiguration,
565    use_lightweight_tokio_runtime: bool,
566) -> Result<(), ClientError> {
567    #[cfg(all(feature = "js", target_family = "wasm"))]
568    {
569        console_error_panic_hook::set_once();
570    }
571    #[cfg(not(target_family = "wasm"))]
572    {
573        LOGGING.set(config.build()).map_err(|_| ClientError::Generic {
574            msg: "logger already initialized".to_owned(),
575            details: None,
576        })?;
577
578        if use_lightweight_tokio_runtime {
579            setup_lightweight_tokio_runtime();
580        } else {
581            setup_multithreaded_tokio_runtime();
582        }
583    }
584
585    Ok(())
586}
587
588/// Set the global enablement level for the Sentry layer (after the logs have
589/// been set up).
590#[matrix_sdk_ffi_macros::export]
591#[cfg(feature = "sentry")]
592pub fn enable_sentry_logging(enabled: bool) {
593    if let Some(ctx) = LOGGING.get() {
594        if let Some(sentry_ctx) = &ctx.sentry {
595            sentry_ctx.enabled.store(enabled, std::sync::atomic::Ordering::SeqCst);
596        } else {
597            warn!("Sentry logging is not enabled");
598        }
599    } else {
600        // Can't use log statements here, since logging hasn't been enabled yet 🧠
601        eprintln!("Logging hasn't been enabled yet");
602    };
603}
604
605/// Updates the tracing subscriber with a new file writer based on the provided
606/// configuration.
607///
608/// This method will throw if `init_platform` hasn't been called, or if it was
609/// called with `write_to_files` set to `None`.
610#[matrix_sdk_ffi_macros::export]
611pub fn reload_tracing_file_writer(
612    configuration: TracingFileConfiguration,
613) -> Result<(), ClientError> {
614    let Some(logging_context) = LOGGING.get() else {
615        return Err(ClientError::Generic {
616            msg: "Logging hasn't been initialized yet".to_owned(),
617            details: None,
618        });
619    };
620
621    let Some(reload_handle) = logging_context.reload_handle.as_ref() else {
622        return Err(ClientError::Generic {
623            msg: "Logging wasn't initialized with a file config".to_owned(),
624            details: None,
625        });
626    };
627
628    let layer = make_file_layer(configuration);
629    reload_handle.reload(layer).map_err(|error| ClientError::Generic {
630        msg: format!("Failed to reload file config: {error}"),
631        details: None,
632    })
633}
634
635#[cfg(not(target_family = "wasm"))]
636fn setup_multithreaded_tokio_runtime() {
637    async_compat::set_runtime_builder(Box::new(|| {
638        eprintln!("spawning a multithreaded tokio runtime");
639
640        let mut builder = tokio::runtime::Builder::new_multi_thread();
641        builder.enable_all();
642        builder
643    }));
644}
645
646#[cfg(not(target_family = "wasm"))]
647fn setup_lightweight_tokio_runtime() {
648    async_compat::set_runtime_builder(Box::new(|| {
649        eprintln!("spawning a lightweight tokio runtime");
650
651        // Get the number of available cores through the system, if possible.
652        let num_available_cores =
653            std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1);
654
655        // The number of worker threads will be either that or 4, whichever is smaller.
656        let num_worker_threads = num_available_cores.min(4);
657
658        // Chosen by a fair dice roll.
659        let num_blocking_threads = 2;
660
661        // 1 MiB of memory per worker thread. Should be enough for everyoneâ„¢.
662        let max_memory_bytes = 1024 * 1024;
663
664        let mut builder = tokio::runtime::Builder::new_multi_thread();
665
666        builder
667            .enable_all()
668            .worker_threads(num_worker_threads)
669            .thread_stack_size(max_memory_bytes)
670            .max_blocking_threads(num_blocking_threads);
671
672        builder
673    }));
674}
675
676#[cfg(test)]
677mod tests {
678    use super::build_tracing_filter;
679    use crate::platform::TraceLogPacks;
680
681    #[test]
682    fn test_default_tracing_filter() {
683        let config = super::TracingConfiguration {
684            log_level: super::LogLevel::Error,
685            trace_log_packs: Vec::new(),
686            extra_targets: vec!["super_duper_app".to_owned()],
687            write_to_stdout_or_system: true,
688            write_to_files: None,
689            #[cfg(feature = "sentry")]
690            sentry_dsn: None,
691        };
692
693        let filter = build_tracing_filter(&config);
694
695        assert_eq!(
696            filter,
697            r#"panic=error,
698            hyper=warn,
699            matrix_sdk_ffi=info,
700            matrix_sdk=info,
701            matrix_sdk::client=trace,
702            matrix_sdk_crypto=debug,
703            matrix_sdk_crypto::olm::account=trace,
704            matrix_sdk::oidc=trace,
705            matrix_sdk::http_client=debug,
706            matrix_sdk::sliding_sync=info,
707            matrix_sdk_base::sliding_sync=info,
708            matrix_sdk_ui::timeline=info,
709            matrix_sdk::send_queue=info,
710            matrix_sdk::event_cache=info,
711            matrix_sdk_base::event_cache=info,
712            matrix_sdk_sqlite::event_cache_store=info,
713            matrix_sdk_common::store_locks=warn,
714            matrix_sdk_base::store::ambiguity_map=warn,
715            matrix_sdk_ui::notification_client=info,
716            super_duper_app=error"#
717                .split('\n')
718                .map(|s| s.trim())
719                .collect::<Vec<_>>()
720                .join("")
721        );
722    }
723
724    #[test]
725    fn test_trace_tracing_filter() {
726        let config = super::TracingConfiguration {
727            log_level: super::LogLevel::Trace,
728            trace_log_packs: Vec::new(),
729            extra_targets: vec!["super_duper_app".to_owned(), "some_other_span".to_owned()],
730            write_to_stdout_or_system: true,
731            write_to_files: None,
732            #[cfg(feature = "sentry")]
733            sentry_dsn: None,
734        };
735
736        let filter = build_tracing_filter(&config);
737
738        assert_eq!(
739            filter,
740            r#"panic=error,
741            hyper=warn,
742            matrix_sdk_ffi=info,
743            matrix_sdk=info,
744            matrix_sdk::client=trace,
745            matrix_sdk_crypto=trace,
746            matrix_sdk_crypto::olm::account=trace,
747            matrix_sdk::oidc=trace,
748            matrix_sdk::http_client=trace,
749            matrix_sdk::sliding_sync=trace,
750            matrix_sdk_base::sliding_sync=trace,
751            matrix_sdk_ui::timeline=trace,
752            matrix_sdk::send_queue=trace,
753            matrix_sdk::event_cache=trace,
754            matrix_sdk_base::event_cache=trace,
755            matrix_sdk_sqlite::event_cache_store=trace,
756            matrix_sdk_common::store_locks=warn,
757            matrix_sdk_base::store::ambiguity_map=warn,
758            matrix_sdk_ui::notification_client=trace,
759            super_duper_app=trace,
760            some_other_span=trace"#
761                .split('\n')
762                .map(|s| s.trim())
763                .collect::<Vec<_>>()
764                .join("")
765        );
766    }
767
768    #[test]
769    fn test_trace_log_packs() {
770        let config = super::TracingConfiguration {
771            log_level: super::LogLevel::Info,
772            trace_log_packs: vec![TraceLogPacks::EventCache, TraceLogPacks::SendQueue],
773            extra_targets: vec!["super_duper_app".to_owned()],
774            write_to_stdout_or_system: true,
775            write_to_files: None,
776            #[cfg(feature = "sentry")]
777            sentry_dsn: None,
778        };
779
780        let filter = build_tracing_filter(&config);
781
782        assert_eq!(
783            filter,
784            r#"panic=error,
785            hyper=warn,
786            matrix_sdk_ffi=info,
787            matrix_sdk=info,
788            matrix_sdk::client=trace,
789            matrix_sdk_crypto=debug,
790            matrix_sdk_crypto::olm::account=trace,
791            matrix_sdk::oidc=trace,
792            matrix_sdk::http_client=debug,
793            matrix_sdk::sliding_sync=info,
794            matrix_sdk_base::sliding_sync=info,
795            matrix_sdk_ui::timeline=info,
796            matrix_sdk::send_queue=trace,
797            matrix_sdk::event_cache=trace,
798            matrix_sdk_base::event_cache=trace,
799            matrix_sdk_sqlite::event_cache_store=trace,
800            matrix_sdk_common::store_locks=warn,
801            matrix_sdk_base::store::ambiguity_map=warn,
802            matrix_sdk_ui::notification_client=info,
803            super_duper_app=info"#
804                .split('\n')
805                .map(|s| s.trim())
806                .collect::<Vec<_>>()
807                .join("")
808        );
809    }
810}