chore: Add logger for the SDK (#7403)

This commit is contained in:
David Perez
2026-10-01 18:20:11 +00:00
committed by GitHub
parent 875f02233a
commit ac9c88cd90
8 changed files with 198 additions and 3 deletions
@@ -7,8 +7,10 @@ import com.bitwarden.core.util.isBuildVersionAtLeast
import com.bitwarden.data.manager.NativeLibraryManager
import com.bitwarden.sdk.Client
import com.bitwarden.sdk.ManagedSettingsBindingClient
import com.bitwarden.sdk.initLogger
import com.x8bit.bitwarden.data.platform.manager.sdk.SdkPlatformApiFactory
import com.x8bit.bitwarden.data.platform.manager.sdk.SdkRepositoryFactory
import com.x8bit.bitwarden.data.platform.manager.sdk.log.SdkLoggerFactory
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
@@ -17,11 +19,13 @@ import kotlinx.coroutines.runBlocking
/**
* Primary implementation of [SdkClientManager].
*/
@Suppress("LongParameterList")
internal class SdkClientManagerImpl(
nativeLibraryManager: NativeLibraryManager,
dispatcherManager: DispatcherManager,
sdkRepoFactory: SdkRepositoryFactory,
sdkPlatformApiFactory: SdkPlatformApiFactory,
sdkLoggerFactory: SdkLoggerFactory,
private val featureFlagManager: FeatureFlagManager,
private val clientProvider: suspend (
userId: String?,
@@ -63,6 +67,11 @@ internal class SdkClientManagerImpl(
if (!isBuildVersionAtLeast(Build.VERSION_CODES.S)) {
nativeLibraryManager.loadLibrary("bitwarden_uniffi")
}
initLogger(
callback = sdkLoggerFactory.getLogCallback(),
level = sdkLoggerFactory.logLevel,
platformLogger = false,
)
// Initialize this now, so that we can access it synchronously later on.
globalClientDeferred = ioScope.async { clientProvider(null, null) }
}
@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.platform.manager.di
import android.app.Application
import android.content.Context
import androidx.core.content.getSystemService
import com.bitwarden.core.data.manager.BuildInfoManager
import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
import com.bitwarden.core.data.manager.dispatcher.DispatcherManagerImpl
import com.bitwarden.core.data.manager.realtime.RealtimeManager
@@ -93,6 +94,8 @@ import com.x8bit.bitwarden.data.platform.manager.sdk.SdkPlatformApiFactory
import com.x8bit.bitwarden.data.platform.manager.sdk.SdkPlatformApiFactoryImpl
import com.x8bit.bitwarden.data.platform.manager.sdk.SdkRepositoryFactory
import com.x8bit.bitwarden.data.platform.manager.sdk.SdkRepositoryFactoryImpl
import com.x8bit.bitwarden.data.platform.manager.sdk.log.SdkLoggerFactory
import com.x8bit.bitwarden.data.platform.manager.sdk.log.SdkLoggerFactoryImpl
import com.x8bit.bitwarden.data.platform.processor.AuthenticatorBridgeProcessor
import com.x8bit.bitwarden.data.platform.processor.AuthenticatorBridgeProcessorImpl
import com.x8bit.bitwarden.data.platform.repository.AuthenticatorBridgeRepository
@@ -241,12 +244,14 @@ object PlatformManagerModule {
nativeLibraryManager: NativeLibraryManager,
sdkRepositoryFactory: SdkRepositoryFactory,
sdkPlatformApiFactory: SdkPlatformApiFactory,
sdkLoggerFactory: SdkLoggerFactory,
): SdkClientManager = SdkClientManagerImpl(
dispatcherManager = dispatcherManager,
featureFlagManager = featureFlagManager,
nativeLibraryManager = nativeLibraryManager,
sdkRepoFactory = sdkRepositoryFactory,
sdkPlatformApiFactory = sdkPlatformApiFactory,
sdkLoggerFactory = sdkLoggerFactory,
)
@Provides
@@ -436,6 +441,14 @@ object PlatformManagerModule {
serverCommConfigManager = serverCommConfigManager,
)
@Provides
@Singleton
fun provideSdkLoggerFactory(
buildInfoManager: BuildInfoManager,
): SdkLoggerFactory = SdkLoggerFactoryImpl(
buildInfoManager = buildInfoManager,
)
@Provides
@Singleton
fun provideKeyManager(
@@ -0,0 +1,19 @@
package com.x8bit.bitwarden.data.platform.manager.sdk.log
import com.bitwarden.sdk.LogCallback
import com.bitwarden.sdk.LogLevel
/**
* Creates and manages the logger for the Bitwarden SDK.
*/
interface SdkLoggerFactory {
/**
* The log level to be used with the Bitwarden SDK.
*/
val logLevel: LogLevel
/**
* The [LogCallback] to be used with the Bitwarden SDK.
*/
fun getLogCallback(): LogCallback
}
@@ -0,0 +1,34 @@
package com.x8bit.bitwarden.data.platform.manager.sdk.log
import android.util.Log
import com.bitwarden.core.data.manager.BuildInfoManager
import com.bitwarden.sdk.LogCallback
import com.bitwarden.sdk.LogLevel
import timber.log.Timber
/**
* The default implementation for the [SdkLoggerFactory].
*/
internal class SdkLoggerFactoryImpl(
private val buildInfoManager: BuildInfoManager,
) : SdkLoggerFactory {
override val logLevel: LogLevel
get() = if (buildInfoManager.isDevBuild) LogLevel.DEBUG else LogLevel.INFO
override fun getLogCallback(): LogCallback = object : LogCallback {
override fun onLog(level: LogLevel, target: String, message: String) {
Timber
.tag(tag = "BitwardenSdkClient")
.log(priority = level.logLevel, message = "$target --$message")
}
}
}
private val LogLevel.logLevel: Int
get() = when (this) {
LogLevel.TRACE -> Log.VERBOSE
LogLevel.DEBUG -> Log.DEBUG
LogLevel.INFO -> Log.INFO
LogLevel.WARN -> Log.WARN
LogLevel.ERROR -> Log.ERROR
}
@@ -8,6 +8,7 @@ import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.SdkClientManagerImpl
import com.x8bit.bitwarden.data.platform.manager.sdk.SdkPlatformApiFactory
import com.x8bit.bitwarden.data.platform.manager.sdk.SdkRepositoryFactory
import com.x8bit.bitwarden.data.platform.manager.sdk.log.SdkLoggerFactory
/**
* The default instance of the [ScopedVaultSdkSource]. This uses its own instance of the
@@ -19,6 +20,7 @@ class ScopedVaultSdkSourceImpl(
featureFlagManager: FeatureFlagManager,
sdkRepositoryFactory: SdkRepositoryFactory,
sdkPlatformApiFactory: SdkPlatformApiFactory,
sdkLoggerFactory: SdkLoggerFactory,
vaultSdkSource: VaultSdkSource = VaultSdkSourceImpl(
sdkClientManager = SdkClientManagerImpl(
dispatcherManager = dispatcherManager,
@@ -30,6 +32,7 @@ class ScopedVaultSdkSourceImpl(
sdkRepoFactory = sdkRepositoryFactory,
featureFlagManager = featureFlagManager,
sdkPlatformApiFactory = sdkPlatformApiFactory,
sdkLoggerFactory = sdkLoggerFactory,
),
dispatcherManager = dispatcherManager,
),
@@ -7,6 +7,7 @@ import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.SdkClientManager
import com.x8bit.bitwarden.data.platform.manager.sdk.SdkPlatformApiFactory
import com.x8bit.bitwarden.data.platform.manager.sdk.SdkRepositoryFactory
import com.x8bit.bitwarden.data.platform.manager.sdk.log.SdkLoggerFactory
import com.x8bit.bitwarden.data.vault.datasource.sdk.ScopedVaultSdkSource
import com.x8bit.bitwarden.data.vault.datasource.sdk.ScopedVaultSdkSourceImpl
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
@@ -43,12 +44,14 @@ object VaultSdkModule {
featureFlagManager: FeatureFlagManager,
sdkRepositoryFactory: SdkRepositoryFactory,
sdkPlatformApiFactory: SdkPlatformApiFactory,
sdkLoggerFactory: SdkLoggerFactory,
): ScopedVaultSdkSource =
ScopedVaultSdkSourceImpl(
dispatcherManager = dispatcherManager,
featureFlagManager = featureFlagManager,
sdkRepositoryFactory = sdkRepositoryFactory,
sdkPlatformApiFactory = sdkPlatformApiFactory,
sdkLoggerFactory = sdkLoggerFactory,
)
@Provides
@@ -3,11 +3,16 @@ package com.x8bit.bitwarden.data.platform.manager
import com.bitwarden.core.data.manager.dispatcher.FakeDispatcherManager
import com.bitwarden.core.util.isBuildVersionAtLeast
import com.bitwarden.data.manager.NativeLibraryManager
import com.bitwarden.sdk.LogLevel
import com.bitwarden.sdk.initLogger
import com.x8bit.bitwarden.data.platform.manager.sdk.SdkPlatformApiFactory
import com.x8bit.bitwarden.data.platform.manager.sdk.SdkRepositoryFactory
import com.x8bit.bitwarden.data.platform.manager.sdk.log.SdkLoggerFactory
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.unmockkStatic
import io.mockk.verify
import kotlinx.coroutines.test.runTest
@@ -28,16 +33,21 @@ class SdkClientManagerTest {
private val sdkPlatformApiFactory: SdkPlatformApiFactory = mockk {
every { getServerCommunicationConfigPlatformApi() } returns mockk()
}
private val sdkLoggerFactory: SdkLoggerFactory = mockk {
every { logLevel } returns LogLevel.INFO
every { getLogCallback() } returns mockk()
}
@BeforeEach
fun setUp() {
mockkStatic(::isBuildVersionAtLeast)
every { isBuildVersionAtLeast(any()) } returns true
mockkStatic(::isBuildVersionAtLeast, ::initLogger)
every { isBuildVersionAtLeast(version = any()) } returns true
every { initLogger(callback = any(), level = any(), platformLogger = any()) } just runs
}
@AfterEach
fun tearDown() {
unmockkStatic(::isBuildVersionAtLeast)
unmockkStatic(::isBuildVersionAtLeast, ::initLogger)
}
@Test
@@ -106,5 +116,6 @@ class SdkClientManagerTest {
featureFlagManager = mockk(),
sdkRepoFactory = sdkRepoFactory,
sdkPlatformApiFactory = sdkPlatformApiFactory,
sdkLoggerFactory = sdkLoggerFactory,
)
}
@@ -0,0 +1,103 @@
package com.x8bit.bitwarden.data.platform.manager.sdk.log
import android.util.Log
import com.bitwarden.core.data.manager.BuildInfoManager
import com.bitwarden.sdk.LogLevel
import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import timber.log.Timber
class SdkLoggerFactoryTest {
private val buildInfoManager: BuildInfoManager = mockk {
every { isDevBuild } returns false
}
private val recordingTree = RecordingTree()
private val sdkLoggerFactory: SdkLoggerFactory = SdkLoggerFactoryImpl(
buildInfoManager = buildInfoManager,
)
@BeforeEach
fun setup() {
Timber.plant(recordingTree)
}
@AfterEach
fun tearDown() {
Timber.uproot(recordingTree)
}
@Test
fun `logLevel should be DEBUG when the build is a dev build`() {
every { buildInfoManager.isDevBuild } returns true
assertEquals(LogLevel.DEBUG, sdkLoggerFactory.logLevel)
}
@Test
fun `logLevel should be INFO when the build is not a dev build`() {
every { buildInfoManager.isDevBuild } returns false
assertEquals(LogLevel.INFO, sdkLoggerFactory.logLevel)
}
@Test
fun `getLogCallback should return a callback that logs with the SDK tag`() {
sdkLoggerFactory
.getLogCallback()
.onLog(
level = LogLevel.INFO,
target = "bitwarden_core::target",
message = "mockMessage",
)
assertEquals(
listOf(
LogEntry(
priority = Log.INFO,
tag = "BitwardenSdkClient",
message = "bitwarden_core::target --mockMessage",
),
),
recordingTree.logEntries,
)
}
@Test
fun `getLogCallback should return a callback that maps each SDK level to a log priority`() {
val callback = sdkLoggerFactory.getLogCallback()
LogLevel.entries.forEach {
callback.onLog(level = it, target = "mockTarget", message = "mockMessage")
}
assertEquals(
listOf(Log.VERBOSE, Log.DEBUG, Log.INFO, Log.WARN, Log.ERROR),
recordingTree.logEntries.map { it.priority },
)
}
}
/**
* A [Timber.Tree] that records each log for later verification.
*/
private class RecordingTree : Timber.Tree() {
val logEntries: MutableList<LogEntry> = mutableListOf()
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
logEntries.add(LogEntry(priority = priority, tag = tag, message = message))
}
}
/**
* Models a single log received by the [RecordingTree].
*/
private data class LogEntry(
val priority: Int,
val tag: String?,
val message: String,
)