From 3d4b8c9b2abd840cfc704710c3c441926be2b7ab Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Wed, 2 Apr 2025 13:21:53 +0200 Subject: [PATCH] Enable Rust trace log packs (#4514) * Enable Rust trace log packs This is a way to forcefully enable trace logs only for a few Rust crates in a safe way --- .../x/initializer/PlatformInitializer.kt | 1 + .../impl/developer/DeveloperSettingsEvents.kt | 2 ++ .../developer/DeveloperSettingsPresenter.kt | 21 ++++++++++++++++++ .../impl/developer/DeveloperSettingsState.kt | 2 ++ .../DeveloperSettingsStateProvider.kt | 4 ++++ .../impl/developer/DeveloperSettingsView.kt | 20 +++++++++++++++++ .../developer/DeveloperSettingsViewTest.kt | 4 ++-- .../matrix/api/tracing/TraceLogPack.kt | 22 +++++++++++++++++++ .../api/tracing/TracingConfiguration.kt | 1 + .../matrix/impl/tracing/RustTracingService.kt | 3 +-- .../impl/tracing/TraceLogPacksMapping.kt | 21 ++++++++++++++++++ .../api/store/AppPreferencesStore.kt | 4 ++++ .../impl/store/DefaultAppPreferencesStore.kt | 19 ++++++++++++++++ .../test/InMemoryAppPreferencesStore.kt | 11 ++++++++++ 14 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TraceLogPack.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/TraceLogPacksMapping.kt diff --git a/app/src/main/kotlin/io/element/android/x/initializer/PlatformInitializer.kt b/app/src/main/kotlin/io/element/android/x/initializer/PlatformInitializer.kt index 650480a0ee..6dd0d69dc0 100644 --- a/app/src/main/kotlin/io/element/android/x/initializer/PlatformInitializer.kt +++ b/app/src/main/kotlin/io/element/android/x/initializer/PlatformInitializer.kt @@ -37,6 +37,7 @@ class PlatformInitializer : Initializer { writesToFilesConfiguration = defaultWriteToDiskConfiguration(bugReporter), logLevel = logLevel, extraTargets = listOf(ELEMENT_X_TARGET), + traceLogPacks = runBlocking { preferencesStore.getTracingLogPacksFlow().first() }, ) bugReporter.setCurrentTracingLogLevel(logLevel.name) platformService.init(tracingConfiguration) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt index 4a9437a188..74c673bc8a 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt @@ -9,11 +9,13 @@ package io.element.android.features.preferences.impl.developer import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem import io.element.android.libraries.featureflag.ui.model.FeatureUiModel +import io.element.android.libraries.matrix.api.tracing.TraceLogPack sealed interface DeveloperSettingsEvents { data class UpdateEnabledFeature(val feature: FeatureUiModel, val isEnabled: Boolean) : DeveloperSettingsEvents data class SetCustomElementCallBaseUrl(val baseUrl: String?) : DeveloperSettingsEvents data class SetHideImagesAndVideos(val value: Boolean) : DeveloperSettingsEvents data class SetTracingLogLevel(val logLevel: LogLevelItem) : DeveloperSettingsEvents + data class ToggleTracingLogPack(val logPack: TraceLogPack, val enabled: Boolean) : DeveloperSettingsEvents data object ClearCache : DeveloperSettingsEvents } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt index ee57d3b8e7..aa2c5a1c50 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt @@ -15,6 +15,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.snapshots.SnapshotStateMap @@ -34,9 +35,13 @@ import io.element.android.libraries.featureflag.api.Feature import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.ui.model.FeatureUiModel +import io.element.android.libraries.matrix.api.tracing.TraceLogPack import io.element.android.libraries.preferences.api.store.AppPreferencesStore +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import java.net.URL @@ -77,6 +82,12 @@ class DeveloperSettingsPresenter @Inject constructor( appPreferencesStore.getTracingLogLevelFlow().map { AsyncData.Success(it.toLogLevelItem()) } } val tracingLogLevel by tracingLogLevelFlow.collectAsState(initial = AsyncData.Uninitialized) + val tracingLogPacks by produceState(persistentListOf()) { + appPreferencesStore.getTracingLogPacksFlow() + // Sort the entries alphabetically by its title + .map { it.sortedBy { it.title }.toPersistentList() } + .collectLatest { value = it } + } LaunchedEffect(Unit) { FeatureFlags.entries @@ -121,6 +132,15 @@ class DeveloperSettingsPresenter @Inject constructor( is DeveloperSettingsEvents.SetTracingLogLevel -> coroutineScope.launch { appPreferencesStore.setTracingLogLevel(event.logLevel.toLogLevel()) } + is DeveloperSettingsEvents.ToggleTracingLogPack -> coroutineScope.launch { + val currentPacks = tracingLogPacks.toMutableSet() + if (currentPacks.contains(event.logPack)) { + currentPacks.remove(event.logPack) + } else { + currentPacks.add(event.logPack) + } + appPreferencesStore.setTracingLogPacks(currentPacks) + } } } @@ -135,6 +155,7 @@ class DeveloperSettingsPresenter @Inject constructor( ), hideImagesAndVideos = hideImagesAndVideos, tracingLogLevel = tracingLogLevel, + tracingLogPacks = tracingLogPacks, eventSink = ::handleEvents ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt index 6bc8743439..efcfcd01d4 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt @@ -12,6 +12,7 @@ import io.element.android.features.rageshake.api.preferences.RageshakePreference import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.featureflag.ui.model.FeatureUiModel +import io.element.android.libraries.matrix.api.tracing.TraceLogPack import kotlinx.collections.immutable.ImmutableList data class DeveloperSettingsState( @@ -22,6 +23,7 @@ data class DeveloperSettingsState( val customElementCallBaseUrlState: CustomElementCallBaseUrlState, val hideImagesAndVideos: Boolean, val tracingLogLevel: AsyncData, + val tracingLogPacks: ImmutableList, val eventSink: (DeveloperSettingsEvents) -> Unit ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt index 064a208f49..18151d32c7 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt @@ -13,6 +13,8 @@ import io.element.android.features.rageshake.api.preferences.aRageshakePreferenc import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.featureflag.ui.model.aFeatureUiModelList +import io.element.android.libraries.matrix.api.tracing.TraceLogPack +import kotlinx.collections.immutable.toPersistentList open class DeveloperSettingsStateProvider : PreviewParameterProvider { override val values: Sequence @@ -33,6 +35,7 @@ fun aDeveloperSettingsState( clearCacheAction: AsyncAction = AsyncAction.Uninitialized, customElementCallBaseUrlState: CustomElementCallBaseUrlState = aCustomElementCallBaseUrlState(), hideImagesAndVideos: Boolean = false, + traceLogPacks: List = emptyList(), eventSink: (DeveloperSettingsEvents) -> Unit = {}, ) = DeveloperSettingsState( features = aFeatureUiModelList(), @@ -42,6 +45,7 @@ fun aDeveloperSettingsState( customElementCallBaseUrlState = customElementCallBaseUrlState, hideImagesAndVideos = hideImagesAndVideos, tracingLogLevel = AsyncData.Success(LogLevelItem.INFO), + tracingLogPacks = traceLogPacks.toPersistentList(), eventSink = eventSink, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt index de368b9a65..e335c0a6cd 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt @@ -7,6 +7,7 @@ package io.element.android.features.preferences.impl.developer +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.progressSemantics import androidx.compose.foundation.text.KeyboardOptions @@ -16,6 +17,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.features.preferences.impl.R import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem import io.element.android.features.rageshake.api.preferences.RageshakePreferencesView @@ -32,6 +34,7 @@ import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.featureflag.ui.FeatureListView import io.element.android.libraries.featureflag.ui.model.FeatureUiModel +import io.element.android.libraries.matrix.api.tracing.TraceLogPack import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.toPersistentList @@ -56,6 +59,7 @@ fun DeveloperSettingsView( FeatureListContent(state) } ElementCallCategory(state = state) + PreferenceCategory(title = "Rust SDK") { PreferenceDropdown( title = "Tracing log level", @@ -67,6 +71,22 @@ fun DeveloperSettingsView( } ) } + PreferenceCategory(title = "Enable trace logs per SDK feature") { + Text( + text = "Requires app reboot", + style = ElementTheme.typography.fontBodyMdRegular, + color = ElementTheme.colors.textSecondary, + modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 8.dp) + ) + for (logPack in TraceLogPack.entries) { + PreferenceSwitch( + title = logPack.title, + isChecked = state.tracingLogPacks.contains(logPack), + onCheckedChange = { isChecked -> state.eventSink(DeveloperSettingsEvents.ToggleTracingLogPack(logPack, isChecked)) } + ) + } + } + PreferenceCategory(title = "Showkase") { ListItem( headlineContent = { diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt index 94b70442d9..255c6442d9 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt @@ -68,7 +68,7 @@ class DeveloperSettingsViewTest { eventsRecorder.assertSingle(DeveloperSettingsEvents.SetCustomElementCallBaseUrl("https://call.element.dev")) } - @Config(qualifiers = "h1024dp") + @Config(qualifiers = "h1200dp") @Test fun `clicking on open showkase invokes the expected callback`() { val eventsRecorder = EventsRecorder(expectEvents = false) @@ -97,7 +97,7 @@ class DeveloperSettingsViewTest { eventsRecorder.assertSingle(DeveloperSettingsEvents.SetTracingLogLevel(LogLevelItem.DEBUG)) } - @Config(qualifiers = "h1500dp") + @Config(qualifiers = "h1700dp") @Test fun `clicking on clear cache emits the expected event`() { val eventsRecorder = EventsRecorder() diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TraceLogPack.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TraceLogPack.kt new file mode 100644 index 0000000000..b0a67a9e92 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TraceLogPack.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.tracing + +enum class TraceLogPack(val key: String) { + EVENT_CACHE("event_cache") { + override val title: String = "Event Cache" + }, + SEND_QUEUE("send_queue") { + override val title: String = "Send Queue" + }, + TIMELINE("timeline") { + override val title: String = "Timeline" + }; + + abstract val title: String +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingConfiguration.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingConfiguration.kt index db2c946bf5..f559176fa6 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingConfiguration.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingConfiguration.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.api.tracing data class TracingConfiguration( val logLevel: LogLevel, val extraTargets: List, + val traceLogPacks: Set, val writesToLogcat: Boolean, val writesToFilesConfiguration: WriteToFilesConfiguration, ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt index 60ee29dc57..07ee1dc1b9 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt @@ -51,7 +51,6 @@ fun TracingConfiguration.map(): org.matrix.rustcomponents.sdk.TracingConfigurati writeToStdoutOrSystem = writesToLogcat, logLevel = logLevel.toRustLogLevel(), extraTargets = extraTargets, - // WARNING: this should be used only to debug issues, changes to this value should *never* be published - traceLogPacks = emptyList(), + traceLogPacks = traceLogPacks.map(), writeToFiles = writesToFilesConfiguration.toTracingFileConfiguration(), ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/TraceLogPacksMapping.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/TraceLogPacksMapping.kt new file mode 100644 index 0000000000..11187a90a3 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/TraceLogPacksMapping.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.tracing + +import io.element.android.libraries.matrix.api.tracing.TraceLogPack +import org.matrix.rustcomponents.sdk.TraceLogPacks as RustTraceLogPack + +fun TraceLogPack.map(): RustTraceLogPack = when (this) { + TraceLogPack.SEND_QUEUE -> RustTraceLogPack.SEND_QUEUE + TraceLogPack.EVENT_CACHE -> RustTraceLogPack.EVENT_CACHE + TraceLogPack.TIMELINE -> RustTraceLogPack.TIMELINE +} + +fun Collection.map(): List { + return map { it.map() } +} diff --git a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt index 537c8d6040..c072229626 100644 --- a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt +++ b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.preferences.api.store import io.element.android.libraries.matrix.api.tracing.LogLevel +import io.element.android.libraries.matrix.api.tracing.TraceLogPack import kotlinx.coroutines.flow.Flow interface AppPreferencesStore { @@ -26,5 +27,8 @@ interface AppPreferencesStore { suspend fun setTracingLogLevel(logLevel: LogLevel) fun getTracingLogLevelFlow(): Flow + suspend fun setTracingLogPacks(targets: Set) + fun getTracingLogPacksFlow(): Flow> + suspend fun reset() } diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt index 06f4b05912..a05e9c48da 100644 --- a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt +++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt @@ -20,6 +20,7 @@ import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.matrix.api.tracing.LogLevel +import io.element.android.libraries.matrix.api.tracing.TraceLogPack import io.element.android.libraries.preferences.api.store.AppPreferencesStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -32,6 +33,7 @@ private val customElementCallBaseUrlKey = stringPreferencesKey("elementCallBaseU private val themeKey = stringPreferencesKey("theme") private val hideImagesAndVideosKey = booleanPreferencesKey("hideImagesAndVideos") private val logLevelKey = stringPreferencesKey("logLevel") +private val traceLogPacksKey = stringPreferencesKey("traceLogPacks") @ContributesBinding(AppScope::class) class DefaultAppPreferencesStore @Inject constructor( @@ -105,6 +107,23 @@ class DefaultAppPreferencesStore @Inject constructor( } } + override suspend fun setTracingLogPacks(targets: Set) { + val value = targets.joinToString(",") { it.key } + store.edit { prefs -> + prefs[traceLogPacksKey] = value + } + } + + override fun getTracingLogPacksFlow(): Flow> { + return store.data.map { prefs -> + prefs[traceLogPacksKey] + ?.split(",") + ?.mapNotNull { value -> TraceLogPack.entries.find { it.key == value } } + ?.toSet() + ?: emptySet() + } + } + override suspend fun reset() { store.edit { it.clear() } } diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt index 1a332b42aa..f57a7d4578 100644 --- a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt +++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.preferences.test import io.element.android.libraries.matrix.api.tracing.LogLevel +import io.element.android.libraries.matrix.api.tracing.TraceLogPack import io.element.android.libraries.preferences.api.store.AppPreferencesStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -19,6 +20,7 @@ class InMemoryAppPreferencesStore( theme: String? = null, simplifiedSlidingSyncEnabled: Boolean = false, logLevel: LogLevel = LogLevel.INFO, + traceLockPacks: Set = emptySet(), ) : AppPreferencesStore { private val isDeveloperModeEnabled = MutableStateFlow(isDeveloperModeEnabled) private val hideImagesAndVideos = MutableStateFlow(hideImagesAndVideos) @@ -26,6 +28,7 @@ class InMemoryAppPreferencesStore( private val theme = MutableStateFlow(theme) private val simplifiedSlidingSyncEnabled = MutableStateFlow(simplifiedSlidingSyncEnabled) private val logLevel = MutableStateFlow(logLevel) + private val tracingLogPacks = MutableStateFlow(traceLockPacks) override suspend fun setDeveloperModeEnabled(enabled: Boolean) { isDeveloperModeEnabled.value = enabled @@ -67,6 +70,14 @@ class InMemoryAppPreferencesStore( return logLevel } + override suspend fun setTracingLogPacks(logPacks: Set) { + tracingLogPacks.value = logPacks + } + + override fun getTracingLogPacksFlow(): Flow> { + return tracingLogPacks + } + override suspend fun reset() { // No op }