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-924 Introduce the new InternalLogger#metric API #1581

Merged
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
1 change: 1 addition & 0 deletions dd-sdk-android-core/api/apiSurface
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ interface com.datadog.android.api.InternalLogger
- TELEMETRY
fun log(Level, Target, () -> String, Throwable? = null, Boolean = false, Map<String, Any?>? = null)
fun log(Level, List<Target>, () -> String, Throwable? = null, Boolean = false, Map<String, Any?>? = null)
fun logMetric(() -> String, Map<String, Any?>)
companion object
val UNBOUND: InternalLogger
interface com.datadog.android.api.SdkCore
Expand Down
1 change: 1 addition & 0 deletions dd-sdk-android-core/api/dd-sdk-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public abstract interface class com/datadog/android/api/InternalLogger {
public static final field Companion Lcom/datadog/android/api/InternalLogger$Companion;
public abstract fun log (Lcom/datadog/android/api/InternalLogger$Level;Lcom/datadog/android/api/InternalLogger$Target;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;ZLjava/util/Map;)V
public abstract fun log (Lcom/datadog/android/api/InternalLogger$Level;Ljava/util/List;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;ZLjava/util/Map;)V
public abstract fun logMetric (Lkotlin/jvm/functions/Function0;Ljava/util/Map;)V
}

public final class com/datadog/android/api/InternalLogger$Companion {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ interface InternalLogger {
additionalProperties: Map<String, Any?>? = null
)

/**
* Logs a specific metric from the internal implementation. The metric values will be sent
* as key-value pairs in the additionalProperties and as part of the
* [com.datadog.android.telemetry.model.TelemetryDebugEvent.Telemetry] event.
* @param messageBuilder the lambda building the metric message
* @param additionalProperties additional properties to add to the metric
*/
fun logMetric(messageBuilder: () -> String, additionalProperties: Map<String, Any?>)

companion object {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,18 @@ internal class SdkInternalLogger(
}
}

override fun logMetric(messageBuilder: () -> String, additionalProperties: Map<String, Any?>) {
val rumFeature = sdkCore?.getFeature(Feature.RUM_FEATURE_NAME) ?: return
val message = messageBuilder()
val telemetryEvent =
mapOf(
TYPE_KEY to "mobile_metric",
MESSAGE_KEY to message,
ADDITIONAL_PROPERTIES_KEY to additionalProperties
)
rumFeature.sendEvent(telemetryEvent)
}

// endregion

// region Internal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.datadog.android.api.feature.FeatureScope
import com.datadog.android.api.feature.FeatureSdkCore
import com.datadog.android.utils.forge.Configurator
import com.datadog.tools.unit.forge.aThrowable
import com.datadog.tools.unit.forge.exhaustiveAttributes
import fr.xgouchet.elmyr.Forge
import fr.xgouchet.elmyr.annotation.IntForgery
import fr.xgouchet.elmyr.annotation.StringForgery
Expand All @@ -23,6 +24,7 @@ import fr.xgouchet.elmyr.junit5.ForgeExtension
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.extension.Extensions
import org.mockito.Mock
Expand Down Expand Up @@ -444,6 +446,53 @@ internal class SdkInternalLoggerTest {
)
}

@Test
fun `𝕄 send metric 𝕎 metric()`(
@StringForgery fakeMessage: String,
forge: Forge
) {
// Given
val mockRumFeatureScope = mock<FeatureScope>()
whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mockRumFeatureScope
val fakeAdditionalProperties = forge.exhaustiveAttributes()
val mockLambda: () -> String = mock()
whenever(mockLambda.invoke()) doReturn fakeMessage
// When
testedInternalLogger.logMetric(
mockLambda,
fakeAdditionalProperties
)

// Then
verify(mockRumFeatureScope)
.sendEvent(
mapOf(
"type" to "mobile_metric",
"message" to fakeMessage,
"additionalProperties" to fakeAdditionalProperties
)
)
}

@Test
fun `𝕄 do nothing metric 𝕎 metric { rum feature not initialized }`(
@StringForgery fakeMessage: String,
forge: Forge
) {
// Given
whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn null
val fakeAdditionalProperties = forge.exhaustiveAttributes()
val mockLambda: () -> String = mock()
whenever(mockLambda.invoke()) doReturn fakeMessage
// When
assertDoesNotThrow {
testedInternalLogger.logMetric(
mockLambda,
fakeAdditionalProperties
)
}
}

private fun InternalLogger.Level.toLogLevel(): Int {
return when (this) {
InternalLogger.Level.VERBOSE -> Log.VERBOSE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,18 +247,20 @@ internal class RumFeature constructor(
}

when (event["type"]) {
"jvm_crash" -> addJvmCrash(event)
"ndk_crash" -> ndkCrashEventHandler.handleEvent(event, sdkCore, dataWriter)
"logger_error" -> addLoggerError(event)
"logger_error_with_stacktrace" -> addLoggerErrorWithStacktrace(event)
"web_view_ingested_notification" -> {
JVM_CRASH_BUS_MESSAGE_TYPE -> addJvmCrash(event)
NDK_CRASH_BUS_MESSAGE_TYPE ->
ndkCrashEventHandler.handleEvent(event, sdkCore, dataWriter)
LOGGER_ERROR_BUS_MESSAGE_TYPE -> addLoggerError(event)
LOGGER_ERROR_WITH_STACK_TRACE_MESSAGE_TYPE -> addLoggerErrorWithStacktrace(event)
WEB_VIEW_INGESTED_NOTIFICATION_MESSAGE_TYPE -> {
(GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor)?.sendWebViewEvent()
}

"telemetry_error" -> logTelemetryError(event)
"telemetry_debug" -> logTelemetryDebug(event)
"telemetry_configuration" -> logTelemetryConfiguration(event)
"flush_and_stop_monitor" -> {
TELEMETRY_ERROR_MESSAGE_TYPE -> logTelemetryError(event)
TELEMETRY_DEBUG_MESSAGE_TYPE -> logTelemetryDebug(event)
MOBILE_METRIC_MESSAGE_TYPE -> logMetric(event)
TELEMETRY_CONFIG_MESSAGE_TYPE -> logTelemetryConfiguration(event)
FLUSH_AND_STOP_MONITOR_MESSAGE_TYPE -> {
(GlobalRumMonitor.get(sdkCore) as? DatadogRumMonitor)?.let {
it.stopKeepAliveCallback()
it.drainExecutorService()
Expand Down Expand Up @@ -493,6 +495,22 @@ internal class RumFeature constructor(
telemetry.debug(message, additionalProperties)
}

private fun logMetric(metricEvent: Map<*, *>) {
val message = metricEvent[EVENT_MESSAGE_PROPERTY] as? String

@Suppress("UNCHECKED_CAST")
val additionalProperties = metricEvent[EVENT_ADDITIONAL_PROPERTIES] as? Map<String, Any?>
if (message == null) {
sdkCore.internalLogger.log(
InternalLogger.Level.WARN,
InternalLogger.Target.MAINTAINER,
{ TELEMETRY_MISSING_MESSAGE_FIELD }
)
return
}
telemetry.metric(message, additionalProperties)
}

private fun logTelemetryConfiguration(event: Map<*, *>) {
TelemetryCoreConfiguration.fromEvent(event, sdkCore.internalLogger)?.let {
(GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor)
Expand Down Expand Up @@ -527,6 +545,17 @@ internal class RumFeature constructor(

internal companion object {

internal const val JVM_CRASH_BUS_MESSAGE_TYPE = "jvm_crash"
internal const val NDK_CRASH_BUS_MESSAGE_TYPE = "ndk_crash"
internal const val LOGGER_ERROR_BUS_MESSAGE_TYPE = "logger_error"
internal const val LOGGER_ERROR_WITH_STACK_TRACE_MESSAGE_TYPE = "logger_error_with_stacktrace"
internal const val WEB_VIEW_INGESTED_NOTIFICATION_MESSAGE_TYPE = "web_view_ingested_notification"
internal const val TELEMETRY_ERROR_MESSAGE_TYPE = "telemetry_error"
internal const val TELEMETRY_DEBUG_MESSAGE_TYPE = "telemetry_debug"
internal const val MOBILE_METRIC_MESSAGE_TYPE = "mobile_metric"
internal const val TELEMETRY_CONFIG_MESSAGE_TYPE = "telemetry_configuration"
internal const val FLUSH_AND_STOP_MONITOR_MESSAGE_TYPE = "flush_and_stop_monitor"

internal const val ALL_IN_SAMPLE_RATE: Float = 100f
internal const val DEFAULT_SAMPLE_RATE: Float = 100f
internal const val DEFAULT_TELEMETRY_SAMPLE_RATE: Float = 20f
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ internal sealed class RumRawEvent {
val kind: String?,
val coreConfiguration: TelemetryCoreConfiguration?,
val additionalProperties: Map<String, Any?>?,
override val eventTime: Time = Time()
val onlyOnce: Boolean = false,
override val eventTime: Time = Time(),
val isMetric: Boolean = false
) : RumRawEvent()
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ internal interface AdvancedRumMonitor : RumMonitor, AdvancedNetworkRumMonitor {
kind: String?
)

fun sendMetricEvent(
message: String,
additionalProperties: Map<String, Any?>?
)

@Suppress("FunctionMaxLength")
fun sendConfigurationTelemetryEvent(coreConfiguration: TelemetryCoreConfiguration)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,20 @@ internal class DatadogRumMonitor(
)
}

override fun sendMetricEvent(message: String, additionalProperties: Map<String, Any?>?) {
handleEvent(
RumRawEvent.SendTelemetry(
type = TelemetryType.DEBUG,
message = message,
stack = null,
kind = null,
coreConfiguration = null,
additionalProperties = additionalProperties,
isMetric = true
)
)
}

override fun sendErrorTelemetryEvent(
message: String,
throwable: Throwable?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@ internal class Telemetry(
(GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor)
?.sendDebugTelemetryEvent(message, additionalProperties)
}
fun metric(message: String, additionalProperties: Map<String, Any?>? = null) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
fun metric(message: String, additionalProperties: Map<String, Any?>? = null) {
fun metric(message: String, additionalProperties: Map<String, Any?>? = null) {

(GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor)
?.sendMetricEvent(message, additionalProperties)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ internal class TelemetryEventHandler(

val eventIdentity = event.identity

if (seenInCurrentSession.contains(eventIdentity)) {
if (!event.isMetric && seenInCurrentSession.contains(eventIdentity)) {
Copy link
Member

Choose a reason for hiding this comment

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

do we have a risk of sending too much metrics by disabling this check and thus adding a lot of pressure on the storage and also on the Datadog servers?

Copy link
Member Author

Choose a reason for hiding this comment

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

I was following what was done in iOS here. I guess the risk was assessed already there, in the end we are going to have an extra sampler in the MetricsDispatcher which will come in the next PR. We have a 15% extra sampler there.

sdkCore.internalLogger.log(
InternalLogger.Level.INFO,
InternalLogger.Target.MAINTAINER,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,69 @@ internal class RumFeatureTest {
verifyNoInteractions(mockInternalLogger)
}

@Test
fun `𝕄 handle metric event 𝕎 onReceive()`(
@StringForgery fakeMessage: String,
forge: Forge
) {
// Given
val fakeAdditionalProperties = forge.exhaustiveAttributes()
testedFeature.onInitialize(appContext.mockInstance)
val event = mapOf(
"type" to RumFeature.MOBILE_METRIC_MESSAGE_TYPE,
"message" to fakeMessage,
"additionalProperties" to fakeAdditionalProperties
)

// When
testedFeature.onReceive(event)

// Then
verify(mockRumMonitor).sendMetricEvent(fakeMessage, fakeAdditionalProperties)
verifyNoMoreInteractions(mockRumMonitor)
verifyNoInteractions(mockInternalLogger)
}

@Test
fun `𝕄 handle metric event 𝕎 onReceive(){no additionalProperties}`(
Copy link
Member

Choose a reason for hiding this comment

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

probably test with missing message is missing

@StringForgery fakeMessage: String
) {
// Given
testedFeature.onInitialize(appContext.mockInstance)
val event = mapOf(
"type" to RumFeature.MOBILE_METRIC_MESSAGE_TYPE,
"message" to fakeMessage
)

// When
testedFeature.onReceive(event)

// Then
verify(mockRumMonitor).sendMetricEvent(fakeMessage, null)
verifyNoMoreInteractions(mockRumMonitor)
verifyNoInteractions(mockInternalLogger)
}

@Test
fun `𝕄 handle metric event 𝕎 onReceive(){no message}`() {
// Given
testedFeature.onInitialize(appContext.mockInstance)
val event = mapOf(
"type" to RumFeature.MOBILE_METRIC_MESSAGE_TYPE
)

// When
testedFeature.onReceive(event)

// Then
mockInternalLogger.verifyLog(
InternalLogger.Level.WARN,
InternalLogger.Target.MAINTAINER,
RumFeature.TELEMETRY_MISSING_MESSAGE_FIELD
)
verifyNoInteractions(mockRumMonitor)
}

@Test
fun `𝕄 log warning 𝕎 onReceive() { telemetry error + message is missing }`() {
// Given
Expand Down
Loading