Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RUMM-2754: Create implementation of InternalLogger #1155

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion dd-sdk-android/apiSurface
Original file line number Diff line number Diff line change
Expand Up @@ -1848,7 +1848,8 @@ interface com.datadog.android.v2.api.InternalLogger
- USER
- MAINTAINER
- TELEMETRY
fun log(Level, Target, String, Throwable?, Map<String, Any>)
fun log(Level, Target, String, Throwable?, Map<String, Any?>)
fun log(Level, List<Target>, String, Throwable?, Map<String, Any?>)
interface com.datadog.android.v2.api.PayloadFormat
fun prefixBytes(): ByteArray
fun suffixBytes(): ByteArray
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,22 @@ interface InternalLogger {
target: Target,
message: String,
error: Throwable?,
attributes: Map<String, Any>
attributes: Map<String, Any?>
)

/**
* 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<Target>,
message: String,
error: Throwable?,
attributes: Map<String, Any?>
)
}
Original file line number Diff line number Diff line change
@@ -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<String, Any?>
) {
when (target) {
// TODO RUMM-2764 we should remove sdkLogger, devLogger and telemetry
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point

// 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<InternalLogger.Target>,
message: String,
error: Throwable?,
attributes: Map<String, Any?>
) {
targets.forEach {
log(level, it, message, error, attributes)
}
}

// endregion

// region Internal

private fun logToUser(
level: InternalLogger.Level,
message: String,
error: Throwable?,
attributes: Map<String, Any?>
) {
devLogger.log(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't want to pass sdkLogger, devLogger and telemetry as dependencies or the problem is that they cannot be yet initialized ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, don't want to reference them in the constructor. Anyway it has no real benefit (other than mocking).

level.toLogLevel(),
message,
error,
attributes
)
}

private fun logToMaintainer(
level: InternalLogger.Level,
message: String,
error: Throwable?,
attributes: Map<String, Any?>
) {
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
}
Original file line number Diff line number Diff line change
@@ -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<TestConfiguration> {
return listOf(logger, rumMonitor)
}
}
}