diff --git a/dd-sdk-android/apiSurface b/dd-sdk-android/apiSurface index 3852985fd8..0b3d3698d0 100644 --- a/dd-sdk-android/apiSurface +++ b/dd-sdk-android/apiSurface @@ -1848,7 +1848,8 @@ interface com.datadog.android.v2.api.InternalLogger - USER - MAINTAINER - TELEMETRY - fun log(Level, Target, String, Throwable?, Map) + fun log(Level, Target, String, Throwable?, Map) + fun log(Level, List, String, Throwable?, Map) interface com.datadog.android.v2.api.PayloadFormat fun prefixBytes(): ByteArray fun suffixBytes(): ByteArray diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/core/internal/SdkFeature.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/core/internal/SdkFeature.kt index 62b78c4ac5..c5d3eb6c97 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/core/internal/SdkFeature.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/core/internal/SdkFeature.kt @@ -27,8 +27,8 @@ import com.datadog.android.v2.api.FeatureEventReceiver import com.datadog.android.v2.api.FeatureScope import com.datadog.android.v2.api.FeatureStorageConfiguration import com.datadog.android.v2.api.FeatureUploadConfiguration -import com.datadog.android.v2.api.NoOpInternalLogger import com.datadog.android.v2.api.context.DatadogContext +import com.datadog.android.v2.core.SdkInternalLogger import com.datadog.android.v2.core.internal.NoOpContextProvider import com.datadog.android.v2.core.internal.data.upload.DataFlusher import com.datadog.android.v2.core.internal.data.upload.DataUploadScheduler @@ -234,8 +234,7 @@ internal class SdkFeature( encryption = coreFeature.localDataEncryption ), fileMover = FileMover(sdkLogger), - // TODO RUMM-0000 create internal logger - internalLogger = NoOpInternalLogger(), + internalLogger = SdkInternalLogger, filePersistenceConfig = coreFeature.buildFilePersistenceConfig().copy( maxBatchSize = storageConfiguration.maxBatchSize, maxItemSize = storageConfiguration.maxItemSize, diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/v2/api/InternalLogger.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/v2/api/InternalLogger.kt index f6f83d1b3e..176414ec2b 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/v2/api/InternalLogger.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/v2/api/InternalLogger.kt @@ -46,6 +46,22 @@ interface InternalLogger { target: Target, message: String, error: Throwable?, - attributes: Map + attributes: Map + ) + + /** + * Logs a message from the internal implementation. + * @param level the severity level of the log + * @param targets list of the target handlers for the log + * @param message the log message + * @param error an optional throwable error + * @param attributes an optional map of custom attributes + */ + fun log( + level: Level, + targets: List, + message: String, + error: Throwable?, + attributes: Map ) } diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/v2/core/SdkInternalLogger.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/v2/core/SdkInternalLogger.kt new file mode 100644 index 0000000000..50cea73e16 --- /dev/null +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/v2/core/SdkInternalLogger.kt @@ -0,0 +1,104 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.v2.core + +import android.util.Log +import com.datadog.android.core.internal.utils.devLogger +import com.datadog.android.core.internal.utils.sdkLogger +import com.datadog.android.core.internal.utils.telemetry +import com.datadog.android.v2.api.InternalLogger + +internal object SdkInternalLogger : InternalLogger { + + // region InternalLogger + + override fun log( + level: InternalLogger.Level, + target: InternalLogger.Target, + message: String, + error: Throwable?, + attributes: Map + ) { + when (target) { + // TODO RUMM-2764 we should remove sdkLogger, devLogger and telemetry + // and use only this instance for logging + InternalLogger.Target.USER -> logToUser(level, message, error, attributes) + InternalLogger.Target.MAINTAINER -> logToMaintainer(level, message, error, attributes) + InternalLogger.Target.TELEMETRY -> logToTelemetry(level, message, error) + } + } + + override fun log( + level: InternalLogger.Level, + targets: List, + message: String, + error: Throwable?, + attributes: Map + ) { + targets.forEach { + log(level, it, message, error, attributes) + } + } + + // endregion + + // region Internal + + private fun logToUser( + level: InternalLogger.Level, + message: String, + error: Throwable?, + attributes: Map + ) { + devLogger.log( + level.toLogLevel(), + message, + error, + attributes + ) + } + + private fun logToMaintainer( + level: InternalLogger.Level, + message: String, + error: Throwable?, + attributes: Map + ) { + sdkLogger.log( + level.toLogLevel(), + message, + error, + attributes + ) + } + + private fun logToTelemetry( + level: InternalLogger.Level, + message: String, + error: Throwable? + ) { + if (level == InternalLogger.Level.ERROR || + level == InternalLogger.Level.WARN || + error != null + ) { + telemetry.error(message, error) + } else { + telemetry.debug(message) + } + } + + private fun InternalLogger.Level.toLogLevel(): Int { + return when (this) { + InternalLogger.Level.DEBUG -> Log.DEBUG + InternalLogger.Level.INFO -> Log.INFO + InternalLogger.Level.WARN -> Log.WARN + InternalLogger.Level.ERROR -> Log.ERROR + } + } + + // endregion +} diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/v2/core/SdkInternalLoggerTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/v2/core/SdkInternalLoggerTest.kt new file mode 100644 index 0000000000..5023a4bfeb --- /dev/null +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/v2/core/SdkInternalLoggerTest.kt @@ -0,0 +1,187 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.v2.core + +import android.util.Log +import com.datadog.android.utils.config.GlobalRumMonitorTestConfiguration +import com.datadog.android.utils.config.LoggerTestConfiguration +import com.datadog.android.utils.forge.Configurator +import com.datadog.android.utils.forge.exhaustiveAttributes +import com.datadog.android.v2.api.InternalLogger +import com.datadog.tools.unit.annotations.TestConfigurationsProvider +import com.datadog.tools.unit.extensions.TestConfigurationExtension +import com.datadog.tools.unit.extensions.config.TestConfiguration +import com.datadog.tools.unit.forge.aThrowable +import com.nhaarman.mockitokotlin2.verify +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.StringForgery +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.quality.Strictness + +@Extensions( + ExtendWith(ForgeExtension::class), + ExtendWith(TestConfigurationExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +@ForgeConfiguration(Configurator::class) +internal class SdkInternalLoggerTest { + + private val testedInternalLogger = SdkInternalLogger + + @Test + fun `𝕄 send dev log 𝕎 log { USER target }`( + @StringForgery fakeMessage: String, + forge: Forge + ) { + // Given + val fakeLevel = forge.aValueFrom(InternalLogger.Level::class.java) + val fakeThrowable = forge.aNullable { forge.aThrowable() } + val fakeAttributes = forge.exhaustiveAttributes() + + // When + testedInternalLogger.log( + fakeLevel, + InternalLogger.Target.USER, + fakeMessage, + fakeThrowable, + fakeAttributes + ) + + // Then + verify(logger.mockDevLogHandler) + .handleLog( + fakeLevel.toLogLevel(), + fakeMessage, + fakeThrowable, + fakeAttributes + ) + } + + @Test + fun `𝕄 send sdk log 𝕎 log { MAINTAINER target }`( + @StringForgery fakeMessage: String, + forge: Forge + ) { + // Given + val fakeLevel = forge.aValueFrom(InternalLogger.Level::class.java) + val fakeThrowable = forge.aNullable { forge.aThrowable() } + val fakeAttributes = forge.exhaustiveAttributes() + + // When + testedInternalLogger.log( + fakeLevel, + InternalLogger.Target.MAINTAINER, + fakeMessage, + fakeThrowable, + fakeAttributes + ) + + // Then + verify(logger.mockSdkLogHandler) + .handleLog( + fakeLevel.toLogLevel(), + fakeMessage, + fakeThrowable, + fakeAttributes + ) + } + + @Test + fun `𝕄 send telemetry log 𝕎 log { TELEMETRY target, no throwable + info or debug }`( + @StringForgery fakeMessage: String, + forge: Forge + ) { + // Given + val fakeLevel = forge.anElementFrom(InternalLogger.Level.INFO, InternalLogger.Level.DEBUG) + val fakeAttributes = forge.exhaustiveAttributes() + + // When + testedInternalLogger.log( + fakeLevel, + InternalLogger.Target.TELEMETRY, + fakeMessage, + null, + fakeAttributes + ) + + // Then + verify(rumMonitor.mockInstance) + .sendDebugTelemetryEvent(fakeMessage) + } + + @Test + fun `𝕄 send telemetry log 𝕎 log { TELEMETRY target, no throwable + warn or error }`( + @StringForgery fakeMessage: String, + forge: Forge + ) { + // Given + val fakeLevel = forge.anElementFrom(InternalLogger.Level.WARN, InternalLogger.Level.ERROR) + val fakeAttributes = forge.exhaustiveAttributes() + + // When + testedInternalLogger.log( + fakeLevel, + InternalLogger.Target.TELEMETRY, + fakeMessage, + null, + fakeAttributes + ) + + // Then + verify(rumMonitor.mockInstance) + .sendErrorTelemetryEvent(fakeMessage, null) + } + + @Test + fun `𝕄 send telemetry log 𝕎 log { TELEMETRY target, with throwable}`( + @StringForgery fakeMessage: String, + forge: Forge + ) { + // Given + val fakeLevel = forge.aValueFrom(InternalLogger.Level::class.java) + val fakeThrowable = forge.aThrowable() + val fakeAttributes = forge.exhaustiveAttributes() + + // When + testedInternalLogger.log( + fakeLevel, + InternalLogger.Target.TELEMETRY, + fakeMessage, + fakeThrowable, + fakeAttributes + ) + + // Then + verify(rumMonitor.mockInstance) + .sendErrorTelemetryEvent(fakeMessage, fakeThrowable) + } + + private fun InternalLogger.Level.toLogLevel(): Int { + return when (this) { + InternalLogger.Level.DEBUG -> Log.DEBUG + InternalLogger.Level.INFO -> Log.INFO + InternalLogger.Level.WARN -> Log.WARN + InternalLogger.Level.ERROR -> Log.ERROR + } + } + + companion object { + val logger = LoggerTestConfiguration() + val rumMonitor = GlobalRumMonitorTestConfiguration() + + @TestConfigurationsProvider + @JvmStatic + fun getTestConfigurations(): List { + return listOf(logger, rumMonitor) + } + } +}