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 6dd0d69dc0..40016922ae 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 @@ -10,11 +10,10 @@ package io.element.android.x.initializer import android.content.Context import android.system.Os import androidx.startup.Initializer -import io.element.android.features.rageshake.api.reporter.BugReporter +import io.element.android.features.rageshake.api.logs.createWriteToFilesConfiguration import io.element.android.libraries.architecture.bindings import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.tracing.TracingConfiguration -import io.element.android.libraries.matrix.api.tracing.WriteToFilesConfiguration import io.element.android.x.di.AppBindings import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking @@ -34,7 +33,7 @@ class PlatformInitializer : Initializer { val logLevel = runBlocking { preferencesStore.getTracingLogLevelFlow().first() } val tracingConfiguration = TracingConfiguration( writesToLogcat = runBlocking { featureFlagService.isFeatureEnabled(FeatureFlags.PrintLogsToLogcat) }, - writesToFilesConfiguration = defaultWriteToDiskConfiguration(bugReporter), + writesToFilesConfiguration = bugReporter.createWriteToFilesConfiguration(), logLevel = logLevel, extraTargets = listOf(ELEMENT_X_TARGET), traceLogPacks = runBlocking { preferencesStore.getTracingLogPacksFlow().first() }, @@ -45,14 +44,5 @@ class PlatformInitializer : Initializer { Os.setenv("RUST_BACKTRACE", "1", true) } - private fun defaultWriteToDiskConfiguration(bugReporter: BugReporter): WriteToFilesConfiguration.Enabled { - return WriteToFilesConfiguration.Enabled( - directory = bugReporter.logDirectory().absolutePath, - filenamePrefix = "logs", - // Keep a maximum of 1 week of log files. - numberOfFiles = 7 * 24, - ) - } - override fun dependencies(): List>> = mutableListOf() } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index ae9f935061..db512ff2ea 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -36,6 +36,7 @@ import io.element.android.appnav.root.RootView import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.login.api.LoginParams import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint +import io.element.android.features.rageshake.api.reporter.BugReporter import io.element.android.features.signedout.api.SignedOutEntryPoint import io.element.android.features.viewfolder.api.ViewFolderEntryPoint import io.element.android.libraries.architecture.BackstackView @@ -73,6 +74,7 @@ class RootFlowNode @AssistedInject constructor( private val signedOutEntryPoint: SignedOutEntryPoint, private val intentResolver: IntentResolver, private val oidcActionFlow: OidcActionFlow, + private val bugReporter: BugReporter, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.SplashScreen, @@ -123,6 +125,7 @@ class RootFlowNode @AssistedInject constructor( private fun switchToNotLoggedInFlow(params: LoginParams?) { matrixSessionCache.removeAll() + bugReporter.setLogDirectorySubfolder(null) backstack.safeRoot(NavTarget.NotLoggedInFlow(params)) } diff --git a/features/rageshake/api/build.gradle.kts b/features/rageshake/api/build.gradle.kts index f47b748c3e..7fe1620613 100644 --- a/features/rageshake/api/build.gradle.kts +++ b/features/rageshake/api/build.gradle.kts @@ -16,5 +16,6 @@ dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.designsystem) implementation(projects.libraries.androidutils) + implementation(projects.libraries.matrix.api) implementation(projects.libraries.uiStrings) } diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/logs/WriteToFilesConfigurationFactory.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/logs/WriteToFilesConfigurationFactory.kt new file mode 100644 index 0000000000..52298b5414 --- /dev/null +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/logs/WriteToFilesConfigurationFactory.kt @@ -0,0 +1,20 @@ +/* + * 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.features.rageshake.api.logs + +import io.element.android.features.rageshake.api.reporter.BugReporter +import io.element.android.libraries.matrix.api.tracing.WriteToFilesConfiguration + +fun BugReporter.createWriteToFilesConfiguration(): WriteToFilesConfiguration { + return WriteToFilesConfiguration.Enabled( + directory = logDirectory().absolutePath, + filenamePrefix = "logs", + // Keep a maximum of 1 week of log files. + numberOfFiles = 7 * 24, + ) +} diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporter.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporter.kt index 1cc3cc8908..bb625370cf 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporter.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporter.kt @@ -34,6 +34,14 @@ interface BugReporter { */ fun logDirectory(): File + /** + * Set the subfolder name for the log directory. + * This will create a subfolder in the log directory with the given name. + * It will also configure the Rust SDK to use this subfolder for its logs. + * If the name is null, the log files will be stored in the base folder for the logs. + */ + fun setLogDirectorySubfolder(subfolderName: String?) + /** * Set the current tracing log level. */ diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt index b9ed1c7778..5f3195c32f 100755 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt @@ -13,6 +13,7 @@ import androidx.core.net.toFile import androidx.core.net.toUri import com.squareup.anvil.annotations.ContributesBinding import io.element.android.appconfig.RageshakeConfig +import io.element.android.features.rageshake.api.logs.createWriteToFilesConfiguration import io.element.android.features.rageshake.api.reporter.BugReporter import io.element.android.features.rageshake.api.reporter.BugReporterListener import io.element.android.features.rageshake.impl.crash.CrashDataStore @@ -28,11 +29,14 @@ import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.SingleIn import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.matrix.api.SdkMetadata +import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.tracing.TracingService import io.element.android.libraries.network.useragent.UserAgentProvider import io.element.android.libraries.sessionstorage.api.SessionStore import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.OkHttpClient @@ -71,6 +75,8 @@ class DefaultBugReporter @Inject constructor( private val bugReporterUrlProvider: BugReporterUrlProvider, private val sdkMetadata: SdkMetadata, private val matrixClientProvider: MatrixClientProvider, + private val tracingService: TracingService, + matrixAuthenticationService: MatrixAuthenticationService, ) : BugReporter { companion object { // filenames @@ -81,7 +87,21 @@ class DefaultBugReporter @Inject constructor( private val logcatCommandDebug = arrayOf("logcat", "-d", "-v", "threadtime", "*:*") private var currentTracingLogLevel: String? = null - private val logCatErrFile = File(logDirectory().absolutePath, LOG_CAT_FILENAME) + private val logCatErrFile: File + get() = File(logDirectory(), LOG_CAT_FILENAME) + private val baseLogDirectory = File(context.cacheDir, LOG_DIRECTORY_NAME) + private var currentLogDirectory: File = baseLogDirectory + + init { + val logSubfolder = runBlocking { + sessionStore.getLatestSession() + }?.userId?.substringAfter(":") + setCurrentLogDirectory(logSubfolder) + matrixAuthenticationService.listenToNewMatrixClients { + // When a new Matrix client is created, we update the tracing configuration to write to files + setLogDirectorySubfolder(it.userIdServerName()) + } + } override suspend fun sendBugReport( withDevicesLogs: Boolean, @@ -286,11 +306,24 @@ class DefaultBugReporter @Inject constructor( } override fun logDirectory(): File { - return File(context.cacheDir, LOG_DIRECTORY_NAME).apply { + return currentLogDirectory.apply { mkdirs() } } + override fun setLogDirectorySubfolder(subfolderName: String?) { + setCurrentLogDirectory(subfolderName) + tracingService.updateWriteToFilesConfiguration(createWriteToFilesConfiguration()) + } + + private fun setCurrentLogDirectory(subfolderName: String?) { + currentLogDirectory = if (subfolderName == null) { + baseLogDirectory + } else { + File(baseLogDirectory, subfolderName) + } + } + suspend fun deleteAllFiles(predicate: (File) -> Boolean) { withContext(coroutineDispatchers.io) { getLogFiles() @@ -325,11 +358,12 @@ class DefaultBugReporter @Inject constructor( * @return the file if the operation succeeds */ override fun saveLogCat() { - if (logCatErrFile.exists()) { - logCatErrFile.safeDelete() + val file = logCatErrFile + if (file.exists()) { + file.safeDelete() } try { - logCatErrFile.writer().use { + file.writer().use { getLogCatError(it) } } catch (error: OutOfMemoryError) { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingService.kt index 5cef6cde02..8c183c80b4 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingService.kt @@ -11,4 +11,6 @@ import timber.log.Timber interface TracingService { fun createTimberTree(target: String): Timber.Tree + + fun updateWriteToFilesConfiguration(config: WriteToFilesConfiguration) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index a1ffbc5da9..1a044a9f4a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -71,9 +71,9 @@ class RustMatrixAuthenticationService @Inject constructor( private var currentClient: Client? = null private var currentHomeserver = MutableStateFlow(null) - private var newMatrixClientObserver: ((MatrixClient) -> Unit)? = null + private val newMatrixClientObservers = mutableListOf<(MatrixClient) -> Unit>() override fun listenToNewMatrixClients(lambda: (MatrixClient) -> Unit) { - newMatrixClientObserver = lambda + newMatrixClientObservers.add(lambda) } private fun rotateSessionPath(): SessionPaths { @@ -155,7 +155,8 @@ class RustMatrixAuthenticationService @Inject constructor( passphrase = pendingPassphrase, sessionPaths = currentSessionPaths, ) - newMatrixClientObserver?.invoke(rustMatrixClientFactory.create(client)) + val matrixClient = rustMatrixClientFactory.create(client) + newMatrixClientObservers.forEach { it.invoke(matrixClient) } sessionStore.storeData(sessionData) // Clean up the strong reference held here since it's no longer necessary @@ -246,7 +247,8 @@ class RustMatrixAuthenticationService @Inject constructor( pendingOAuthAuthorizationData?.close() pendingOAuthAuthorizationData = null - newMatrixClientObserver?.invoke(rustMatrixClientFactory.create(client)) + val matrixClient = rustMatrixClientFactory.create(client) + newMatrixClientObservers.forEach { it.invoke(matrixClient) } sessionStore.storeData(sessionData) // Clean up the strong reference held here since it's no longer necessary @@ -290,7 +292,8 @@ class RustMatrixAuthenticationService @Inject constructor( passphrase = pendingPassphrase, sessionPaths = emptySessionPaths, ) - newMatrixClientObserver?.invoke(rustMatrixClientFactory.create(client)) + val matrixClient = rustMatrixClientFactory.create(client) + newMatrixClientObservers.forEach { it.invoke(matrixClient) } sessionStore.storeData(sessionData) // Clean up the strong reference held here since it's no longer necessary 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 912561d843..728bf136b8 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 @@ -15,6 +15,7 @@ import io.element.android.libraries.matrix.api.tracing.TracingConfiguration import io.element.android.libraries.matrix.api.tracing.TracingService import io.element.android.libraries.matrix.api.tracing.WriteToFilesConfiguration import org.matrix.rustcomponents.sdk.TracingFileConfiguration +import org.matrix.rustcomponents.sdk.reloadTracingFileWriter import timber.log.Timber import javax.inject.Inject @@ -23,6 +24,12 @@ class RustTracingService @Inject constructor(private val buildMeta: BuildMeta) : override fun createTimberTree(target: String): Timber.Tree { return RustTracingTree(target = target, retrieveFromStackTrace = buildMeta.isDebuggable) } + + override fun updateWriteToFilesConfiguration(config: WriteToFilesConfiguration) { + config.toTracingFileConfiguration()?.let { + reloadTracingFileWriter(it) + } + } } private fun LogLevel.toRustLogLevel(): org.matrix.rustcomponents.sdk.LogLevel {