diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/envelope/session/SessionPayloadSourceImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/envelope/session/SessionPayloadSourceImpl.kt index 10be94e20..a775a83aa 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/envelope/session/SessionPayloadSourceImpl.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/envelope/session/SessionPayloadSourceImpl.kt @@ -1,7 +1,13 @@ package io.embrace.android.embracesdk.capture.envelope.session import io.embrace.android.embracesdk.anr.ndk.NativeThreadSamplerService +import io.embrace.android.embracesdk.arch.schema.AppTerminationCause import io.embrace.android.embracesdk.internal.payload.SessionPayload +import io.embrace.android.embracesdk.internal.payload.Span +import io.embrace.android.embracesdk.internal.payload.toNewPayload +import io.embrace.android.embracesdk.internal.spans.CurrentSessionSpan +import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData +import io.embrace.android.embracesdk.internal.spans.SpanSink import io.embrace.android.embracesdk.logging.InternalErrorService import io.embrace.android.embracesdk.session.captureDataSafely import io.embrace.android.embracesdk.session.orchestrator.SessionSnapshotType @@ -9,12 +15,24 @@ import io.embrace.android.embracesdk.session.orchestrator.SessionSnapshotType internal class SessionPayloadSourceImpl( private val internalErrorService: InternalErrorService, private val nativeThreadSamplerService: NativeThreadSamplerService?, + private val spanSink: SpanSink, + private val currentSessionSpan: CurrentSessionSpan ) : SessionPayloadSource { - override fun getSessionPayload(endType: SessionSnapshotType) = SessionPayload( - spans = null, - spanSnapshots = null, - internalError = captureDataSafely { internalErrorService.currentExceptionError?.toNewPayload() }, - sharedLibSymbolMapping = captureDataSafely { nativeThreadSamplerService?.getNativeSymbols() } - ) + override fun getSessionPayload(endType: SessionSnapshotType): SessionPayload { + return SessionPayload( + spans = retrieveSpanData(endType), + spanSnapshots = null, + internalError = captureDataSafely { internalErrorService.currentExceptionError?.toNewPayload() }, + sharedLibSymbolMapping = captureDataSafely { nativeThreadSamplerService?.getNativeSymbols() } + ) + } + + private fun retrieveSpanData(endType: SessionSnapshotType): List? = captureDataSafely { + when (endType) { + SessionSnapshotType.NORMAL_END -> currentSessionSpan.endSession(null) + SessionSnapshotType.PERIODIC_CACHE -> spanSink.completedSpans() + SessionSnapshotType.JVM_CRASH -> currentSessionSpan.endSession(AppTerminationCause.Crash) + }.map(EmbraceSpanData::toNewPayload) + } } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/payload/SpanMapper.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/payload/SpanMapper.kt index 42f05b720..144e841c2 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/payload/SpanMapper.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/payload/SpanMapper.kt @@ -1,32 +1,31 @@ package io.embrace.android.embracesdk.internal.payload -import io.opentelemetry.api.common.Attributes +import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData +import io.embrace.android.embracesdk.spans.EmbraceSpanEvent import io.opentelemetry.api.trace.StatusCode -import io.opentelemetry.sdk.trace.data.EventData -import io.opentelemetry.sdk.trace.data.SpanData -internal fun SpanData.toNewPayload() = Span( +internal fun EmbraceSpanData.toNewPayload() = Span( traceId = traceId, spanId = spanId, parentSpanId = parentSpanId, name = name, - startTimeUnixNano = startEpochNanos, - endTimeUnixNano = endEpochNanos, - status = when (status.statusCode) { + startTimeUnixNano = startTimeNanos, + endTimeUnixNano = endTimeNanos, + status = when (status) { StatusCode.UNSET -> Span.Status.UNSET StatusCode.OK -> Span.Status.OK StatusCode.ERROR -> Span.Status.ERROR else -> Span.Status.UNSET }, - events = events.map(EventData::toNewPayload), + events = events.map(EmbraceSpanEvent::toNewPayload), attributes = attributes.toNewPayload() ) -internal fun EventData.toNewPayload() = SpanEvent( +internal fun EmbraceSpanEvent.toNewPayload() = SpanEvent( name = name, - timeUnixNano = epochNanos, + timeUnixNano = timestampNanos, attributes = attributes.toNewPayload() ) -internal fun Attributes.toNewPayload(): List = - asMap().map { (key, value) -> Attribute(key.key, value.toString()) } +internal fun Map.toNewPayload(): List = + map { (key, value) -> Attribute(key, value) } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/envelope/SessionEnvelopeSourceTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/envelope/SessionEnvelopeSourceTest.kt index 930d6466e..995788b27 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/envelope/SessionEnvelopeSourceTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/envelope/SessionEnvelopeSourceTest.kt @@ -1,8 +1,10 @@ package io.embrace.android.embracesdk.capture.envelope import io.embrace.android.embracesdk.capture.envelope.session.SessionPayloadSourceImpl +import io.embrace.android.embracesdk.fakes.FakeCurrentSessionSpan import io.embrace.android.embracesdk.fakes.FakeInternalErrorService import io.embrace.android.embracesdk.fakes.FakeNativeThreadSamplerService +import io.embrace.android.embracesdk.internal.spans.SpanSinkImpl import io.embrace.android.embracesdk.session.orchestrator.SessionSnapshotType import org.junit.Test @@ -13,7 +15,9 @@ internal class SessionEnvelopeSourceTest { SessionEnvelopeSource( SessionPayloadSourceImpl( FakeInternalErrorService(), - FakeNativeThreadSamplerService() + FakeNativeThreadSamplerService(), + SpanSinkImpl(), + FakeCurrentSessionSpan() ) ).getEnvelope(SessionSnapshotType.NORMAL_END) } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/envelope/session/SessionPayloadSourceImplTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/envelope/session/SessionPayloadSourceImplTest.kt index c3ad2c53b..71422798e 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/envelope/session/SessionPayloadSourceImplTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/envelope/session/SessionPayloadSourceImplTest.kt @@ -1,18 +1,26 @@ package io.embrace.android.embracesdk.capture.envelope.session import io.embrace.android.embracesdk.fakes.FakeClock +import io.embrace.android.embracesdk.fakes.FakeCurrentSessionSpan import io.embrace.android.embracesdk.fakes.FakeInternalErrorService import io.embrace.android.embracesdk.fakes.FakeNativeThreadSamplerService +import io.embrace.android.embracesdk.fakes.FakeSpanData import io.embrace.android.embracesdk.internal.payload.SessionPayload +import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData +import io.embrace.android.embracesdk.internal.spans.SpanSinkImpl import io.embrace.android.embracesdk.payload.LegacyExceptionError import io.embrace.android.embracesdk.session.orchestrator.SessionSnapshotType import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull import org.junit.Before import org.junit.Test internal class SessionPayloadSourceImplTest { private lateinit var impl: SessionPayloadSourceImpl + private lateinit var sink: SpanSinkImpl + private lateinit var currentSessionSpan: FakeCurrentSessionSpan + private val cacheSpan = FakeSpanData(name = "cache-span") @Before fun setUp() { @@ -21,9 +29,17 @@ internal class SessionPayloadSourceImplTest { addException(RuntimeException(), "test", FakeClock()) } } + sink = SpanSinkImpl().apply { + storeCompletedSpans(listOf(cacheSpan)) + } + currentSessionSpan = FakeCurrentSessionSpan().apply { + spanData = listOf(EmbraceSpanData(FakeSpanData("my-span"))) + } impl = SessionPayloadSourceImpl( errorService, - FakeNativeThreadSamplerService() + FakeNativeThreadSamplerService(), + sink, + currentSessionSpan ) } @@ -31,23 +47,30 @@ internal class SessionPayloadSourceImplTest { fun `session crash`() { val payload = impl.getSessionPayload(SessionSnapshotType.JVM_CRASH) assertPayloadPopulated(payload) + val span = checkNotNull(payload.spans?.single()) + assertEquals("my-span", span.name) } @Test fun `session cache`() { val payload = impl.getSessionPayload(SessionSnapshotType.PERIODIC_CACHE) assertPayloadPopulated(payload) + val span = checkNotNull(payload.spans?.single()) + assertEquals("cache-span", span.name) } @Test fun `session lifecycle change`() { val payload = impl.getSessionPayload(SessionSnapshotType.NORMAL_END) assertPayloadPopulated(payload) + val span = checkNotNull(payload.spans?.single()) + assertEquals("my-span", span.name) } private fun assertPayloadPopulated(payload: SessionPayload) { val err = checkNotNull(payload.internalError) assertEquals(1, err.count) assertEquals(mapOf("armeabi-v7a" to "my-symbols"), payload.sharedLibSymbolMapping) + assertNull(payload.spanSnapshots) } } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeCurrentSessionSpan.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeCurrentSessionSpan.kt index 98739a5fc..a87ef46a5 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeCurrentSessionSpan.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeCurrentSessionSpan.kt @@ -12,6 +12,7 @@ internal class FakeCurrentSessionSpan : CurrentSessionSpan { var initializedCallCount = 0 var addedEvents = mutableListOf() var addedAttributes = mutableListOf() + var spanData = listOf() override fun initializeService(sdkInitStartTimeMs: Long) { } @@ -32,7 +33,7 @@ internal class FakeCurrentSessionSpan : CurrentSessionSpan { } override fun endSession(appTerminationCause: AppTerminationCause?): List { - return emptyList() + return spanData } override fun canStartNewSpan(parent: EmbraceSpan?, internal: Boolean): Boolean { diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/payload/SpanMapperTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/payload/SpanMapperTest.kt index b709b2aa1..ad46f0958 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/payload/SpanMapperTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/payload/SpanMapperTest.kt @@ -1,7 +1,7 @@ package io.embrace.android.embracesdk.internal.payload import io.embrace.android.embracesdk.fakes.FakeSpanData -import io.opentelemetry.sdk.trace.data.SpanData +import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData import org.junit.Assert.assertEquals import org.junit.Test @@ -9,33 +9,33 @@ internal class SpanMapperTest { @Test fun toSpan() { - val input: SpanData = FakeSpanData() + val input = EmbraceSpanData(FakeSpanData()) val output = input.toNewPayload() assertEquals(input.traceId, output.traceId) assertEquals(input.spanId, output.spanId) assertEquals(input.parentSpanId, output.parentSpanId) assertEquals(input.name, output.name) - assertEquals(input.startEpochNanos, output.startTimeUnixNano) - assertEquals(input.endEpochNanos, output.endTimeUnixNano) - assertEquals(input.status.statusCode.name, checkNotNull(output.status).name) + assertEquals(input.startTimeNanos, output.startTimeUnixNano) + assertEquals(input.endTimeNanos, output.endTimeUnixNano) + assertEquals(input.status.name, checkNotNull(output.status).name) // validate event copied val inputEvent = input.events.single() val outputEvent = checkNotNull(output.events).single() assertEquals(inputEvent.name, outputEvent.name) - assertEquals(inputEvent.epochNanos, outputEvent.timeUnixNano) + assertEquals(inputEvent.timestampNanos, outputEvent.timeUnixNano) // test event attributes - val inputEventAttrs = checkNotNull(inputEvent.attributes?.asMap()) + val inputEventAttrs = checkNotNull(inputEvent.attributes) val outputEventAttrs = checkNotNull(outputEvent.attributes?.single()) - assertEquals(inputEventAttrs.keys.single().key, outputEventAttrs.key) + assertEquals(inputEventAttrs.keys.single(), outputEventAttrs.key) assertEquals(inputEventAttrs.values.single(), outputEventAttrs.data) // test attributes - val inputAttrs = checkNotNull(input.attributes?.asMap()) + val inputAttrs = checkNotNull(input.attributes) val outputAttrs = checkNotNull(output.attributes?.single()) - assertEquals(inputAttrs.keys.single().key, outputAttrs.key) + assertEquals(inputAttrs.keys.single(), outputAttrs.key) assertEquals(inputAttrs.values.single(), outputAttrs.data) } }