diff --git a/features/dd-sdk-android-trace/api/apiSurface b/features/dd-sdk-android-trace/api/apiSurface index 97be68530d..b190f744b8 100644 --- a/features/dd-sdk-android-trace/api/apiSurface +++ b/features/dd-sdk-android-trace/api/apiSurface @@ -48,7 +48,7 @@ data class com.datadog.android.trace.model.SpanEvent fun fromJson(kotlin.String): Meta fun fromJsonObject(com.google.gson.JsonObject): Meta data class Dd - constructor(kotlin.String? = "android") + constructor(kotlin.String? = "android", Application? = null, Session? = null, View? = null) fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Dd @@ -74,6 +74,24 @@ data class com.datadog.android.trace.model.SpanEvent companion object fun fromJson(kotlin.String): Network fun fromJsonObject(com.google.gson.JsonObject): Network + data class Application + constructor(kotlin.String? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Application + fun fromJsonObject(com.google.gson.JsonObject): Application + data class Session + constructor(kotlin.String? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Session + fun fromJsonObject(com.google.gson.JsonObject): Session + data class View + constructor(kotlin.String? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): View + fun fromJsonObject(com.google.gson.JsonObject): View data class Client constructor(SimCarrier? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null) fun toJson(): com.google.gson.JsonElement diff --git a/features/dd-sdk-android-trace/api/dd-sdk-android-trace.api b/features/dd-sdk-android-trace/api/dd-sdk-android-trace.api index 122a015efa..a6636c15d7 100644 --- a/features/dd-sdk-android-trace/api/dd-sdk-android-trace.api +++ b/features/dd-sdk-android-trace/api/dd-sdk-android-trace.api @@ -99,6 +99,28 @@ public final class com/datadog/android/trace/model/SpanEvent { public fun toString ()Ljava/lang/String; } +public final class com/datadog/android/trace/model/SpanEvent$Application { + public static final field Companion Lcom/datadog/android/trace/model/SpanEvent$Application$Companion; + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/trace/model/SpanEvent$Application; + public static synthetic fun copy$default (Lcom/datadog/android/trace/model/SpanEvent$Application;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/trace/model/SpanEvent$Application; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/trace/model/SpanEvent$Application; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/trace/model/SpanEvent$Application; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/trace/model/SpanEvent$Application$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/trace/model/SpanEvent$Application; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/trace/model/SpanEvent$Application; +} + public final class com/datadog/android/trace/model/SpanEvent$Client { public static final field Companion Lcom/datadog/android/trace/model/SpanEvent$Client$Companion; public fun ()V @@ -137,15 +159,21 @@ public final class com/datadog/android/trace/model/SpanEvent$Companion { public final class com/datadog/android/trace/model/SpanEvent$Dd { public static final field Companion Lcom/datadog/android/trace/model/SpanEvent$Dd$Companion; public fun ()V - public fun (Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Lcom/datadog/android/trace/model/SpanEvent$Application;Lcom/datadog/android/trace/model/SpanEvent$Session;Lcom/datadog/android/trace/model/SpanEvent$View;)V + public synthetic fun (Ljava/lang/String;Lcom/datadog/android/trace/model/SpanEvent$Application;Lcom/datadog/android/trace/model/SpanEvent$Session;Lcom/datadog/android/trace/model/SpanEvent$View;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lcom/datadog/android/trace/model/SpanEvent$Dd; - public static synthetic fun copy$default (Lcom/datadog/android/trace/model/SpanEvent$Dd;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/trace/model/SpanEvent$Dd; + public final fun component2 ()Lcom/datadog/android/trace/model/SpanEvent$Application; + public final fun component3 ()Lcom/datadog/android/trace/model/SpanEvent$Session; + public final fun component4 ()Lcom/datadog/android/trace/model/SpanEvent$View; + public final fun copy (Ljava/lang/String;Lcom/datadog/android/trace/model/SpanEvent$Application;Lcom/datadog/android/trace/model/SpanEvent$Session;Lcom/datadog/android/trace/model/SpanEvent$View;)Lcom/datadog/android/trace/model/SpanEvent$Dd; + public static synthetic fun copy$default (Lcom/datadog/android/trace/model/SpanEvent$Dd;Ljava/lang/String;Lcom/datadog/android/trace/model/SpanEvent$Application;Lcom/datadog/android/trace/model/SpanEvent$Session;Lcom/datadog/android/trace/model/SpanEvent$View;ILjava/lang/Object;)Lcom/datadog/android/trace/model/SpanEvent$Dd; public fun equals (Ljava/lang/Object;)Z public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/trace/model/SpanEvent$Dd; public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/trace/model/SpanEvent$Dd; + public final fun getApplication ()Lcom/datadog/android/trace/model/SpanEvent$Application; + public final fun getSession ()Lcom/datadog/android/trace/model/SpanEvent$Session; public final fun getSource ()Ljava/lang/String; + public final fun getView ()Lcom/datadog/android/trace/model/SpanEvent$View; public fun hashCode ()I public final fun toJson ()Lcom/google/gson/JsonElement; public fun toString ()Ljava/lang/String; @@ -235,6 +263,28 @@ public final class com/datadog/android/trace/model/SpanEvent$Network$Companion { public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/trace/model/SpanEvent$Network; } +public final class com/datadog/android/trace/model/SpanEvent$Session { + public static final field Companion Lcom/datadog/android/trace/model/SpanEvent$Session$Companion; + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/trace/model/SpanEvent$Session; + public static synthetic fun copy$default (Lcom/datadog/android/trace/model/SpanEvent$Session;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/trace/model/SpanEvent$Session; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/trace/model/SpanEvent$Session; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/trace/model/SpanEvent$Session; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/trace/model/SpanEvent$Session$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/trace/model/SpanEvent$Session; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/trace/model/SpanEvent$Session; +} + public final class com/datadog/android/trace/model/SpanEvent$SimCarrier { public static final field Companion Lcom/datadog/android/trace/model/SpanEvent$SimCarrier$Companion; public fun ()V @@ -313,6 +363,28 @@ public final class com/datadog/android/trace/model/SpanEvent$Usr$Companion { public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/trace/model/SpanEvent$Usr; } +public final class com/datadog/android/trace/model/SpanEvent$View { + public static final field Companion Lcom/datadog/android/trace/model/SpanEvent$View$Companion; + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/trace/model/SpanEvent$View; + public static synthetic fun copy$default (Lcom/datadog/android/trace/model/SpanEvent$View;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/trace/model/SpanEvent$View; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/trace/model/SpanEvent$View; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/trace/model/SpanEvent$View; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/trace/model/SpanEvent$View$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/trace/model/SpanEvent$View; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/trace/model/SpanEvent$View; +} + public final class com/datadog/android/trace/sqlite/SqliteDatabaseExtKt { public static final fun transactionTraced (Landroid/database/sqlite/SQLiteDatabase;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Ljava/lang/Object; public static synthetic fun transactionTraced$default (Landroid/database/sqlite/SQLiteDatabase;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/lang/Object; diff --git a/features/dd-sdk-android-trace/src/main/json/trace/span-schema.json b/features/dd-sdk-android-trace/src/main/json/trace/span-schema.json index 074256f3be..49975d2b46 100644 --- a/features/dd-sdk-android-trace/src/main/json/trace/span-schema.json +++ b/features/dd-sdk-android-trace/src/main/json/trace/span-schema.json @@ -104,6 +104,39 @@ "description": "The trace source", "default": "android", "readOnly": true + }, + "application": { + "type": "object", + "description": "The RUM Application attributes", + "properties": { + "id": { + "type": "string", + "description": "RUM Application ID", + "readOnly": true + } + } + }, + "session": { + "type": "object", + "description": "The active RUM Session attributes", + "properties": { + "id": { + "type": "string", + "description": "The RUM Session ID", + "readOnly": true + } + } + }, + "view": { + "type": "object", + "description": "The active RUM View attributes", + "properties": { + "id": { + "type": "string", + "description": "The RUM View ID", + "readOnly": true + } + } } }, "readOnly": true diff --git a/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/domain/event/DdSpanToSpanEventMapper.kt b/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/domain/event/DdSpanToSpanEventMapper.kt index 8db7ae9b9c..5a6f5783d0 100644 --- a/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/domain/event/DdSpanToSpanEventMapper.kt +++ b/features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/domain/event/DdSpanToSpanEventMapper.kt @@ -9,6 +9,7 @@ package com.datadog.android.trace.internal.domain.event import com.datadog.android.api.context.DatadogContext import com.datadog.android.api.context.NetworkInfo import com.datadog.android.core.internal.utils.toHexString +import com.datadog.android.log.LogAttributes import com.datadog.android.trace.model.SpanEvent import com.datadog.opentracing.DDSpan @@ -68,9 +69,15 @@ internal class DdSpanToSpanEventMapper( email = userInfo.email, additionalProperties = userInfo.additionalProperties.toMutableMap() ) + val dd = SpanEvent.Dd( + source = datadogContext.source, + application = event.tags[LogAttributes.RUM_APPLICATION_ID]?.let { SpanEvent.Application(it as? String) }, + session = event.tags[LogAttributes.RUM_SESSION_ID]?.let { SpanEvent.Session(it as? String) }, + view = event.tags[LogAttributes.RUM_VIEW_ID]?.let { SpanEvent.View(it as? String) } + ) return SpanEvent.Meta( version = datadogContext.version, - dd = SpanEvent.Dd(source = datadogContext.source), + dd = dd, span = SpanEvent.Span(), tracer = SpanEvent.Tracer( version = datadogContext.sdkVersion diff --git a/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/assertj/SpanEventAssert.kt b/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/assertj/SpanEventAssert.kt index 0b3a476fa5..79f88d04cf 100644 --- a/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/assertj/SpanEventAssert.kt +++ b/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/assertj/SpanEventAssert.kt @@ -93,13 +93,43 @@ internal class SpanEventAssert(actual: SpanEvent) : fun hasSpanSource(spanSource: String): SpanEventAssert { assertThat(actual.meta.dd.source) .overridingErrorMessage( - "Expected SpanEvent to have source: $spanSource" + + "Expected SpanEvent to have _dd.source: $spanSource" + " but instead was: ${actual.meta.dd.source}" ) .isEqualTo(spanSource) return this } + fun hasApplicationId(applicationId: String?): SpanEventAssert { + assertThat(actual.meta.dd.application?.id) + .overridingErrorMessage( + "Expected SpanEvent to have _dd.application.id: $applicationId" + + " but instead was: ${actual.meta.dd.application?.id}" + ) + .isEqualTo(applicationId) + return this + } + + fun hasSessionId(sessionId: String?): SpanEventAssert { + assertThat(actual.meta.dd.session?.id) + .overridingErrorMessage( + "Expected SpanEvent to have _dd.session.id: $sessionId" + + " but instead was: ${actual.meta.dd.session?.id}" + ) + .isEqualTo(sessionId) + return this + } + + fun hasViewId(viewId: String?): SpanEventAssert { + assertThat(actual.meta.dd.view?.id) + .overridingErrorMessage( + "Expected SpanEvent to have _dd.view.id: $viewId" + + " but instead was: ${actual.meta.dd.view?.id}" + ) + .isEqualTo(viewId) + return this + } + fun hasSpanStartTime(startTime: Long): SpanEventAssert { assertThat(actual.start) .overridingErrorMessage( diff --git a/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/domain/event/DdSpanToSpanEventMapperTest.kt b/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/domain/event/DdSpanToSpanEventMapperTest.kt index 91b0b7c77e..0c1b5d8a5f 100644 --- a/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/domain/event/DdSpanToSpanEventMapperTest.kt +++ b/features/dd-sdk-android-trace/src/test/kotlin/com/datadog/android/trace/internal/domain/event/DdSpanToSpanEventMapperTest.kt @@ -8,6 +8,7 @@ package com.datadog.android.trace.internal.domain.event import com.datadog.android.api.context.DatadogContext import com.datadog.android.core.internal.utils.toHexString +import com.datadog.android.log.LogAttributes import com.datadog.android.trace.assertj.SpanEventAssert.Companion.assertThat import com.datadog.android.utils.forge.Configurator import com.datadog.opentracing.DDSpan @@ -15,6 +16,7 @@ import com.datadog.tools.unit.setFieldValue import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.Forgery +import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.junit.jupiter.api.BeforeEach @@ -49,13 +51,13 @@ internal class DdSpanToSpanEventMapperTest { } @RepeatedTest(4) - fun `M map a DdSpan to a SpanEvent W map`( + fun `M map a DdSpan to a SpanEvent W map()`( @Forgery fakeSpan: DDSpan ) { - // WHEN + // When val event = testedMapper.map(fakeDatadogContext, fakeSpan) - // THEN + // Then assertThat(event) .hasSpanId(fakeSpan.spanId.toHexString()) .hasTraceId(fakeSpan.traceId.toHexString()) @@ -65,6 +67,54 @@ internal class DdSpanToSpanEventMapperTest { .hasResourceName(fakeSpan.resourceName) .hasSpanType("custom") .hasSpanSource(fakeDatadogContext.source) + .hasApplicationId(null) + .hasSessionId(null) + .hasViewId(null) + .hasErrorFlag(fakeSpan.error.toLong()) + .hasSpanStartTime(fakeSpan.startTime + fakeDatadogContext.time.serverTimeOffsetNs) + .hasSpanDuration(fakeSpan.durationNano) + .hasTracerVersion(fakeDatadogContext.sdkVersion) + .hasClientPackageVersion(fakeDatadogContext.version) + .apply { + if (fakeNetworkInfoEnabled) { + hasNetworkInfo(fakeDatadogContext.networkInfo) + } else { + doesntHaveNetworkInfo() + } + } + .hasUserInfo(fakeDatadogContext.userInfo) + .hasMeta(fakeSpan.meta) + .hasMetrics(fakeSpan.metrics) + } + + @RepeatedTest(4) + fun `M map a DdSpan to a SpanEvent with RUM info W map() {RUM info present}`( + @Forgery fakeSpan: DDSpan, + @StringForgery fakeApplicationId: String, + @StringForgery fakeSessionId: String, + @StringForgery fakeViewId: String + ) { + // Given + fakeSpan.setTag(LogAttributes.RUM_APPLICATION_ID, fakeApplicationId) + fakeSpan.setTag(LogAttributes.RUM_SESSION_ID, fakeSessionId) + fakeSpan.setTag(LogAttributes.RUM_VIEW_ID, fakeViewId) + + // When + val event = testedMapper.map(fakeDatadogContext, fakeSpan) + + // Then + assertThat(event) + .hasSpanId(fakeSpan.spanId.toHexString()) + .hasTraceId(fakeSpan.traceId.toHexString()) + .hasParentId(fakeSpan.parentId.toHexString()) + .hasServiceName(fakeSpan.serviceName) + .hasOperationName(fakeSpan.operationName) + .hasResourceName(fakeSpan.resourceName) + .hasSpanType("custom") + .hasSpanSource(fakeDatadogContext.source) + .hasApplicationId(fakeApplicationId) + .hasSessionId(fakeSessionId) + .hasViewId(fakeViewId) .hasErrorFlag(fakeSpan.error.toLong()) .hasSpanStartTime(fakeSpan.startTime + fakeDatadogContext.time.serverTimeOffsetNs) .hasSpanDuration(fakeSpan.durationNano) @@ -83,32 +133,32 @@ internal class DdSpanToSpanEventMapperTest { } @Test - fun `M mark the SpanEvent as top span W map { parentId is 0 }`( + fun `M mark the SpanEvent as top span W map() { parentId is 0 }`( @Forgery fakeSpan: DDSpan ) { - // GIVEN + // Given fakeSpan.setFieldValue("parentId", 0) - // WHEN + // When val event = testedMapper.map(fakeDatadogContext, fakeSpan) - // THEN + // Then assertThat(event) .isTopSpan() } @Test - fun `M not mark the SpanEvent as top span W map { parentId is different than 0 }`( + fun `M not mark the SpanEvent as top span W map() { parentId is different than 0 }`( forge: Forge, @Forgery fakeSpan: DDSpan ) { - // GIVEN + // Given fakeSpan.context().setFieldValue("parentId", BigInteger.valueOf(forge.aLong(min = 1))) - // WHEN + // When val event = testedMapper.map(fakeDatadogContext, fakeSpan) - // THEN + // Then assertThat(event) .isNotTopSpan() }