From 2a6796aed3f4ee0b67d8fccfd892e57831636232 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Wed, 15 Jan 2025 12:17:18 +0100 Subject: [PATCH] feat: database logger [WPB-14608] (#3227) * feat: database logger * detekt * detekt * set logger name per platform * Trigger CI * fix test --- .../feature/debug/ChangeProfilingUseCase.kt | 7 +- .../kalium/logic/feature/debug/DebugScope.kt | 6 +- .../ObserveDatabaseLoggerStateUseCase.kt | 30 +++++ .../persistence/db/DebugExtension.android.kt | 20 ++++ .../kalium/persistence/db/UserDatabase.kt | 1 - .../persistence/db/DebugExtension.apple.kt | 20 ++++ .../kalium/persistence/db/DebugExtension.kt | 111 ++++++++++++++++++ .../persistence/db/UserDatabaseBuilder.kt | 27 ++--- .../persistence/dao/client/ClientDAOTest.kt | 38 ------ .../persistence/db/DebugExtension.js.kt | 22 ++++ .../persistence/db/DebugExtension.jvm.kt | 22 ++++ 11 files changed, 239 insertions(+), 65 deletions(-) create mode 100644 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/ObserveDatabaseLoggerStateUseCase.kt create mode 100644 persistence/src/androidMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.android.kt create mode 100644 persistence/src/appleMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.apple.kt create mode 100644 persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.kt create mode 100644 persistence/src/jsMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.js.kt create mode 100644 persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.jvm.kt diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/ChangeProfilingUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/ChangeProfilingUseCase.kt index 83588fea472..fc5ee605a03 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/ChangeProfilingUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/ChangeProfilingUseCase.kt @@ -23,10 +23,9 @@ class ChangeProfilingUseCase( private val userStorage: UserStorage, ) { /** - * Changes the profiling of the database (cipher_profile) if the profile is specified and the database is encrypted - * @param enabled true to enable profiling, false to disable + * Change profiling state. */ - operator fun invoke(enabled: Boolean) { - userStorage.database.changeProfiling(enabled) + suspend operator fun invoke(enabled: Boolean) { + userStorage.database.debugExtension.changeProfiling(enabled) } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/DebugScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/DebugScope.kt index 3ac9e3e5763..6367488ad5f 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/DebugScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/DebugScope.kt @@ -93,7 +93,7 @@ class DebugScope internal constructor( private val legalHoldHandler: LegalHoldHandler, private val notificationTokenRepository: NotificationTokenRepository, private val scope: CoroutineScope, - userStorage: UserStorage, + private val userStorage: UserStorage, logger: KaliumLogger, internal val dispatcher: KaliumDispatcher = KaliumDispatcherImpl, ) { @@ -227,5 +227,7 @@ class DebugScope internal constructor( notificationTokenRepository, ) - val changeProfiling: ChangeProfilingUseCase = ChangeProfilingUseCase(userStorage) + val changeProfiling: ChangeProfilingUseCase get() = ChangeProfilingUseCase(userStorage) + + val observeDatabaseLoggerState get() = ObserveDatabaseLoggerStateUseCase(userStorage) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/ObserveDatabaseLoggerStateUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/ObserveDatabaseLoggerStateUseCase.kt new file mode 100644 index 00000000000..c4fd40af94d --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/ObserveDatabaseLoggerStateUseCase.kt @@ -0,0 +1,30 @@ +/* + * Wire + * Copyright (C) 2025 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.logic.feature.debug + +import com.wire.kalium.logic.di.UserStorage +import kotlinx.coroutines.flow.Flow + +/** + * Use case to observe the state of the database logger. + */ +class ObserveDatabaseLoggerStateUseCase( + private val userStorage: UserStorage, +) { + suspend operator fun invoke(): Flow = userStorage.database.debugExtension.observeIsProfilingEnabled() +} diff --git a/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.android.kt b/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.android.kt new file mode 100644 index 00000000000..86825694070 --- /dev/null +++ b/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.android.kt @@ -0,0 +1,20 @@ +/* + * Wire + * Copyright (C) 2025 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.persistence.db + +internal actual fun platformDatabaseLogger(): String = "logcat" diff --git a/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/db/UserDatabase.kt b/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/db/UserDatabase.kt index 4491d1bf47c..84f63d0a743 100644 --- a/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/db/UserDatabase.kt +++ b/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/db/UserDatabase.kt @@ -57,7 +57,6 @@ actual fun userDatabaseBuilder( dispatcher = dispatcher, platformDatabaseData = platformDatabaseData, isEncrypted = isEncryptionEnabled, - cipherProfile = "logcat", ) } diff --git a/persistence/src/appleMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.apple.kt b/persistence/src/appleMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.apple.kt new file mode 100644 index 00000000000..0d5faf02c18 --- /dev/null +++ b/persistence/src/appleMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.apple.kt @@ -0,0 +1,20 @@ +/* + * Wire + * Copyright (C) 2025 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.persistence.db + +internal actual fun platformDatabaseLogger(): String = "os_log" diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.kt new file mode 100644 index 00000000000..a2a232ecd3c --- /dev/null +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.kt @@ -0,0 +1,111 @@ +/* + * Wire + * Copyright (C) 2025 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.persistence.db + +import app.cash.sqldelight.db.QueryResult +import app.cash.sqldelight.db.SqlDriver +import com.wire.kalium.persistence.dao.MetadataDAO +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class DebugExtension( + private val sqlDriver: SqlDriver, + private val isEncrypted: Boolean, + private val metaDataDao: MetadataDAO, +) { + + suspend fun observeIsProfilingEnabled(): Flow = + metaDataDao.valueByKeyFlow(KEY_CIPHER_PROFILE) + .map { state -> + state?.let { DBProfile.fromString(it) }.let { + it is DBProfile.ON + } + } + + /** + * Changes the profiling of the database (cipher_profile) if the profile is specified and the database is encrypted + * @param enabled true to enable profiling, false to disable + */ + suspend fun changeProfiling(enabled: Boolean): Long? = + if (isEncrypted) { + val state = if (enabled) DBProfile.ON.Device else DBProfile.Off + sqlDriver.executeQuery( + identifier = null, + sql = """PRAGMA cipher_profile= '${state.logTarget}';""", + mapper = { cursor -> + cursor.next() + cursor.getLong(0).let { QueryResult.Value(it) } + }, + parameters = 0, + ).value.also { + updateMetadata(state) + } + + } else { + error("Cannot change profiling on unencrypted database") + } + + private suspend fun updateMetadata(state: DBProfile) { + metaDataDao.insertValue( + value = state.logTarget, + key = KEY_CIPHER_PROFILE + ) + } + + private companion object { + const val KEY_CIPHER_PROFILE = "cipher_profile" + } +} + +sealed interface DBProfile { + val logTarget: String + + data object Off : DBProfile { + override val logTarget: String = "off" + + override fun toString(): String { + return "off" + } + } + + sealed interface ON : DBProfile { + data object Device : ON { + override val logTarget: String = "logcat" + + override fun toString(): String { + return platformDatabaseLogger() + } + } + + data class CustomFile(override val logTarget: String) : ON { + override fun toString(): String { + return logTarget + } + } + } + + companion object { + fun fromString(value: String): DBProfile = when (value) { + "off" -> Off + platformDatabaseLogger() -> ON.Device + else -> ON.CustomFile(value) + } + } +} + +internal expect fun platformDatabaseLogger(): String diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt index 76f9ab33e17..7bbebca1f26 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt @@ -120,7 +120,6 @@ class UserDatabaseBuilder internal constructor( private val platformDatabaseData: PlatformDatabaseData, private val isEncrypted: Boolean, private val queriesContext: CoroutineContext = KaliumDispatcherImpl.io, - private val cipherProfile: String? = null, ) { internal val database: UserDatabase = UserDatabase( @@ -314,30 +313,18 @@ class UserDatabaseBuilder internal constructor( queriesContext ) + val debugExtension: DebugExtension + get() = DebugExtension( + sqlDriver = sqlDriver, + metaDataDao = metadataDAO, + isEncrypted = isEncrypted + ) + /** * @return the absolute path of the DB file or null if the DB file does not exist */ fun dbFileLocation(): String? = getDatabaseAbsoluteFileLocation(platformDatabaseData, userId) - /** - * Changes the profiling of the database (cipher_profile) if the profile is specified and the database is encrypted - * @param enabled true to enable profiling, false to disable - */ - fun changeProfiling(enabled: Boolean) { - if (isEncrypted && cipherProfile != null) { - val cipherProfileValue = if (enabled) cipherProfile else "off" - sqlDriver.executeQuery( - identifier = null, - sql = "PRAGMA cipher_profile='$cipherProfileValue'", - mapper = { - it.next() - it.getLong(0).let { QueryResult.Value(it) } - }, - parameters = 0, - ) - } - } - /** * drops DB connection and delete the DB file */ diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/client/ClientDAOTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/client/ClientDAOTest.kt index 4fa472f3ee1..1f871df56f9 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/client/ClientDAOTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/client/ClientDAOTest.kt @@ -484,44 +484,6 @@ class ClientDAOTest : BaseDatabaseTest() { assertNull(clientDAO.isMLSCapable(userId, clientId = client.id)) } - @Test - fun givenPersistedClient_whenUpsertingTheSameExactClient_thenItShouldIgnoreAndNotNotifyOtherQueries() = runTest { - // Given - userDAO.upsertUser(user) - clientDAO.insertClient(insertedClient) - - clientDAO.observeClient(user.id, insertedClient.id).test { - val initialValue = awaitItem() - assertEquals(insertedClient.toClient(), initialValue) - - // When - clientDAO.insertClient(insertedClient) // the same exact client is being saved again - - // Then - expectNoEvents() // other query should not be notified - } - } - - @Test - fun givenPersistedClient_whenUpsertingUpdatedClient_thenItShouldBeSavedAndOtherQueriesShouldBeUpdated() = runTest { - // Given - userDAO.upsertUser(user) - clientDAO.insertClient(insertedClient) - val updatedInsertedClient = insertedClient.copy(label = "new_label") - - clientDAO.observeClient(user.id, insertedClient.id).test { - val initialValue = awaitItem() - assertEquals(insertedClient.toClient(), initialValue) - - // When - clientDAO.insertClient(updatedInsertedClient) // updated client is being saved that should replace the old one - - // Then - val updatedValue = awaitItem() // other query should be notified - assertEquals(updatedInsertedClient.toClient(), updatedValue) - } - } - private companion object { val userId = QualifiedIDEntity("test", "domain") val user = newUserEntity(userId) diff --git a/persistence/src/jsMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.js.kt b/persistence/src/jsMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.js.kt new file mode 100644 index 00000000000..232bf463761 --- /dev/null +++ b/persistence/src/jsMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.js.kt @@ -0,0 +1,22 @@ +/* + * Wire + * Copyright (C) 2025 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.persistence.db + +internal actual fun platformDatabaseLogger(): String { + TODO("Not yet implemented") +} diff --git a/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.jvm.kt b/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.jvm.kt new file mode 100644 index 00000000000..232bf463761 --- /dev/null +++ b/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/db/DebugExtension.jvm.kt @@ -0,0 +1,22 @@ +/* + * Wire + * Copyright (C) 2025 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.persistence.db + +internal actual fun platformDatabaseLogger(): String { + TODO("Not yet implemented") +}