Merge pull request #1211 from vector-im/feature/bma/sdkLog
Add UI to configure Rust sdk log (tracing)
This commit is contained in:
@@ -17,7 +17,10 @@
|
||||
package io.element.android.x.initializer
|
||||
|
||||
import android.content.Context
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.startup.Initializer
|
||||
import io.element.android.features.preferences.impl.developer.tracing.SharedPrefTracingConfigurationStore
|
||||
import io.element.android.features.preferences.impl.developer.tracing.TargetLogLevelMapBuilder
|
||||
import io.element.android.libraries.architecture.bindings
|
||||
import io.element.android.libraries.matrix.api.tracing.TracingConfiguration
|
||||
import io.element.android.libraries.matrix.api.tracing.TracingFilterConfigurations
|
||||
@@ -34,8 +37,11 @@ class TracingInitializer : Initializer<Unit> {
|
||||
val bugReporter = appBindings.bugReporter()
|
||||
Timber.plant(tracingService.createTimberTree())
|
||||
val tracingConfiguration = if (BuildConfig.DEBUG) {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val store = SharedPrefTracingConfigurationStore(prefs)
|
||||
val builder = TargetLogLevelMapBuilder(store)
|
||||
TracingConfiguration(
|
||||
filterConfiguration = TracingFilterConfigurations.debug,
|
||||
filterConfiguration = TracingFilterConfigurations.custom(builder.getCurrentMap()),
|
||||
writesToLogcat = true,
|
||||
writesToFilesConfiguration = WriteToFilesConfiguration.Disabled
|
||||
)
|
||||
|
||||
@@ -33,6 +33,7 @@ import io.element.android.features.preferences.api.PreferencesEntryPoint
|
||||
import io.element.android.features.preferences.impl.about.AboutNode
|
||||
import io.element.android.features.preferences.impl.analytics.AnalyticsSettingsNode
|
||||
import io.element.android.features.preferences.impl.developer.DeveloperSettingsNode
|
||||
import io.element.android.features.preferences.impl.developer.tracing.ConfigureTracingNode
|
||||
import io.element.android.features.preferences.impl.root.PreferencesRootNode
|
||||
import io.element.android.libraries.architecture.BackstackNode
|
||||
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
|
||||
@@ -60,6 +61,9 @@ class PreferencesFlowNode @AssistedInject constructor(
|
||||
@Parcelize
|
||||
data object DeveloperSettings : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object ConfigureTracing : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object AnalyticsSettings : NavTarget
|
||||
|
||||
@@ -94,7 +98,15 @@ class PreferencesFlowNode @AssistedInject constructor(
|
||||
createNode<PreferencesRootNode>(buildContext, plugins = listOf(callback))
|
||||
}
|
||||
NavTarget.DeveloperSettings -> {
|
||||
createNode<DeveloperSettingsNode>(buildContext)
|
||||
val callback = object : DeveloperSettingsNode.Callback {
|
||||
override fun openConfigureTracing() {
|
||||
backstack.push(NavTarget.ConfigureTracing)
|
||||
}
|
||||
}
|
||||
createNode<DeveloperSettingsNode>(buildContext, listOf(callback))
|
||||
}
|
||||
NavTarget.ConfigureTracing -> {
|
||||
createNode<ConfigureTracingNode>(buildContext)
|
||||
}
|
||||
NavTarget.About -> {
|
||||
createNode<AboutNode>(buildContext)
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.airbnb.android.showkase.models.Showkase
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.bumble.appyx.core.plugin.plugins
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
@@ -37,6 +38,14 @@ class DeveloperSettingsNode @AssistedInject constructor(
|
||||
private val presenter: DeveloperSettingsPresenter,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun openConfigureTracing()
|
||||
}
|
||||
|
||||
private fun onOpenConfigureTracing() {
|
||||
plugins<Callback>().forEach { it.openConfigureTracing() }
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val activity = LocalContext.current as Activity
|
||||
@@ -50,6 +59,7 @@ class DeveloperSettingsNode @AssistedInject constructor(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
onOpenShowkase = ::openShowkase,
|
||||
onOpenConfigureTracing = ::onOpenConfigureTracing,
|
||||
onBackPressed = ::navigateUp
|
||||
)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
fun DeveloperSettingsView(
|
||||
state: DeveloperSettingsState,
|
||||
onOpenShowkase: () -> Unit,
|
||||
onOpenConfigureTracing: () -> Unit,
|
||||
onBackPressed: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
@@ -47,6 +48,12 @@ fun DeveloperSettingsView(
|
||||
PreferenceCategory(title = "Feature flags") {
|
||||
FeatureListContent(state)
|
||||
}
|
||||
PreferenceCategory(title = "Rust SDK") {
|
||||
PreferenceText(
|
||||
title = "Configure tracing",
|
||||
onClick = onOpenConfigureTracing,
|
||||
)
|
||||
}
|
||||
PreferenceCategory(title = "Showkase") {
|
||||
PreferenceText(
|
||||
title = "Open Showkase browser",
|
||||
@@ -109,6 +116,7 @@ private fun ContentToPreview(state: DeveloperSettingsState) {
|
||||
DeveloperSettingsView(
|
||||
state = state,
|
||||
onOpenShowkase = {},
|
||||
onOpenConfigureTracing = {},
|
||||
onBackPressed = {}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.impl.developer.tracing
|
||||
|
||||
import io.element.android.libraries.matrix.api.tracing.LogLevel
|
||||
import io.element.android.libraries.matrix.api.tracing.Target
|
||||
|
||||
sealed interface ConfigureTracingEvents {
|
||||
data class UpdateFilter(val target: Target, val logLevel: LogLevel) : ConfigureTracingEvents
|
||||
data object ResetFilters : ConfigureTracingEvents
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.impl.developer.tracing
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.libraries.di.AppScope
|
||||
|
||||
@ContributesNode(AppScope::class)
|
||||
class ConfigureTracingNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: ConfigureTracingPresenter,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
ConfigureTracingView(
|
||||
state = state,
|
||||
onBackPressed = ::navigateUp,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.impl.developer.tracing
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import javax.inject.Inject
|
||||
|
||||
class ConfigureTracingPresenter @Inject constructor(
|
||||
private val tracingConfigurationStore: TracingConfigurationStore,
|
||||
private val targetLogLevelMapBuilder: TargetLogLevelMapBuilder,
|
||||
) : Presenter<ConfigureTracingState> {
|
||||
|
||||
@Composable
|
||||
override fun present(): ConfigureTracingState {
|
||||
val modifiedMap = remember { mutableStateOf(targetLogLevelMapBuilder.getCurrentMap()) }
|
||||
|
||||
fun handleEvents(event: ConfigureTracingEvents) {
|
||||
when (event) {
|
||||
is ConfigureTracingEvents.UpdateFilter -> {
|
||||
modifiedMap.value = modifiedMap.value.toMutableMap()
|
||||
.apply { this[event.target] = event.logLevel }
|
||||
tracingConfigurationStore.storeLogLevel(event.target, event.logLevel)
|
||||
}
|
||||
ConfigureTracingEvents.ResetFilters -> {
|
||||
modifiedMap.value = targetLogLevelMapBuilder.getDefaultMap()
|
||||
tracingConfigurationStore.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ConfigureTracingState(
|
||||
targetsToLogLevel = modifiedMap.value.toImmutableMap(),
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.impl.developer.tracing
|
||||
|
||||
import io.element.android.libraries.matrix.api.tracing.LogLevel
|
||||
import io.element.android.libraries.matrix.api.tracing.Target
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
|
||||
data class ConfigureTracingState(
|
||||
val targetsToLogLevel: ImmutableMap<Target, LogLevel>,
|
||||
val eventSink: (ConfigureTracingEvents) -> Unit
|
||||
)
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.impl.developer.tracing
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.matrix.api.tracing.LogLevel
|
||||
import io.element.android.libraries.matrix.api.tracing.Target
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
|
||||
open class ConfigureTracingStateProvider : PreviewParameterProvider<ConfigureTracingState> {
|
||||
override val values: Sequence<ConfigureTracingState>
|
||||
get() = sequenceOf(
|
||||
aConfigureTracingState(),
|
||||
)
|
||||
}
|
||||
|
||||
fun aConfigureTracingState() = ConfigureTracingState(
|
||||
targetsToLogLevel = persistentMapOf(
|
||||
Target.COMMON to LogLevel.INFO,
|
||||
Target.MATRIX_SDK_FFI to LogLevel.WARN,
|
||||
Target.MATRIX_SDK_BASE_SLIDING_SYNC to LogLevel.ERROR,
|
||||
),
|
||||
eventSink = {}
|
||||
)
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.impl.developer.tracing
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material.icons.filled.ArrowDropUp
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.preview.DayNightPreviews
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
||||
import io.element.android.libraries.designsystem.theme.components.DropdownMenu
|
||||
import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.matrix.api.tracing.LogLevel
|
||||
import io.element.android.libraries.matrix.api.tracing.Target
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ConfigureTracingView(
|
||||
state: ConfigureTracingState,
|
||||
onBackPressed: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
Scaffold(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.systemBarsPadding()
|
||||
.imePadding(),
|
||||
contentWindowInsets = WindowInsets.statusBars,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
navigationIcon = {
|
||||
BackButton(onClick = onBackPressed)
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = "Configure tracing",
|
||||
style = ElementTheme.typography.aliasScreenTitle,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = { showMenu = !showMenu }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
tint = ElementTheme.materialColors.secondary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = showMenu,
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
showMenu = false
|
||||
state.eventSink.invoke(ConfigureTracingEvents.ResetFilters)
|
||||
},
|
||||
text = { Text("Reset to default") },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
Icons.Outlined.Delete,
|
||||
tint = ElementTheme.materialColors.secondary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
content = {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(it)
|
||||
.consumeWindowInsets(it)
|
||||
.verticalScroll(state = rememberScrollState())
|
||||
) {
|
||||
CrateListContent(state)
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = "Kill and restart the app for the change to take effect.",
|
||||
style = ElementTheme.typography.fontHeadingSmMedium,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CrateListContent(
|
||||
state: ConfigureTracingState,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
fun onLogLevelChange(target: Target, logLevel: LogLevel) {
|
||||
state.eventSink(ConfigureTracingEvents.UpdateFilter(target, logLevel))
|
||||
}
|
||||
|
||||
TargetAndLogLevelListView(
|
||||
modifier = modifier,
|
||||
data = state.targetsToLogLevel,
|
||||
onLogLevelChange = ::onLogLevelChange,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TargetAndLogLevelListView(
|
||||
data: ImmutableMap<Target, LogLevel>,
|
||||
onLogLevelChange: (Target, LogLevel) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
data.forEach { item ->
|
||||
fun onLogLevelChange(logLevel: LogLevel) {
|
||||
onLogLevelChange(item.key, logLevel)
|
||||
}
|
||||
|
||||
TargetAndLogLevelView(
|
||||
target = item.key,
|
||||
logLevel = item.value,
|
||||
onLogLevelChange = ::onLogLevelChange
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TargetAndLogLevelView(
|
||||
target: Target,
|
||||
logLevel: LogLevel,
|
||||
onLogLevelChange: (LogLevel) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
ListItem(
|
||||
modifier = modifier,
|
||||
headlineContent = { Text(text = target.filter.takeIf { it.isNotEmpty() } ?: "(common)") },
|
||||
trailingContent = ListItemContent.Custom {
|
||||
LogLevelDropdownMenu(
|
||||
logLevel = logLevel,
|
||||
onLogLevelChange = onLogLevelChange,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LogLevelDropdownMenu(
|
||||
logLevel: LogLevel,
|
||||
onLogLevelChange: (LogLevel) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
Box(modifier = modifier) {
|
||||
DropdownMenuItem(
|
||||
modifier = Modifier.widthIn(max = 120.dp),
|
||||
text = { Text(text = logLevel.filter) },
|
||||
onClick = { expanded = !expanded },
|
||||
trailingIcon = {
|
||||
if (expanded) {
|
||||
Icon(Icons.Default.ArrowDropUp, contentDescription = null)
|
||||
} else {
|
||||
Icon(Icons.Default.ArrowDropDown, contentDescription = null)
|
||||
}
|
||||
},
|
||||
)
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
) {
|
||||
LogLevel.values().forEach { logLevel ->
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(text = logLevel.filter)
|
||||
},
|
||||
onClick = {
|
||||
expanded = false
|
||||
onLogLevelChange(logLevel)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
internal fun ConfigureTracingViewPreview(
|
||||
@PreviewParameter(ConfigureTracingStateProvider::class) state: ConfigureTracingState
|
||||
) = ElementPreview {
|
||||
ConfigureTracingView(
|
||||
state = state,
|
||||
onBackPressed = {},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.impl.developer.tracing
|
||||
|
||||
import io.element.android.libraries.matrix.api.tracing.LogLevel
|
||||
import io.element.android.libraries.matrix.api.tracing.Target
|
||||
import io.element.android.libraries.matrix.api.tracing.TracingFilterConfigurations
|
||||
import javax.inject.Inject
|
||||
|
||||
class TargetLogLevelMapBuilder @Inject constructor(
|
||||
private val tracingConfigurationStore: TracingConfigurationStore,
|
||||
) {
|
||||
private val defaultConfig = TracingFilterConfigurations.debug
|
||||
|
||||
fun getDefaultMap(): Map<Target, LogLevel> {
|
||||
return Target.entries.associateWith { target ->
|
||||
defaultConfig.getLogLevel(target)
|
||||
?: LogLevel.INFO
|
||||
}
|
||||
}
|
||||
|
||||
fun getCurrentMap(): Map<Target, LogLevel> {
|
||||
return Target.entries.associateWith { target ->
|
||||
tracingConfigurationStore.getLogLevel(target)
|
||||
?: defaultConfig.getLogLevel(target)
|
||||
?: LogLevel.INFO
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.impl.developer.tracing
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.DefaultPreferences
|
||||
import io.element.android.libraries.matrix.api.tracing.LogLevel
|
||||
import io.element.android.libraries.matrix.api.tracing.Target
|
||||
import javax.inject.Inject
|
||||
|
||||
interface TracingConfigurationStore {
|
||||
fun getLogLevel(target: Target): LogLevel?
|
||||
fun storeLogLevel(target: Target, logLevel: LogLevel)
|
||||
fun reset()
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class SharedPrefTracingConfigurationStore @Inject constructor(
|
||||
@DefaultPreferences private val sharedPreferences: SharedPreferences
|
||||
) : TracingConfigurationStore {
|
||||
override fun getLogLevel(target: Target): LogLevel? {
|
||||
return sharedPreferences.getString("$KEY_PREFIX${target.name}", null)
|
||||
?.let { LogLevel.valueOf(it) }
|
||||
}
|
||||
|
||||
override fun storeLogLevel(target: Target, logLevel: LogLevel) {
|
||||
sharedPreferences.edit {
|
||||
putString("$KEY_PREFIX${target.name}", logLevel.name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
sharedPreferences.edit {
|
||||
sharedPreferences.all.keys.filter { it.startsWith(KEY_PREFIX) }.forEach {
|
||||
remove(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_PREFIX = "tracing_log_level_"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.impl.developer.tracing
|
||||
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.tracing.LogLevel
|
||||
import io.element.android.libraries.matrix.api.tracing.Target
|
||||
import io.element.android.tests.testutils.waitForPredicate
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class ConfigureTracingPresenterTest {
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val store = InMemoryTracingConfigurationStore()
|
||||
val presenter = ConfigureTracingPresenter(
|
||||
store,
|
||||
TargetLogLevelMapBuilder(store),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.targetsToLogLevel).isNotEmpty()
|
||||
assertThat(initialState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.DEBUG)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - store is taken into account`() = runTest {
|
||||
val store = InMemoryTracingConfigurationStore()
|
||||
store.givenLogLevel(LogLevel.ERROR)
|
||||
val presenter = ConfigureTracingPresenter(
|
||||
store,
|
||||
TargetLogLevelMapBuilder(store),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.targetsToLogLevel).isNotEmpty()
|
||||
assertThat(initialState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - change a value`() = runTest {
|
||||
val store = InMemoryTracingConfigurationStore()
|
||||
val presenter = ConfigureTracingPresenter(
|
||||
store,
|
||||
TargetLogLevelMapBuilder(store),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.DEBUG)
|
||||
initialState.eventSink.invoke(ConfigureTracingEvents.UpdateFilter(Target.MATRIX_SDK_CRYPTO, LogLevel.WARN))
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.WARN)
|
||||
waitForPredicate { store.hasStoreLogLevelBeenCalled }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - reset`() = runTest {
|
||||
val store = InMemoryTracingConfigurationStore()
|
||||
val presenter = ConfigureTracingPresenter(
|
||||
store,
|
||||
TargetLogLevelMapBuilder(store),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.DEBUG)
|
||||
initialState.eventSink.invoke(ConfigureTracingEvents.UpdateFilter(Target.MATRIX_SDK_CRYPTO, LogLevel.WARN))
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.WARN)
|
||||
waitForPredicate { store.hasStoreLogLevelBeenCalled }
|
||||
finalState.eventSink.invoke(ConfigureTracingEvents.ResetFilters)
|
||||
val resetState = awaitItem()
|
||||
assertThat(resetState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.DEBUG)
|
||||
waitForPredicate { store.hasResetBeenCalled }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.impl.developer.tracing
|
||||
|
||||
import io.element.android.libraries.matrix.api.tracing.LogLevel
|
||||
import io.element.android.libraries.matrix.api.tracing.Target
|
||||
|
||||
class InMemoryTracingConfigurationStore : TracingConfigurationStore {
|
||||
var hasResetBeenCalled = false
|
||||
private set
|
||||
var hasStoreLogLevelBeenCalled = false
|
||||
private set
|
||||
private var logLevel: LogLevel? = null
|
||||
|
||||
fun givenLogLevel(logLevel: LogLevel?) {
|
||||
this.logLevel = logLevel
|
||||
}
|
||||
|
||||
override fun getLogLevel(target: Target): LogLevel? {
|
||||
return logLevel
|
||||
}
|
||||
|
||||
override fun storeLogLevel(target: Target, logLevel: LogLevel) {
|
||||
hasStoreLogLevelBeenCalled = true
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
hasResetBeenCalled = true
|
||||
}
|
||||
}
|
||||
@@ -21,22 +21,27 @@ data class TracingFilterConfiguration(
|
||||
) {
|
||||
|
||||
// Order should matters
|
||||
private val targetsToLogLevel: MutableMap<Target, LogLevel> = mutableMapOf(
|
||||
Target.COMMON to LogLevel.Info,
|
||||
Target.HYPER to LogLevel.Warn,
|
||||
Target.MATRIX_SDK_CRYPTO to LogLevel.Debug,
|
||||
Target.MATRIX_SDK_HTTP_CLIENT to LogLevel.Debug,
|
||||
Target.MATRIX_SDK_SLIDING_SYNC to LogLevel.Trace,
|
||||
Target.MATRIX_SDK_BASE_SLIDING_SYNC to LogLevel.Trace,
|
||||
Target.MATRIX_SDK_UI_TIMELINE to LogLevel.Info,
|
||||
private val targetsToLogLevel: Map<Target, LogLevel> = mapOf(
|
||||
Target.COMMON to LogLevel.INFO,
|
||||
Target.HYPER to LogLevel.WARN,
|
||||
Target.MATRIX_SDK_CRYPTO to LogLevel.DEBUG,
|
||||
Target.MATRIX_SDK_HTTP_CLIENT to LogLevel.DEBUG,
|
||||
Target.MATRIX_SDK_SLIDING_SYNC to LogLevel.TRACE,
|
||||
Target.MATRIX_SDK_BASE_SLIDING_SYNC to LogLevel.TRACE,
|
||||
Target.MATRIX_SDK_UI_TIMELINE to LogLevel.INFO,
|
||||
)
|
||||
|
||||
fun getLogLevel(target: Target): LogLevel? {
|
||||
return overrides[target] ?: targetsToLogLevel[target]
|
||||
}
|
||||
|
||||
val filter: String
|
||||
get() {
|
||||
val fullMap = targetsToLogLevel.toMutableMap()
|
||||
overrides.forEach { (target, logLevel) ->
|
||||
targetsToLogLevel[target] = logLevel
|
||||
fullMap[target] = logLevel
|
||||
}
|
||||
return targetsToLogLevel.map {
|
||||
return fullMap.map {
|
||||
if (it.key.filter.isEmpty()) {
|
||||
it.value.filter
|
||||
} else {
|
||||
@@ -59,25 +64,25 @@ enum class Target(open val filter: String) {
|
||||
MATRIX_SDK_UI_TIMELINE("matrix_sdk_ui::timeline"),
|
||||
}
|
||||
|
||||
sealed class LogLevel(val filter: String) {
|
||||
data object Warn : LogLevel("warn")
|
||||
data object Trace : LogLevel("trace")
|
||||
data object Info : LogLevel("info")
|
||||
data object Debug : LogLevel("debug")
|
||||
data object Error : LogLevel("error")
|
||||
enum class LogLevel(open val filter: String) {
|
||||
ERROR("error"),
|
||||
WARN("warn"),
|
||||
INFO("info"),
|
||||
DEBUG("debug"),
|
||||
TRACE("trace"),
|
||||
}
|
||||
|
||||
object TracingFilterConfigurations {
|
||||
val release = TracingFilterConfiguration(
|
||||
overrides = mapOf(
|
||||
Target.COMMON to LogLevel.Info,
|
||||
Target.ELEMENT to LogLevel.Debug
|
||||
Target.COMMON to LogLevel.INFO,
|
||||
Target.ELEMENT to LogLevel.DEBUG
|
||||
),
|
||||
)
|
||||
val debug = TracingFilterConfiguration(
|
||||
overrides = mapOf(
|
||||
Target.COMMON to LogLevel.Info,
|
||||
Target.ELEMENT to LogLevel.Trace
|
||||
Target.COMMON to LogLevel.INFO,
|
||||
Target.ELEMENT to LogLevel.TRACE
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user