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