diff --git a/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt b/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt index ec3259fb7c..da8592771c 100644 --- a/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt +++ b/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt @@ -24,8 +24,7 @@ import io.element.android.x.di.DaggerAppComponent import io.element.android.x.info.logApplicationInfo import io.element.android.x.initializer.CrashInitializer import io.element.android.x.initializer.EmojiInitializer -import io.element.android.x.initializer.MatrixInitializer -import io.element.android.x.initializer.TimberInitializer +import io.element.android.x.initializer.TracingInitializer class ElementXApplication : Application(), DaggerComponentOwner { @@ -39,8 +38,7 @@ class ElementXApplication : Application(), DaggerComponentOwner { appComponent = DaggerAppComponent.factory().create(applicationContext) AppInitializer.getInstance(this).apply { initializeComponent(CrashInitializer::class.java) - initializeComponent(TimberInitializer::class.java) - initializeComponent(MatrixInitializer::class.java) + initializeComponent(TracingInitializer::class.java) initializeComponent(EmojiInitializer::class.java) } logApplicationInfo() diff --git a/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt b/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt index 4d75d8601e..5fb3523d6e 100644 --- a/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt +++ b/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt @@ -17,11 +17,15 @@ package io.element.android.x.di import com.squareup.anvil.annotations.ContributesTo +import io.element.android.features.rageshake.api.reporter.BugReporter import io.element.android.libraries.designsystem.utils.SnackbarDispatcher import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.tracing.TracingService @ContributesTo(AppScope::class) interface AppBindings { fun mainDaggerComponentOwner(): MainDaggerComponentsOwner fun snackbarDispatcher(): SnackbarDispatcher + fun tracingService(): TracingService + fun bugReporter(): BugReporter } diff --git a/app/src/main/kotlin/io/element/android/x/initializer/MatrixInitializer.kt b/app/src/main/kotlin/io/element/android/x/initializer/MatrixInitializer.kt deleted file mode 100644 index 5eebc88756..0000000000 --- a/app/src/main/kotlin/io/element/android/x/initializer/MatrixInitializer.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2022 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.x.initializer - -import android.content.Context -import androidx.startup.Initializer -import io.element.android.libraries.matrix.impl.tracing.setupTracing -import io.element.android.libraries.matrix.api.tracing.TracingConfigurations -import io.element.android.x.BuildConfig - -class MatrixInitializer : Initializer { - - override fun create(context: Context) { - if (BuildConfig.DEBUG) { - setupTracing(TracingConfigurations.debug) - } else { - setupTracing(TracingConfigurations.release) - } - } - - override fun dependencies(): List>> = listOf(TimberInitializer::class.java) -} diff --git a/app/src/main/kotlin/io/element/android/x/initializer/TracingInitializer.kt b/app/src/main/kotlin/io/element/android/x/initializer/TracingInitializer.kt new file mode 100644 index 0000000000..068d439994 --- /dev/null +++ b/app/src/main/kotlin/io/element/android/x/initializer/TracingInitializer.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022 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.x.initializer + +import android.content.Context +import androidx.startup.Initializer +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 +import io.element.android.libraries.matrix.api.tracing.WriteToFilesConfiguration +import io.element.android.x.BuildConfig +import io.element.android.x.di.AppBindings +import timber.log.Timber + +class TracingInitializer : Initializer { + + override fun create(context: Context) { + val appBindings = context.bindings() + val tracingService = appBindings.tracingService() + val bugReporter = appBindings.bugReporter() + Timber.plant(tracingService.createTimberTree()) + val tracingConfiguration = if (BuildConfig.DEBUG) { + TracingConfiguration( + filterConfiguration = TracingFilterConfigurations.debug, + writesToLogcat = true, + writesToFilesConfiguration = WriteToFilesConfiguration.Disabled + ) + } else { + TracingConfiguration( + filterConfiguration = TracingFilterConfigurations.release, + writesToLogcat = false, + writesToFilesConfiguration = WriteToFilesConfiguration.Enabled( + directory = bugReporter.logDirectory().absolutePath, + filenamePrefix = "logs" + ) + ) + } + bugReporter.cleanLogDirectoryIfNeeded() + tracingService.setupTracing(tracingConfiguration) + } + + override fun dependencies(): List>> = mutableListOf() +} 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 0af13dcdda..99849ef1d4 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 @@ -16,6 +16,8 @@ package io.element.android.features.rageshake.api.reporter +import java.io.File + interface BugReporter { /** * Send a bug report. @@ -43,4 +45,14 @@ interface BugReporter { customFields: Map? = null, listener: BugReporterListener? ) + + /** + * Clean the log files if needed to avoid wasting disk space. + */ + fun cleanLogDirectoryIfNeeded() + + /** + * Provide the log directory. + */ + fun logDirectory(): File } diff --git a/features/rageshake/impl/build.gradle.kts b/features/rageshake/impl/build.gradle.kts index 3283e3f37a..464d521689 100644 --- a/features/rageshake/impl/build.gradle.kts +++ b/features/rageshake/impl/build.gradle.kts @@ -32,6 +32,7 @@ anvil { dependencies { implementation(projects.anvilannotations) anvil(projects.anvilcodegen) + implementation(projects.services.toolbox.api) implementation(projects.libraries.androidutils) implementation(projects.libraries.core) implementation(projects.libraries.network) 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 65dc48aca8..a8491b5a74 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 @@ -18,6 +18,7 @@ package io.element.android.features.rageshake.impl.reporter import android.content.Context import android.os.Build +import android.text.format.DateUtils.DAY_IN_MILLIS import androidx.core.net.toFile import androidx.core.net.toUri import com.squareup.anvil.annotations.ContributesBinding @@ -27,10 +28,10 @@ import io.element.android.features.rageshake.api.reporter.BugReporterListener import io.element.android.features.rageshake.api.reporter.ReportType import io.element.android.features.rageshake.api.screenshot.ScreenshotHolder import io.element.android.features.rageshake.impl.R -import io.element.android.features.rageshake.impl.logs.VectorFileLogger import io.element.android.libraries.androidutils.file.compressFile import io.element.android.libraries.androidutils.file.safeDelete import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.extensions.toOnOff import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.mimetype.MimeTypes @@ -38,7 +39,10 @@ import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.network.useragent.UserAgentProvider import io.element.android.libraries.sessionstorage.api.SessionStore +import io.element.android.services.toolbox.api.systemclock.SystemClock +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.Call import okhttp3.MediaType.Companion.toMediaTypeOrNull @@ -65,6 +69,8 @@ class DefaultBugReporter @Inject constructor( @ApplicationContext private val context: Context, private val screenshotHolder: ScreenshotHolder, private val crashDataStore: CrashDataStore, + private val coroutineScope: CoroutineScope, + private val systemClock: SystemClock, private val coroutineDispatchers: CoroutineDispatchers, private val okHttpClient: Provider, private val userAgentProvider: UserAgentProvider, @@ -87,6 +93,7 @@ class DefaultBugReporter @Inject constructor( // filenames private const val LOG_CAT_ERROR_FILENAME = "logcatError.log" private const val LOG_CAT_FILENAME = "logcat.log" + private const val LOG_DIRECTORY_NAME = "logs" // private const val KEY_REQUESTS_FILENAME = "keyRequests.log" private const val BUFFER_SIZE = 1024 * 1024 * 50 @@ -158,9 +165,8 @@ class DefaultBugReporter @Inject constructor( val gzippedFiles = ArrayList() - val vectorFileLogger = VectorFileLogger.getFromTimber() - if (withDevicesLogs && vectorFileLogger != null) { - val files = vectorFileLogger.getLogFiles() + if (withDevicesLogs) { + val files = getLogFiles() files.mapNotNullTo(gzippedFiles) { f -> if (!mIsCancelled) { compressFile(f) @@ -168,6 +174,7 @@ class DefaultBugReporter @Inject constructor( null } } + files.deleteAllExceptMostRecent() } if (!mIsCancelled && (withCrashLogs || withDevicesLogs)) { @@ -458,6 +465,54 @@ class DefaultBugReporter @Inject constructor( ) } + override fun logDirectory(): File { + return File(context.cacheDir, LOG_DIRECTORY_NAME) + } + + override fun cleanLogDirectoryIfNeeded() { + coroutineScope.launch(coroutineDispatchers.io) { + // delete the log files older than 1 day, except the most recent one + deleteOldLogFiles(systemClock.epochMillis() - DAY_IN_MILLIS) + } + } + + /** + * @return the files on the log directory. + */ + private fun getLogFiles(): List { + return tryOrNull( + onError = { Timber.e(it, "## getLogFiles() failed") } + ) { + val logDirectory = logDirectory() + logDirectory.listFiles()?.toList() + }.orEmpty() + } + + /** + * Delete the log files older than the given time except the most recent one. + * @param time the time in ms + */ + private fun deleteOldLogFiles(time: Long) { + val logFiles = getLogFiles() + val oldLogFiles = logFiles.filter { it.lastModified() < time } + oldLogFiles.deleteAllExceptMostRecent() + } + + /** + * Delete all the log files except the most recent one. + * + */ + private fun List.deleteAllExceptMostRecent() { + if (size > 1) { + val mostRecentFile = maxByOrNull { it.lastModified() } + forEach { file -> + if (file != mostRecentFile) { + file.safeDelete() + } + } + } + } + // ============================================================================================================== // Logcat management // ============================================================================================================== @@ -485,6 +540,10 @@ class DefaultBugReporter @Inject constructor( Timber.e(error, "## saveLogCat() : fail to write logcat OOM") } catch (e: Exception) { Timber.e(e, "## saveLogCat() : fail to write logcat") + } finally { + if (logCatErrFile.exists()) { + logCatErrFile.safeDelete() + } } return null diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/FakeBugReporter.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/FakeBugReporter.kt index ac8940a1ac..82edaf563d 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/FakeBugReporter.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/FakeBugReporter.kt @@ -21,6 +21,7 @@ import io.element.android.features.rageshake.api.reporter.BugReporterListener import io.element.android.features.rageshake.api.reporter.ReportType import io.element.android.libraries.matrix.test.A_FAILURE_REASON import kotlinx.coroutines.delay +import java.io.File class FakeBugReporter(val mode: FakeBugReporterMode = FakeBugReporterMode.Success) : BugReporter { override suspend fun sendBugReport( @@ -55,6 +56,14 @@ class FakeBugReporter(val mode: FakeBugReporterMode = FakeBugReporterMode.Succes delay(100) listener?.onUploadSucceed(null) } + + override fun cleanLogDirectoryIfNeeded() { + // No op + } + + override fun logDirectory(): File { + return File("fake") + } } enum class FakeBugReporterMode { 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 8bcd602b9f..6381cc7ed8 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * 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. @@ -17,64 +17,7 @@ package io.element.android.libraries.matrix.api.tracing data class TracingConfiguration( - val overrides: Map = emptyMap() -) { - - // Order should matters - private val targets: MutableMap = mutableMapOf( - Target.Common to LogLevel.Warn, - Target.Hyper to LogLevel.Warn, - Target.Sled to LogLevel.Warn, - Target.MatrixSdk.Root to LogLevel.Warn, - Target.MatrixSdk.Sled to LogLevel.Warn, - Target.MatrixSdk.Crypto to LogLevel.Debug, - Target.MatrixSdk.HttpClient to LogLevel.Debug, - Target.MatrixSdk.SlidingSync to LogLevel.Trace, - Target.MatrixSdk.BaseSlidingSync to LogLevel.Trace, - ) - - val filter: String - get() { - overrides.forEach { (target, logLevel) -> - targets[target] = logLevel - } - return targets.map { - if (it.key.filter.isEmpty()) { - it.value.filter - } else { - "${it.key.filter}=${it.value.filter}" - } - }.joinToString(separator = ",") - } -} - -sealed class Target(open val filter: String) { - object Common : Target("") - object Hyper : Target("hyper") - object Sled : Target("sled") - sealed class MatrixSdk(override val filter: String) : Target(filter) { - object Root : MatrixSdk("matrix_sdk") - object Sled : MatrixSdk("matrix_sdk_sled") - object Crypto: MatrixSdk("matrix_sdk_crypto") - object FFI : MatrixSdk("matrix_sdk_ffi") - object HttpClient : MatrixSdk("matrix_sdk::http_client") - object UniffiAPI : MatrixSdk("matrix_sdk_ffi::uniffi_api") - object SlidingSync : MatrixSdk("matrix_sdk::sliding_sync") - object BaseSlidingSync : MatrixSdk("matrix_sdk_base::sliding_sync") - } -} - -sealed class LogLevel(val filter: String) { - object Warn : LogLevel("warn") - object Trace : LogLevel("trace") - object Info : LogLevel("info") - object Debug : LogLevel("debug") - object Error : LogLevel("error") -} - -object TracingConfigurations { - val release = TracingConfiguration(overrides = mapOf(Target.Common to LogLevel.Info)) - val debug = TracingConfiguration(overrides = mapOf(Target.Common to LogLevel.Info)) - - fun custom(overrides: Map) = TracingConfiguration(overrides) -} + val filterConfiguration: TracingFilterConfiguration, + val writesToLogcat: Boolean, + val writesToFilesConfiguration: WriteToFilesConfiguration, +) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingFilterConfiguration.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingFilterConfiguration.kt new file mode 100644 index 0000000000..21c6954c2a --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingFilterConfiguration.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2022 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.libraries.matrix.api.tracing + +data class TracingFilterConfiguration( + val overrides: Map = emptyMap(), +) { + + // Order should matters + private val targetsToLogLevel: MutableMap = 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, + ) + + val filter: String + get() { + overrides.forEach { (target, logLevel) -> + targetsToLogLevel[target] = logLevel + } + return targetsToLogLevel.map { + if (it.key.filter.isEmpty()) { + it.value.filter + } else { + "${it.key.filter}=${it.value.filter}" + } + }.joinToString(separator = ",") + } +} + +enum class Target(open val filter: String) { + COMMON(""), + ELEMENT("elementx"), + HYPER("hyper"), + MATRIX_SDK_FFI("matrix_sdk_ffi"), + MATRIX_SDK_UNIFFI_API("matrix_sdk_ffi::uniffi_api"), + MATRIX_SDK_CRYPTO("matrix_sdk_crypto"), + MATRIX_SDK_HTTP_CLIENT("matrix_sdk::http_client"), + MATRIX_SDK_SLIDING_SYNC("matrix_sdk::sliding_sync"), + MATRIX_SDK_BASE_SLIDING_SYNC("matrix_sdk_base::sliding_sync"), + MATRIX_SDK_UI_TIMELINE("matrix_sdk_ui::timeline"), +} + +sealed class LogLevel(val filter: String) { + object Warn : LogLevel("warn") + object Trace : LogLevel("trace") + object Info : LogLevel("info") + object Debug : LogLevel("debug") + object Error : LogLevel("error") +} + +object TracingFilterConfigurations { + val release = TracingFilterConfiguration( + overrides = mapOf( + 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 + ) + ) + + /** + * Use this method to create a custom configuration where all targets will have the same log level. + */ + fun custom(logLevel: LogLevel) = TracingFilterConfiguration(overrides = Target.values().associateWith { logLevel }) + + /** + * Use this method to override the log level of specific targets. + */ + fun custom(overrides: Map) = TracingFilterConfiguration(overrides) +} diff --git a/app/src/main/kotlin/io/element/android/x/initializer/TimberInitializer.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingService.kt similarity index 51% rename from app/src/main/kotlin/io/element/android/x/initializer/TimberInitializer.kt rename to libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingService.kt index 5a641d75c6..4a74f83b20 100644 --- a/app/src/main/kotlin/io/element/android/x/initializer/TimberInitializer.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingService.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * 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. @@ -14,22 +14,11 @@ * limitations under the License. */ -package io.element.android.x.initializer +package io.element.android.libraries.matrix.api.tracing -import android.content.Context -import androidx.startup.Initializer -import io.element.android.features.rageshake.impl.logs.VectorFileLogger -import io.element.android.x.BuildConfig import timber.log.Timber -class TimberInitializer : Initializer { - - override fun create(context: Context) { - if (BuildConfig.DEBUG) { - Timber.plant(Timber.DebugTree()) - } - Timber.plant(VectorFileLogger(context)) - } - - override fun dependencies(): List>> = emptyList() +interface TracingService { + fun setupTracing(tracingConfiguration: TracingConfiguration) + fun createTimberTree(): Timber.Tree } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/WriteToFilesConfiguration.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/WriteToFilesConfiguration.kt new file mode 100644 index 0000000000..cafa375a6a --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/WriteToFilesConfiguration.kt @@ -0,0 +1,22 @@ +/* + * 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.libraries.matrix.api.tracing + +sealed class WriteToFilesConfiguration { + object Disabled : WriteToFilesConfiguration() + data class Enabled(val directory: String, val filenamePrefix: String) : WriteToFilesConfiguration() +} diff --git a/libraries/matrix/impl/build.gradle.kts b/libraries/matrix/impl/build.gradle.kts index 499e36988c..016833def6 100644 --- a/libraries/matrix/impl/build.gradle.kts +++ b/libraries/matrix/impl/build.gradle.kts @@ -29,7 +29,7 @@ anvil { } dependencies { -// api(projects.libraries.rustsdk) + // implementation(projects.libraries.rustsdk) implementation(libs.matrix.sdk) implementation(projects.libraries.di) implementation(projects.libraries.androidutils) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt index ca40ca400c..2546614a78 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt @@ -37,12 +37,12 @@ class RustSyncService( ) : SyncService { override suspend fun startSync() = runCatching { - Timber.v("Start sync") + Timber.i("Start sync") innerSyncService.start() } override fun stopSync() = runCatching { - Timber.v("Stop sync") + Timber.i("Stop sync") innerSyncService.pause() } @@ -50,7 +50,7 @@ class RustSyncService( roomListStateFlow .map(RoomListServiceState::toSyncState) .onEach { state -> - Timber.v("Sync state=$state") + Timber.i("Sync state=$state") } .distinctUntilChanged() .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, SyncState.Idle) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/LogEventLocation.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/LogEventLocation.kt new file mode 100644 index 0000000000..51b8923cc8 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/LogEventLocation.kt @@ -0,0 +1,42 @@ +/* + * 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.libraries.matrix.impl.tracing + +/** + * This class is used to provide file, line, column information to the Rust SDK [org.matrix.rustcomponents.sdk.logEvent] method. + * The data is extracted from a [StackTraceElement] instance. + */ +data class LogEventLocation( + val file: String, + val line: UInt, + val column: UInt, +) { + + companion object { + /** + * Create a [LogEventLocation] from a [StackTraceElement]. + */ + fun from(stackTraceElement: StackTraceElement): LogEventLocation { + return LogEventLocation( + file = stackTraceElement.fileName, + line = stackTraceElement.lineNumber.toUInt(), + column = 0u, + ) + } + } +} + 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 new file mode 100644 index 0000000000..d3bd53bd10 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt @@ -0,0 +1,51 @@ +/* + * 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.libraries.matrix.impl.tracing + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +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 timber.log.Timber +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class RustTracingService @Inject constructor() : TracingService { + + override fun setupTracing(tracingConfiguration: TracingConfiguration) { + val filter = tracingConfiguration.filterConfiguration + val rustTracingConfiguration = org.matrix.rustcomponents.sdk.TracingConfiguration( + filter = tracingConfiguration.filterConfiguration.filter, + writeToStdoutOrSystem = tracingConfiguration.writesToLogcat, + writeToFiles = when (val writeToFilesConfiguration = tracingConfiguration.writesToFilesConfiguration) { + is WriteToFilesConfiguration.Disabled -> null + is WriteToFilesConfiguration.Enabled -> TracingFileConfiguration( + path = writeToFilesConfiguration.directory, + filePrefix = writeToFilesConfiguration.filenamePrefix, + ) + }, + ) + org.matrix.rustcomponents.sdk.setupTracing(rustTracingConfiguration) + Timber.v("Tracing config filter = $filter") + } + + override fun createTimberTree(): Timber.Tree { + return RustTracingTree() + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingTree.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingTree.kt new file mode 100644 index 0000000000..2131bb83ba --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingTree.kt @@ -0,0 +1,75 @@ +/* + * 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.libraries.matrix.impl.tracing + +import android.util.Log +import io.element.android.libraries.matrix.api.tracing.Target +import org.matrix.rustcomponents.sdk.LogLevel +import org.matrix.rustcomponents.sdk.logEvent +import timber.log.Timber + +/** + * List of fully qualified class names to ignore when looking for the first stack trace element. + */ +private val fqcnIgnore = listOf( + Timber::class.java.name, + Timber.Forest::class.java.name, + Timber.Tree::class.java.name, + RustTracingTree::class.java.name, +) + +/** + * A Timber tree that passes logs to the Rust SDK. + */ +internal class RustTracingTree : Timber.Tree() { + + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + val location = getLogEventLocationFromStackTrace() + val logLevel = priority.toLogLevel() + logEvent( + file = location.file, + line = location.line, + column = location.column, + level = logLevel, + target = Target.ELEMENT.filter, + message = message, + ) + } + + /** + * Extract the [LogEventLocation] from the stack trace. + */ + private fun getLogEventLocationFromStackTrace(): LogEventLocation { + return Throwable(null, null).stackTrace + .first { it.className !in fqcnIgnore } + .let(LogEventLocation::from) + } +} + +/** + * Convert a log priority to a Rust SDK log level. + */ +private fun Int.toLogLevel(): LogLevel { + return when (this) { + Log.VERBOSE -> LogLevel.TRACE + Log.DEBUG -> LogLevel.DEBUG + Log.INFO -> LogLevel.INFO + Log.WARN -> LogLevel.WARN + Log.ERROR -> LogLevel.ERROR + else -> LogLevel.DEBUG + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/TracingConfiguration.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/TracingConfiguration.kt deleted file mode 100644 index ac8c62aeb6..0000000000 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/TracingConfiguration.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2022 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.libraries.matrix.impl.tracing - -import io.element.android.libraries.matrix.api.tracing.TracingConfiguration -import timber.log.Timber - -fun setupTracing(tracingConfiguration: TracingConfiguration) { - val filter = tracingConfiguration.filter - Timber.v("Tracing config filter = $filter") - val rustTracingConfiguration = org.matrix.rustcomponents.sdk.TracingConfiguration( - filter = filter, - writeToStdoutOrSystem = true, - writeToFiles = null, - ) - org.matrix.rustcomponents.sdk.setupTracing(rustTracingConfiguration) -} diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/Singleton.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/Singleton.kt index 5f8c6555a5..77057d2e45 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/Singleton.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/Singleton.kt @@ -17,19 +17,24 @@ package io.element.android.samples.minimal import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.matrix.impl.tracing.setupTracing -import io.element.android.libraries.matrix.api.tracing.TracingConfigurations +import io.element.android.libraries.matrix.api.tracing.TracingConfiguration +import io.element.android.libraries.matrix.api.tracing.TracingFilterConfigurations +import io.element.android.libraries.matrix.api.tracing.WriteToFilesConfiguration +import io.element.android.libraries.matrix.impl.tracing.RustTracingService import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope import kotlinx.coroutines.plus -import timber.log.Timber object Singleton { init { - Timber.plant(Timber.DebugTree()) - setupTracing(TracingConfigurations.debug) + val tracingConfiguration = TracingConfiguration( + filterConfiguration = TracingFilterConfigurations.debug, + writesToLogcat = true, + writesToFilesConfiguration = WriteToFilesConfiguration.Disabled + ) + RustTracingService().setupTracing(tracingConfiguration) } val appScope = MainScope() + CoroutineName("Minimal Scope")