diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/injection/SessionModule.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/injection/SessionModule.kt index 65165fd7de..a2e9fcf157 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/injection/SessionModule.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/injection/SessionModule.kt @@ -69,8 +69,20 @@ internal class SessionModuleImpl( override val v2PayloadMessageCollator: V2PayloadMessageCollator by singleton { V2PayloadMessageCollator( essentialServiceModule.gatingService, - v1PayloadMessageCollator, payloadModule.sessionEnvelopeSource, + essentialServiceModule.metadataService, + dataContainerModule.eventService, + customerLogModule.logMessageService, + dataContainerModule.performanceInfoService, + nativeModule.nativeThreadSamplerService, + androidServicesModule.preferencesService, + openTelemetryModule.spanRepository, + openTelemetryModule.spanSink, + openTelemetryModule.currentSessionSpan, + sessionPropertiesService, + dataCaptureServiceModule.startupService, + anrModule.anrOtelMapper, + nativeModule.nativeAnrOtelMapper, initModule.logger ) } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/message/V2PayloadMessageCollator.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/message/V2PayloadMessageCollator.kt index 5a8c9e2b67..542c7a6b79 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/message/V2PayloadMessageCollator.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/message/V2PayloadMessageCollator.kt @@ -1,11 +1,29 @@ package io.embrace.android.embracesdk.session.message +import io.embrace.android.embracesdk.anr.AnrOtelMapper +import io.embrace.android.embracesdk.anr.ndk.NativeAnrOtelMapper +import io.embrace.android.embracesdk.anr.ndk.NativeThreadSamplerService +import io.embrace.android.embracesdk.arch.schema.AppTerminationCause +import io.embrace.android.embracesdk.capture.PerformanceInfoService import io.embrace.android.embracesdk.capture.envelope.session.SessionEnvelopeSource +import io.embrace.android.embracesdk.capture.metadata.MetadataService +import io.embrace.android.embracesdk.capture.startup.StartupService +import io.embrace.android.embracesdk.event.EventService +import io.embrace.android.embracesdk.event.LogMessageService import io.embrace.android.embracesdk.gating.GatingService +import io.embrace.android.embracesdk.internal.payload.Span +import io.embrace.android.embracesdk.internal.payload.toOldPayload +import io.embrace.android.embracesdk.internal.spans.CurrentSessionSpan +import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData +import io.embrace.android.embracesdk.internal.spans.SpanRepository +import io.embrace.android.embracesdk.internal.spans.SpanSink import io.embrace.android.embracesdk.logging.EmbLogger import io.embrace.android.embracesdk.payload.Session import io.embrace.android.embracesdk.payload.SessionMessage +import io.embrace.android.embracesdk.prefs.PreferencesService +import io.embrace.android.embracesdk.session.captureDataSafely import io.embrace.android.embracesdk.session.orchestrator.SessionSnapshotType +import io.embrace.android.embracesdk.session.properties.SessionPropertiesService /** * Generates a V2 payload. This currently calls through to the V1 collator for @@ -13,13 +31,36 @@ import io.embrace.android.embracesdk.session.orchestrator.SessionSnapshotType */ internal class V2PayloadMessageCollator( private val gatingService: GatingService, - private val v1Collator: V1PayloadMessageCollator, private val sessionEnvelopeSource: SessionEnvelopeSource, - private val logger: EmbLogger + private val metadataService: MetadataService, + private val eventService: EventService, + private val logMessageService: LogMessageService, + private val performanceInfoService: PerformanceInfoService, + private val nativeThreadSamplerService: NativeThreadSamplerService?, + private val preferencesService: PreferencesService, + private val spanRepository: SpanRepository, + private val spanSink: SpanSink, + private val currentSessionSpan: CurrentSessionSpan, + private val sessionPropertiesService: SessionPropertiesService, + private val startupService: StartupService, + private val anrOtelMapper: AnrOtelMapper, + private val nativeAnrOtelMapper: NativeAnrOtelMapper, + private val logger: EmbLogger, ) : PayloadMessageCollator { override fun buildInitialSession(params: InitialEnvelopeParams): Session { - return v1Collator.buildInitialSession(params) + return with(params) { + Session( + sessionId = currentSessionSpan.getSessionId(), + startTime = startTime, + isColdStart = coldStart, + messageType = Session.MESSAGE_TYPE_END, + appState = appState, + startType = startType, + number = getSessionNumber(preferencesService), + properties = getProperties(sessionPropertiesService), + ) + } } override fun buildFinalSessionMessage(params: FinalEnvelopeParams.SessionParams): SessionMessage { @@ -32,8 +73,31 @@ internal class V2PayloadMessageCollator( captureSpans = false, logger = logger ) - return v1Collator.buildFinalSessionMessage(newParams) - .convertToV2Payload(newParams.endType) + val obj = with(newParams) { + val base = buildFinalBackgroundActivity(newParams) + val startupInfo = getStartupEventInfo(eventService) + + val endSession = base.copy( + isEndedCleanly = endType.endedCleanly, + networkLogIds = captureDataSafely(logger) { + logMessageService.findNetworkLogIds( + initial.startTime, + endTime + ) + }, + properties = captureDataSafely(logger, sessionPropertiesService::getProperties), + terminationTime = terminationTime, + isReceivedTermination = receivedTermination, + endTime = endTimeVal, + sdkStartupDuration = startupService.getSdkStartupDuration(initial.isColdStart), + startupDuration = startupInfo?.duration, + startupThreshold = startupInfo?.threshold, + symbols = captureDataSafely(logger) { nativeThreadSamplerService?.getNativeSymbols() } + ) + val envelope = buildWrapperEnvelope(newParams, endSession, initial.startTime, endTime) + gatingService.gateSessionMessage(envelope) + } + return obj.convertToV2Payload(newParams.endType) } override fun buildFinalBackgroundActivityMessage(params: FinalEnvelopeParams.BackgroundActivityParams): SessionMessage { @@ -46,8 +110,12 @@ internal class V2PayloadMessageCollator( captureSpans = false, logger = logger ) - return v1Collator.buildFinalBackgroundActivityMessage(newParams) - .convertToV2Payload(newParams.endType) + val msg = buildFinalBackgroundActivity(newParams) + val startTime = msg.startTime + val endTime = newParams.endTime + val envelope = buildWrapperEnvelope(newParams, msg, startTime, endTime) + val obj = gatingService.gateSessionMessage(envelope) + return obj.convertToV2Payload(newParams.endType) } private fun SessionMessage.convertToV2Payload(endType: SessionSnapshotType): SessionMessage { @@ -61,12 +129,74 @@ internal class V2PayloadMessageCollator( type = envelope.type, // make legacy fields null - userInfo = null, version = null, spans = null, + ) + } + + /** + * Creates a background activity stop message. + */ + private fun buildFinalBackgroundActivity( + params: FinalEnvelopeParams + ): Session = with(params) { + return initial.copy( + endTime = endTime, + eventIds = captureDataSafely(logger) { + eventService.findEventIdsForSession() + }, + lastHeartbeatTime = endTime, + endType = lifeEventType, + crashReportId = crashId + ) + } + + private fun buildWrapperEnvelope( + params: FinalEnvelopeParams, + finalPayload: Session, + startTime: Long, + endTime: Long, + ): SessionMessage { + val spans: List? = captureDataSafely(logger) { + val result = when { + !params.captureSpans -> null + !params.isCacheAttempt -> { + val appTerminationCause = when { + finalPayload.crashReportId != null -> AppTerminationCause.Crash + else -> null + } + val spans = currentSessionSpan.endSession(appTerminationCause) + if (appTerminationCause == null) { + sessionPropertiesService.populateCurrentSession() + } + spans + } + + else -> spanSink.completedSpans() + } + // add ANR spans if the payload is capturing spans. + result?.plus(anrOtelMapper.snapshot(!params.isCacheAttempt).map(Span::toOldPayload)) + ?.plus(nativeAnrOtelMapper.snapshot(!params.isCacheAttempt).map(Span::toOldPayload)) + ?: result + } + val spanSnapshots = captureDataSafely(logger) { + spanRepository.getActiveSpans().mapNotNull { it.snapshot()?.toOldPayload() } + } - // future: make appInfo, deviceInfo, performanceInfo, breadcrumbs null. - // this is blocked until we can migrate others + return SessionMessage( + session = finalPayload, + appInfo = captureDataSafely(logger, metadataService::getAppInfo), + deviceInfo = captureDataSafely(logger, metadataService::getDeviceInfo), + performanceInfo = captureDataSafely(logger) { + performanceInfoService.getSessionPerformanceInfo( + startTime, + endTime, + finalPayload.isColdStart, + null + ) + }, + spans = spans, + spanSnapshots = spanSnapshots, ) } } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/PayloadFactoryBaTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/PayloadFactoryBaTest.kt index 1d57328bf4..a18ab4a193 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/PayloadFactoryBaTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/PayloadFactoryBaTest.kt @@ -182,7 +182,24 @@ internal class PayloadFactoryBaTest { resourceSource = FakeEnvelopeResourceSource(), sessionPayloadSource = FakeSessionPayloadSource() ) - val v2Collator = V2PayloadMessageCollator(gatingService, collator, sessionEnvelopeSource, logger) + val v2Collator = V2PayloadMessageCollator( + gatingService, + sessionEnvelopeSource, + metadataService, + eventService, + logMessageService, + performanceInfoService, + null, + preferencesService, + spanRepository, + spanSink, + currentSessionSpan, + FakeSessionPropertiesService(), + FakeStartupService(), + AnrOtelMapper(FakeAnrService()), + NativeAnrOtelMapper(null, EmbraceSerializer()), + logger + ) return PayloadFactoryImpl(collator, v2Collator, configService, logger).apply { if (createInitialSession) { startPayloadWithState(ProcessState.BACKGROUND, clock.now(), true) diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/PayloadFactorySessionTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/PayloadFactorySessionTest.kt index a2999697ff..2037902abd 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/PayloadFactorySessionTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/PayloadFactorySessionTest.kt @@ -1,17 +1,43 @@ package io.embrace.android.embracesdk.session import io.embrace.android.embracesdk.FakeDeliveryService +import io.embrace.android.embracesdk.FakeNdkService +import io.embrace.android.embracesdk.FakeSessionPropertiesService +import io.embrace.android.embracesdk.anr.AnrOtelMapper +import io.embrace.android.embracesdk.anr.ndk.NativeAnrOtelMapper import io.embrace.android.embracesdk.capture.envelope.session.SessionEnvelopeSourceImpl +import io.embrace.android.embracesdk.capture.metadata.MetadataService +import io.embrace.android.embracesdk.capture.user.UserService +import io.embrace.android.embracesdk.concurrency.BlockingScheduledExecutorService +import io.embrace.android.embracesdk.config.LocalConfigParser +import io.embrace.android.embracesdk.config.local.LocalConfig +import io.embrace.android.embracesdk.event.EventService +import io.embrace.android.embracesdk.event.LogMessageService +import io.embrace.android.embracesdk.fakes.FakeAnrService import io.embrace.android.embracesdk.fakes.FakeClock import io.embrace.android.embracesdk.fakes.FakeConfigService import io.embrace.android.embracesdk.fakes.FakeEnvelopeMetadataSource import io.embrace.android.embracesdk.fakes.FakeEnvelopeResourceSource +import io.embrace.android.embracesdk.fakes.FakeEventService import io.embrace.android.embracesdk.fakes.FakeGatingService +import io.embrace.android.embracesdk.fakes.FakeInternalErrorService +import io.embrace.android.embracesdk.fakes.FakeLogMessageService +import io.embrace.android.embracesdk.fakes.FakeMetadataService +import io.embrace.android.embracesdk.fakes.FakePerformanceInfoService +import io.embrace.android.embracesdk.fakes.FakePreferenceService import io.embrace.android.embracesdk.fakes.FakeProcessStateService +import io.embrace.android.embracesdk.fakes.FakeSessionIdTracker import io.embrace.android.embracesdk.fakes.FakeSessionPayloadSource +import io.embrace.android.embracesdk.fakes.FakeStartupService +import io.embrace.android.embracesdk.fakes.FakeUserService import io.embrace.android.embracesdk.fakes.injection.FakeInitModule +import io.embrace.android.embracesdk.internal.serialization.EmbraceSerializer +import io.embrace.android.embracesdk.internal.spans.CurrentSessionSpan +import io.embrace.android.embracesdk.internal.spans.SpanRepository +import io.embrace.android.embracesdk.internal.spans.SpanService import io.embrace.android.embracesdk.internal.spans.SpanSink import io.embrace.android.embracesdk.logging.EmbLoggerImpl +import io.embrace.android.embracesdk.logging.InternalErrorService import io.embrace.android.embracesdk.session.lifecycle.ProcessState import io.embrace.android.embracesdk.session.message.PayloadFactory import io.embrace.android.embracesdk.session.message.PayloadFactoryImpl @@ -36,6 +62,22 @@ internal class PayloadFactorySessionTest { private lateinit var service: PayloadFactory private lateinit var deliveryService: FakeDeliveryService private lateinit var configService: FakeConfigService + private lateinit var clock: FakeClock + private lateinit var performanceInfoService: FakePerformanceInfoService + private lateinit var metadataService: MetadataService + private lateinit var sessionIdTracker: FakeSessionIdTracker + private lateinit var activityService: FakeProcessStateService + private lateinit var eventService: EventService + private lateinit var logMessageService: LogMessageService + private lateinit var userService: UserService + private lateinit var internalErrorService: InternalErrorService + private lateinit var ndkService: FakeNdkService + private lateinit var localConfig: LocalConfig + private lateinit var spanRepository: SpanRepository + private lateinit var currentSessionSpan: CurrentSessionSpan + private lateinit var spanService: SpanService + private lateinit var preferencesService: FakePreferenceService + private lateinit var blockingExecutorService: BlockingScheduledExecutorService companion object { @@ -59,7 +101,33 @@ internal class PayloadFactorySessionTest { fun before() { deliveryService = FakeDeliveryService() configService = FakeConfigService() + clock = FakeClock(10000L) spanSink = FakeInitModule(clock = clock).openTelemetryModule.spanSink + + performanceInfoService = FakePerformanceInfoService() + metadataService = FakeMetadataService() + sessionIdTracker = FakeSessionIdTracker() + activityService = FakeProcessStateService(isInBackground = true) + eventService = FakeEventService() + logMessageService = FakeLogMessageService() + internalErrorService = FakeInternalErrorService() + ndkService = FakeNdkService() + preferencesService = FakePreferenceService(backgroundActivityEnabled = true) + userService = FakeUserService() + val initModule = FakeInitModule(clock = clock) + spanRepository = initModule.openTelemetryModule.spanRepository + currentSessionSpan = initModule.openTelemetryModule.currentSessionSpan + spanService = initModule.openTelemetryModule.spanService + configService.updateListeners() + localConfig = LocalConfigParser.buildConfig( + "GrCPU", + false, + "{\"background_activity\": {\"max_background_activity_seconds\": 3600}}", + EmbraceSerializer(), + EmbLoggerImpl() + ) + + blockingExecutorService = BlockingScheduledExecutorService(blockingMode = false) } @After @@ -100,7 +168,25 @@ internal class PayloadFactorySessionTest { ) val logger = EmbLoggerImpl() val v1Collator = mockk(relaxed = true) - val v2Collator = V2PayloadMessageCollator(FakeGatingService(), v1Collator, sessionEnvelopeSource, logger) + val gatingService = FakeGatingService() + val v2Collator = V2PayloadMessageCollator( + gatingService, + sessionEnvelopeSource, + metadataService, + eventService, + logMessageService, + performanceInfoService, + null, + preferencesService, + spanRepository, + spanSink, + currentSessionSpan, + FakeSessionPropertiesService(), + FakeStartupService(), + AnrOtelMapper(FakeAnrService()), + NativeAnrOtelMapper(null, EmbraceSerializer()), + logger + ) service = PayloadFactoryImpl(v1Collator, v2Collator, FakeConfigService(), logger) } } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/SessionHandlerTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/SessionHandlerTest.kt index 73301a546c..0b90a279cc 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/SessionHandlerTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/SessionHandlerTest.kt @@ -38,7 +38,9 @@ import io.embrace.android.embracesdk.fakes.fakeSession import io.embrace.android.embracesdk.fakes.fakeSessionBehavior import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.internal.serialization.EmbraceSerializer +import io.embrace.android.embracesdk.internal.spans.CurrentSessionSpan import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData +import io.embrace.android.embracesdk.internal.spans.SpanRepository import io.embrace.android.embracesdk.internal.spans.SpanService import io.embrace.android.embracesdk.internal.spans.SpanSink import io.embrace.android.embracesdk.logging.EmbLogger @@ -101,6 +103,8 @@ internal class SessionHandlerTest { private lateinit var executorService: BlockingScheduledExecutorService private lateinit var scheduledWorker: ScheduledWorker private lateinit var logger: EmbLogger + private lateinit var spanRepository: SpanRepository + private lateinit var currentSessionSpan: CurrentSessionSpan @Before fun before() { @@ -141,6 +145,9 @@ internal class SessionHandlerTest { val initModule = FakeInitModule(clock = clock) spanSink = initModule.openTelemetryModule.spanSink spanService = initModule.openTelemetryModule.spanService + spanRepository = initModule.openTelemetryModule.spanRepository + currentSessionSpan = initModule.openTelemetryModule.currentSessionSpan + val payloadMessageCollator = V1PayloadMessageCollator( gatingService, metadataService, @@ -163,13 +170,25 @@ internal class SessionHandlerTest { ) val v2Collator = V2PayloadMessageCollator( gatingService, - payloadMessageCollator, SessionEnvelopeSourceImpl( metadataSource = FakeEnvelopeMetadataSource(), resourceSource = FakeEnvelopeResourceSource(), sessionPayloadSource = FakeSessionPayloadSource() ), - logger + metadataService, + eventService, + logMessageService, + performanceInfoService, + null, + preferencesService, + spanRepository, + spanSink, + currentSessionSpan, + FakeSessionPropertiesService(), + FakeStartupService(), + AnrOtelMapper(FakeAnrService()), + NativeAnrOtelMapper(null, EmbraceSerializer()), + logger, ) payloadFactory = PayloadFactoryImpl(payloadMessageCollator, v2Collator, configService, logger) } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/message/PayloadFactoryImplTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/message/PayloadFactoryImplTest.kt index 85926a5117..b425492012 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/message/PayloadFactoryImplTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/message/PayloadFactoryImplTest.kt @@ -70,14 +70,26 @@ internal class PayloadFactoryImplTest { logger = initModule.logger ) val v2Collator = V2PayloadMessageCollator( - FakeGatingService(), - v1Collator, - SessionEnvelopeSourceImpl( + gatingService = FakeGatingService(), + nativeThreadSamplerService = null, + preferencesService = FakePreferenceService(), + eventService = FakeEventService(), + logMessageService = FakeLogMessageService(), + metadataService = FakeMetadataService(), + performanceInfoService = FakePerformanceInfoService(), + spanRepository = initModule.openTelemetryModule.spanRepository, + spanSink = initModule.openTelemetryModule.spanSink, + currentSessionSpan = initModule.openTelemetryModule.currentSessionSpan, + sessionPropertiesService = FakeSessionPropertiesService(), + startupService = FakeStartupService(), + anrOtelMapper = AnrOtelMapper(FakeAnrService()), + nativeAnrOtelMapper = NativeAnrOtelMapper(null, EmbraceSerializer()), + logger = initModule.logger, + sessionEnvelopeSource = SessionEnvelopeSourceImpl( FakeEnvelopeMetadataSource(), FakeEnvelopeResourceSource(), FakeSessionPayloadSource() - ), - initModule.logger + ) ) factory = PayloadFactoryImpl( v1payloadMessageCollator = v1Collator, diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/message/V2PayloadMessageCollatorTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/message/V2PayloadMessageCollatorTest.kt index 6c7439d7b6..ee266a6178 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/message/V2PayloadMessageCollatorTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/message/V2PayloadMessageCollatorTest.kt @@ -75,7 +75,24 @@ internal class V2PayloadMessageCollatorTest { resourceSource = FakeEnvelopeResourceSource(), sessionPayloadSource = FakeSessionPayloadSource() ) - v2collator = V2PayloadMessageCollator(gatingService, v1collator, sessionEnvelopeSource, initModule.logger) + v2collator = V2PayloadMessageCollator( + gatingService = gatingService, + nativeThreadSamplerService = null, + preferencesService = FakePreferenceService(), + eventService = FakeEventService(), + logMessageService = FakeLogMessageService(), + metadataService = FakeMetadataService(), + performanceInfoService = FakePerformanceInfoService(), + spanRepository = initModule.openTelemetryModule.spanRepository, + spanSink = initModule.openTelemetryModule.spanSink, + currentSessionSpan = initModule.openTelemetryModule.currentSessionSpan, + sessionPropertiesService = FakeSessionPropertiesService(), + startupService = FakeStartupService(), + anrOtelMapper = AnrOtelMapper(FakeAnrService()), + nativeAnrOtelMapper = NativeAnrOtelMapper(null, EmbraceSerializer()), + logger = initModule.logger, + sessionEnvelopeSource = sessionEnvelopeSource + ) } @Test @@ -210,13 +227,5 @@ internal class V2PayloadMessageCollatorTest { assertEquals(15000000000L, lastHeartbeatTime) assertEquals("crashId", crashReportId) assertNotNull(eventIds) - assertNotNull(infoLogIds) - assertNotNull(warningLogIds) - assertNotNull(errorLogIds) - assertNotNull(infoLogsAttemptedToSend) - assertNotNull(warnLogsAttemptedToSend) - assertNotNull(errorLogsAttemptedToSend) - assertNotNull(exceptionError) - assertNotNull(unhandledExceptions) } }