Merge pull request #5132 from element-hq/feature/bma/rageshakeConfig

Let enterprise build store the logs in a dedicated subfolder
This commit is contained in:
Benoit Marty
2025-08-07 16:35:00 +02:00
committed by GitHub
13 changed files with 325 additions and 75 deletions

View File

@@ -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<Unit> {
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<Unit> {
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<Class<out Initializer<*>>> = mutableListOf()
}

View File

@@ -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<RootFlowNode.NavTarget>(
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))
}

View File

@@ -42,12 +42,7 @@ class MatrixSessionCache @Inject constructor(
init {
authenticationService.listenToNewMatrixClients { matrixClient ->
val syncOrchestrator = syncOrchestratorFactory.create(matrixClient)
sessionIdsToMatrixSession[matrixClient.sessionId] = InMemoryMatrixSession(
matrixClient = matrixClient,
syncOrchestrator = syncOrchestrator,
)
syncOrchestrator.start()
onNewMatrixClient(matrixClient)
}
}
@@ -105,17 +100,21 @@ class MatrixSessionCache @Inject constructor(
Timber.d("Restore matrix session: $sessionId")
return authenticationService.restoreSession(sessionId)
.onSuccess { matrixClient ->
val syncOrchestrator = syncOrchestratorFactory.create(matrixClient)
sessionIdsToMatrixSession[matrixClient.sessionId] = InMemoryMatrixSession(
matrixClient = matrixClient,
syncOrchestrator = syncOrchestrator,
)
syncOrchestrator.start()
onNewMatrixClient(matrixClient)
}
.onFailure {
Timber.e(it, "Fail to restore session")
}
}
private fun onNewMatrixClient(matrixClient: MatrixClient) {
val syncOrchestrator = syncOrchestratorFactory.create(matrixClient)
sessionIdsToMatrixSession[matrixClient.sessionId] = InMemoryMatrixSession(
matrixClient = matrixClient,
syncOrchestrator = syncOrchestrator,
)
syncOrchestrator.start()
}
}
private data class InMemoryMatrixSession(

View File

@@ -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)
}

View File

@@ -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,
)
}

View File

@@ -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.
*/

View File

@@ -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,24 @@ 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 {
if (buildMeta.isEnterpriseBuild) {
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
// the files in a dedicated subfolders.
setLogDirectorySubfolder(it.userIdServerName())
}
}
}
override suspend fun sendBugReport(
withDevicesLogs: Boolean,
@@ -286,16 +309,44 @@ 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?) {
if (buildMeta.isEnterpriseBuild) {
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()
.filter(predicate)
.forEach { it.safeDelete() }
deleteAllFilesRecursive(baseLogDirectory, predicate)
}
}
private fun deleteAllFilesRecursive(
directory: File,
predicate: (File) -> Boolean,
) {
directory.listFiles()?.forEach { file ->
if (file.isDirectory) {
deleteAllFilesRecursive(file, predicate)
} else {
if (predicate(file)) {
file.safeDelete()
}
}
}
}
@@ -325,11 +376,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) {

View File

@@ -53,6 +53,10 @@ class FakeBugReporter(val mode: Mode = Mode.Success) : BugReporter {
return File("fake")
}
override fun setLogDirectorySubfolder(subfolderName: String?) {
// No op
}
override fun setCurrentTracingLogLevel(logLevel: String) {
// No op
}

View File

@@ -10,16 +10,26 @@ package io.element.android.features.rageshake.impl.reporter
import com.google.common.truth.Truth.assertThat
import io.element.android.appconfig.RageshakeConfig
import io.element.android.features.rageshake.api.reporter.BugReporterListener
import io.element.android.features.rageshake.impl.crash.CrashDataStore
import io.element.android.features.rageshake.impl.crash.FakeCrashDataStore
import io.element.android.features.rageshake.impl.screenshot.FakeScreenshotHolder
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.api.tracing.TracingService
import io.element.android.libraries.matrix.api.tracing.WriteToFilesConfiguration
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
import io.element.android.libraries.matrix.test.FakeSdkMetadata
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
import io.element.android.libraries.matrix.test.tracing.FakeTracingService
import io.element.android.libraries.network.useragent.DefaultUserAgentProvider
import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.libraries.sessionstorage.impl.memory.InMemorySessionStore
import io.element.android.libraries.sessionstorage.test.aSessionData
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@@ -45,7 +55,7 @@ class DefaultBugReporterTest {
.setResponseCode(200)
)
server.start()
val sut = createDefaultBugReporter(server)
val sut = createDefaultBugReporter(server = server)
var onUploadCancelledCalled = false
var onUploadFailedCalled = false
val progressValues = mutableListOf<Int>()
@@ -97,22 +107,14 @@ class DefaultBugReporterTest {
storeData(aSessionData(sessionId = "@foo:example.com", deviceId = "ABCDEFGH"))
}
val buildMeta = aBuildMeta()
val fakeEncryptionService = FakeEncryptionService()
val matrixClient = FakeMatrixClient(encryptionService = fakeEncryptionService)
fakeEncryptionService.givenDeviceKeys("CURVECURVECURVE", "EDKEYEDKEYEDKY")
val sut = DefaultBugReporter(
context = RuntimeEnvironment.getApplication(),
screenshotHolder = FakeScreenshotHolder(),
val sut = createDefaultBugReporter(
server = server,
crashDataStore = FakeCrashDataStore(),
coroutineDispatchers = testCoroutineDispatchers(),
okHttpClient = { OkHttpClient.Builder().build() },
userAgentProvider = DefaultUserAgentProvider(buildMeta, FakeSdkMetadata("123456789")),
sessionStore = mockSessionStore,
buildMeta = buildMeta,
bugReporterUrlProvider = { server.url("/") },
sdkMetadata = FakeSdkMetadata("123456789"),
matrixClientProvider = FakeMatrixClientProvider(getClient = { Result.success(matrixClient) })
)
@@ -166,22 +168,13 @@ class DefaultBugReporterTest {
storeData(aSessionData("@foo:example.com", "ABCDEFGH"))
}
val buildMeta = aBuildMeta()
val fakeEncryptionService = FakeEncryptionService()
val matrixClient = FakeMatrixClient(encryptionService = fakeEncryptionService)
fakeEncryptionService.givenDeviceKeys(null, null)
val sut = DefaultBugReporter(
context = RuntimeEnvironment.getApplication(),
screenshotHolder = FakeScreenshotHolder(),
crashDataStore = FakeCrashDataStore(),
coroutineDispatchers = testCoroutineDispatchers(),
okHttpClient = { OkHttpClient.Builder().build() },
userAgentProvider = DefaultUserAgentProvider(buildMeta, FakeSdkMetadata("123456789")),
val sut = createDefaultBugReporter(
server = server,
sessionStore = mockSessionStore,
buildMeta = buildMeta,
bugReporterUrlProvider = { server.url("/") },
sdkMetadata = FakeSdkMetadata("123456789"),
matrixClientProvider = FakeMatrixClientProvider(getClient = { Result.success(matrixClient) })
)
@@ -209,21 +202,13 @@ class DefaultBugReporterTest {
)
server.start()
val buildMeta = aBuildMeta()
val fakeEncryptionService = FakeEncryptionService()
fakeEncryptionService.givenDeviceKeys(null, null)
val sut = DefaultBugReporter(
context = RuntimeEnvironment.getApplication(),
screenshotHolder = FakeScreenshotHolder(),
val sut = createDefaultBugReporter(
server = server,
crashDataStore = FakeCrashDataStore("I did crash", true),
coroutineDispatchers = testCoroutineDispatchers(),
okHttpClient = { OkHttpClient.Builder().build() },
userAgentProvider = DefaultUserAgentProvider(buildMeta, FakeSdkMetadata("123456789")),
sessionStore = InMemorySessionStore(),
buildMeta = buildMeta,
bugReporterUrlProvider = { server.url("/") },
sdkMetadata = FakeSdkMetadata("123456789"),
matrixClientProvider = FakeMatrixClientProvider(getClient = { Result.failure(Exception("Mock no client")) })
)
@@ -276,7 +261,7 @@ class DefaultBugReporterTest {
.setBody("""{"error": "An error body"}""")
)
server.start()
val sut = createDefaultBugReporter(server)
val sut = createDefaultBugReporter(server = server)
var onUploadCancelledCalled = false
var onUploadFailedCalled = false
var onUploadFailedReason: String? = null
@@ -318,22 +303,172 @@ class DefaultBugReporterTest {
assertThat(onUploadSucceedCalled).isFalse()
}
@Test
fun `the log directory is initialized using the last session store data`() = runTest {
val sut = createDefaultBugReporter(
buildMeta = aBuildMeta(isEnterpriseBuild = true),
sessionStore = InMemorySessionStore().apply {
storeData(aSessionData(sessionId = "@alice:domain.com"))
}
)
assertThat(sut.logDirectory().absolutePath).endsWith("/cache/logs/domain.com")
}
@Test
fun `foss build - the log directory is initialized to the root log directory`() = runTest {
val sut = createDefaultBugReporter(
sessionStore = InMemorySessionStore().apply {
storeData(aSessionData(sessionId = "@alice:domain.com"))
}
)
assertThat(sut.logDirectory().absolutePath).endsWith("/cache/logs")
}
@Test
fun `when the log directory is updated, the tracing service is invoked`() = runTest {
var param: WriteToFilesConfiguration? = null
val updateWriteToFilesConfigurationResult = lambdaRecorder<WriteToFilesConfiguration, Unit> {
param = it
}
val sut = createDefaultBugReporter(
buildMeta = aBuildMeta(isEnterpriseBuild = true),
tracingService = FakeTracingService(
updateWriteToFilesConfigurationResult = updateWriteToFilesConfigurationResult,
),
)
sut.setLogDirectorySubfolder("my.sub.folder")
updateWriteToFilesConfigurationResult.assertions().isCalledOnce()
assertThat(param).isNotNull()
assertThat(param).isInstanceOf(WriteToFilesConfiguration.Enabled::class.java)
assertThat((param as WriteToFilesConfiguration.Enabled).directory).endsWith("/cache/logs/my.sub.folder")
assertThat((param as WriteToFilesConfiguration.Enabled).filenamePrefix).isEqualTo("logs")
assertThat((param as WriteToFilesConfiguration.Enabled).numberOfFiles).isEqualTo(168)
assertThat((param as WriteToFilesConfiguration.Enabled).filenameSuffix).isEqualTo("log")
}
@Test
fun `foss build - when the log directory is updated, the tracing service is not invoked`() = runTest {
val updateWriteToFilesConfigurationResult = lambdaRecorder<WriteToFilesConfiguration, Unit> {}
val sut = createDefaultBugReporter(
tracingService = FakeTracingService(
updateWriteToFilesConfigurationResult = updateWriteToFilesConfigurationResult,
)
)
sut.setLogDirectorySubfolder("my.sub.folder")
updateWriteToFilesConfigurationResult.assertions().isNeverCalled()
}
@Test
fun `when the log directory is reset, the tracing service is invoked`() = runTest {
var param: WriteToFilesConfiguration? = null
val updateWriteToFilesConfigurationResult = lambdaRecorder<WriteToFilesConfiguration, Unit> {
param = it
}
val sut = createDefaultBugReporter(
buildMeta = aBuildMeta(isEnterpriseBuild = true),
tracingService = FakeTracingService(
updateWriteToFilesConfigurationResult = updateWriteToFilesConfigurationResult,
),
)
sut.setLogDirectorySubfolder(null)
updateWriteToFilesConfigurationResult.assertions().isCalledOnce()
assertThat(param).isNotNull()
assertThat(param).isInstanceOf(WriteToFilesConfiguration.Enabled::class.java)
assertThat((param as WriteToFilesConfiguration.Enabled).directory).endsWith("/cache/logs")
assertThat((param as WriteToFilesConfiguration.Enabled).filenamePrefix).isEqualTo("logs")
assertThat((param as WriteToFilesConfiguration.Enabled).numberOfFiles).isEqualTo(168)
assertThat((param as WriteToFilesConfiguration.Enabled).filenameSuffix).isEqualTo("log")
}
@Test
fun `foss build - when the log directory is reset, the tracing service is not invoked`() = runTest {
val updateWriteToFilesConfigurationResult = lambdaRecorder<WriteToFilesConfiguration, Unit> {}
val sut = createDefaultBugReporter(
tracingService = FakeTracingService(
updateWriteToFilesConfigurationResult = updateWriteToFilesConfigurationResult,
)
)
sut.setLogDirectorySubfolder(null)
updateWriteToFilesConfigurationResult.assertions().isNeverCalled()
}
@Test
fun `when a new MatrixClient is created the logs folder is updated`() = runTest {
var param: WriteToFilesConfiguration? = null
val updateWriteToFilesConfigurationResult = lambdaRecorder<WriteToFilesConfiguration, Unit> {
param = it
}
val matrixAuthenticationService = FakeMatrixAuthenticationService().apply {
givenMatrixClient(
FakeMatrixClient(
userIdServerNameLambda = { "domain.foo.org" },
)
)
}
val sut = createDefaultBugReporter(
buildMeta = aBuildMeta(isEnterpriseBuild = true),
matrixAuthenticationService = matrixAuthenticationService,
tracingService = FakeTracingService(
updateWriteToFilesConfigurationResult = updateWriteToFilesConfigurationResult,
)
)
assertThat(sut.logDirectory().absolutePath).endsWith("/cache/logs")
matrixAuthenticationService.login("alice", "password")
assertThat(sut.logDirectory().absolutePath).endsWith("/cache/logs/domain.foo.org")
updateWriteToFilesConfigurationResult.assertions().isCalledOnce()
assertThat(param).isNotNull()
assertThat(param).isInstanceOf(WriteToFilesConfiguration.Enabled::class.java)
assertThat((param as WriteToFilesConfiguration.Enabled).directory).endsWith("/cache/logs/domain.foo.org")
assertThat((param as WriteToFilesConfiguration.Enabled).filenamePrefix).isEqualTo("logs")
assertThat((param as WriteToFilesConfiguration.Enabled).numberOfFiles).isEqualTo(168)
assertThat((param as WriteToFilesConfiguration.Enabled).filenameSuffix).isEqualTo("log")
}
@Test
fun `foss build - when a new MatrixClient is created the logs folder is not updated`() = runTest {
val updateWriteToFilesConfigurationResult = lambdaRecorder<WriteToFilesConfiguration, Unit> {}
val matrixAuthenticationService = FakeMatrixAuthenticationService().apply {
givenMatrixClient(
FakeMatrixClient(
userIdServerNameLambda = { "domain.foo.org" },
)
)
}
val sut = createDefaultBugReporter(
matrixAuthenticationService = matrixAuthenticationService,
tracingService = FakeTracingService(
updateWriteToFilesConfigurationResult = updateWriteToFilesConfigurationResult,
)
)
assertThat(sut.logDirectory().absolutePath).endsWith("/cache/logs")
matrixAuthenticationService.login("alice", "password")
assertThat(sut.logDirectory().absolutePath).endsWith("/cache/logs")
updateWriteToFilesConfigurationResult.assertions().isNeverCalled()
}
private fun TestScope.createDefaultBugReporter(
server: MockWebServer
buildMeta: BuildMeta = aBuildMeta(),
sessionStore: SessionStore = InMemorySessionStore(),
matrixClientProvider: MatrixClientProvider = FakeMatrixClientProvider(),
crashDataStore: CrashDataStore = FakeCrashDataStore(),
server: MockWebServer = MockWebServer(),
tracingService: TracingService = FakeTracingService(),
matrixAuthenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
): DefaultBugReporter {
val buildMeta = aBuildMeta()
return DefaultBugReporter(
context = RuntimeEnvironment.getApplication(),
screenshotHolder = FakeScreenshotHolder(),
crashDataStore = FakeCrashDataStore(),
crashDataStore = crashDataStore,
coroutineDispatchers = testCoroutineDispatchers(),
okHttpClient = { OkHttpClient.Builder().build() },
userAgentProvider = DefaultUserAgentProvider(buildMeta, FakeSdkMetadata("123456789")),
sessionStore = InMemorySessionStore(),
sessionStore = sessionStore,
buildMeta = buildMeta,
bugReporterUrlProvider = { server.url("/") },
sdkMetadata = FakeSdkMetadata("123456789"),
matrixClientProvider = FakeMatrixClientProvider()
matrixClientProvider = matrixClientProvider,
tracingService = tracingService,
matrixAuthenticationService = matrixAuthenticationService,
)
}

View File

@@ -11,4 +11,6 @@ import timber.log.Timber
interface TracingService {
fun createTimberTree(target: String): Timber.Tree
fun updateWriteToFilesConfiguration(config: WriteToFilesConfiguration)
}

View File

@@ -71,9 +71,9 @@ class RustMatrixAuthenticationService @Inject constructor(
private var currentClient: Client? = null
private var currentHomeserver = MutableStateFlow<MatrixHomeServerDetails?>(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

View File

@@ -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 {

View File

@@ -0,0 +1,26 @@
/*
* 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.test.tracing
import io.element.android.libraries.matrix.api.tracing.TracingService
import io.element.android.libraries.matrix.api.tracing.WriteToFilesConfiguration
import io.element.android.tests.testutils.lambda.lambdaError
import timber.log.Timber
class FakeTracingService(
private val createTimberTreeResult: (String) -> Timber.Tree = { lambdaError() },
private val updateWriteToFilesConfigurationResult: (WriteToFilesConfiguration) -> Unit = { lambdaError() }
) : TracingService {
override fun createTimberTree(target: String): Timber.Tree {
return createTimberTreeResult(target)
}
override fun updateWriteToFilesConfiguration(config: WriteToFilesConfiguration) {
updateWriteToFilesConfigurationResult(config)
}
}