From 14f903b737f61673c36d911e8f8d445db9aa4b30 Mon Sep 17 00:00:00 2001 From: Jamie Lynch Date: Wed, 15 Nov 2023 10:37:45 +0000 Subject: [PATCH] refactor: extract session message collator --- .../embracesdk/injection/SessionModule.kt | 28 ++- .../embracesdk/session/SessionHandler.kt | 151 +--------------- .../session/SessionMessageCollator.kt | 164 ++++++++++++++++++ .../fakes/injection/FakeSessionModule.kt | 4 + .../embracesdk/session/SessionHandlerTest.kt | 23 ++- .../session/SessionModuleImplTest.kt | 2 + 6 files changed, 212 insertions(+), 160 deletions(-) create mode 100644 embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/SessionMessageCollator.kt 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 c942c3df6..91daef7ec 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 @@ -6,6 +6,7 @@ import io.embrace.android.embracesdk.session.EmbraceBackgroundActivityService import io.embrace.android.embracesdk.session.EmbraceSessionProperties import io.embrace.android.embracesdk.session.EmbraceSessionService import io.embrace.android.embracesdk.session.SessionHandler +import io.embrace.android.embracesdk.session.SessionMessageCollator import io.embrace.android.embracesdk.session.SessionService import io.embrace.android.embracesdk.worker.ExecutorName import io.embrace.android.embracesdk.worker.WorkerThreadModule @@ -14,6 +15,7 @@ internal interface SessionModule { val sessionHandler: SessionHandler val sessionService: SessionService val backgroundActivityService: BackgroundActivityService? + val sessionMessageCollator: SessionMessageCollator } internal class SessionModuleImpl( @@ -31,6 +33,24 @@ internal class SessionModuleImpl( workerThreadModule: WorkerThreadModule ) : SessionModule { + override val sessionMessageCollator: SessionMessageCollator by singleton { + SessionMessageCollator( + essentialServiceModule.configService, + essentialServiceModule.metadataService, + dataContainerModule.eventService, + customerLogModule.remoteLogger, + sdkObservabilityModule.exceptionService, + dataContainerModule.performanceInfoService, + dataCaptureServiceModule.webviewService, + dataCaptureServiceModule.activityLifecycleBreadcrumbService, + dataCaptureServiceModule.thermalStatusService, + nativeModule.nativeThreadSamplerService, + dataCaptureServiceModule.breadcrumbService, + essentialServiceModule.userService, + initModule.clock + ) + } + override val sessionHandler: SessionHandler by singleton { SessionHandler( coreModule.logger, @@ -42,16 +62,10 @@ internal class SessionModuleImpl( dataCaptureServiceModule.breadcrumbService, essentialServiceModule.activityLifecycleTracker, nativeModule.ndkService, - dataContainerModule.eventService, - customerLogModule.remoteLogger, sdkObservabilityModule.exceptionService, - dataContainerModule.performanceInfoService, essentialServiceModule.memoryCleanerService, deliveryModule.deliveryService, - dataCaptureServiceModule.webviewService, - dataCaptureServiceModule.activityLifecycleBreadcrumbService, - dataCaptureServiceModule.thermalStatusService, - nativeModule.nativeThreadSamplerService, + sessionMessageCollator, initModule.clock, workerThreadModule.scheduledExecutor(ExecutorName.SESSION_CLOSER), workerThreadModule.scheduledExecutor(ExecutorName.SESSION_CACHING) diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/SessionHandler.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/SessionHandler.kt index e18d15613..d6c4911d9 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/SessionHandler.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/SessionHandler.kt @@ -1,20 +1,13 @@ package io.embrace.android.embracesdk.session import androidx.annotation.VisibleForTesting -import io.embrace.android.embracesdk.anr.ndk.NativeThreadSamplerService -import io.embrace.android.embracesdk.capture.PerformanceInfoService import io.embrace.android.embracesdk.capture.connectivity.NetworkConnectivityService import io.embrace.android.embracesdk.capture.crumbs.BreadcrumbService -import io.embrace.android.embracesdk.capture.crumbs.activity.ActivityLifecycleBreadcrumbService import io.embrace.android.embracesdk.capture.metadata.MetadataService -import io.embrace.android.embracesdk.capture.thermalstate.ThermalStatusService import io.embrace.android.embracesdk.capture.user.UserService -import io.embrace.android.embracesdk.capture.webview.WebViewService import io.embrace.android.embracesdk.comms.delivery.DeliveryService import io.embrace.android.embracesdk.comms.delivery.SessionMessageState import io.embrace.android.embracesdk.config.ConfigService -import io.embrace.android.embracesdk.event.EmbraceRemoteLogger -import io.embrace.android.embracesdk.event.EventService import io.embrace.android.embracesdk.internal.MessageType import io.embrace.android.embracesdk.internal.clock.Clock import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData @@ -23,7 +16,6 @@ import io.embrace.android.embracesdk.logging.EmbraceInternalErrorService import io.embrace.android.embracesdk.logging.InternalEmbraceLogger import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger.Companion.logDeveloper import io.embrace.android.embracesdk.ndk.NdkService -import io.embrace.android.embracesdk.payload.BetaFeatures import io.embrace.android.embracesdk.payload.Session import io.embrace.android.embracesdk.payload.Session.SessionLifeEventType import io.embrace.android.embracesdk.payload.SessionMessage @@ -46,16 +38,10 @@ internal class SessionHandler( private val breadcrumbService: BreadcrumbService, private val activityLifecycleTracker: ActivityTracker, private val ndkService: NdkService, - private val eventService: EventService, - private val remoteLogger: EmbraceRemoteLogger, private val exceptionService: EmbraceInternalErrorService, - private val performanceInfoService: PerformanceInfoService, private val memoryCleanerService: MemoryCleanerService, private val deliveryService: DeliveryService, - private val webViewService: WebViewService, - private val activityLifecycleBreadcrumbService: ActivityLifecycleBreadcrumbService?, - private val thermalStatusService: ThermalStatusService, - private val nativeThreadSamplerService: NativeThreadSamplerService?, + private val sessionMessageCollator: SessionMessageCollator, private val clock: Clock, private val automaticSessionStopper: ScheduledExecutorService, private val sessionPeriodicCacheExecutorService: ScheduledExecutorService @@ -95,7 +81,7 @@ internal class SessionHandler( // Record the connection type at the start of the session. networkConnectivityService.networkStatusOnSessionStarted(session.startTime) - val sessionMessage = buildStartSessionMessage(session) + val sessionMessage = sessionMessageCollator.buildStartSessionMessage(session) metadataService.setActiveSessionId(session.sessionId) @@ -245,123 +231,6 @@ internal class SessionHandler( } } - @Suppress("ComplexMethod") - private fun buildEndSessionMessage( - originSession: Session, - endedCleanly: Boolean, - forceQuit: Boolean, - crashId: String?, - endType: SessionLifeEventType, - sessionProperties: EmbraceSessionProperties, - sdkStartupDuration: Long, - endTime: Long, - spans: List? = null - ): SessionMessage { - val startTime: Long = originSession.startTime - - // if it's a crash session, then add the stacktrace to the session payload - val crashReportId = when { - !crashId.isNullOrEmpty() -> crashId - else -> null - } - val terminationTime = when { - forceQuit -> endTime - else -> null - } - val receivedTermination = when { - forceQuit -> true - else -> null - } - // We don't set end time for force-quit, as the API interprets this to be a clean - // termination - val endTimeVal = when { - forceQuit -> null - else -> endTime - } - - val sdkStartDuration = when (originSession.isColdStart) { - true -> sdkStartupDuration - false -> null - } - - val startupEventInfo = eventService.getStartupMomentInfo() - - val startupDuration = when (originSession.isColdStart && startupEventInfo != null) { - true -> startupEventInfo.duration - false -> null - } - val startupThreshold = when (originSession.isColdStart && startupEventInfo != null) { - true -> startupEventInfo.threshold - false -> null - } - - val betaFeatures = when (configService.sdkModeBehavior.isBetaFeaturesEnabled()) { - false -> null - else -> BetaFeatures( - thermalStates = thermalStatusService.getCapturedData(), - activityLifecycleBreadcrumbs = activityLifecycleBreadcrumbService?.getCapturedData() - ) - } - - val endSession = originSession.copy( - isEndedCleanly = endedCleanly, - appState = EmbraceSessionService.APPLICATION_STATE_FOREGROUND, - messageType = MESSAGE_TYPE_END, - eventIds = eventService.findEventIdsForSession(startTime, endTime), - infoLogIds = remoteLogger.findInfoLogIds(startTime, endTime), - warningLogIds = remoteLogger.findWarningLogIds(startTime, endTime), - errorLogIds = remoteLogger.findErrorLogIds(startTime, endTime), - networkLogIds = remoteLogger.findNetworkLogIds(startTime, endTime), - infoLogsAttemptedToSend = remoteLogger.getInfoLogsAttemptedToSend(), - warnLogsAttemptedToSend = remoteLogger.getWarnLogsAttemptedToSend(), - errorLogsAttemptedToSend = remoteLogger.getErrorLogsAttemptedToSend(), - lastHeartbeatTime = clock.now(), - properties = sessionProperties.get(), - endType = endType, - unhandledExceptions = remoteLogger.getUnhandledExceptionsSent(), - webViewInfo = webViewService.getCapturedData(), - crashReportId = crashReportId, - terminationTime = terminationTime, - isReceivedTermination = receivedTermination, - endTime = endTimeVal, - sdkStartupDuration = sdkStartDuration, - startupDuration = startupDuration, - startupThreshold = startupThreshold, - user = userService.getUserInfo(), - betaFeatures = betaFeatures, - symbols = nativeThreadSamplerService?.getNativeSymbols() - ) - - val performanceInfo = performanceInfoService.getSessionPerformanceInfo( - startTime, - endTime, - originSession.isColdStart, - originSession.isReceivedTermination - ) - - val appInfo = metadataService.getAppInfo() - val deviceInfo = metadataService.getDeviceInfo() - val breadcrumbs = breadcrumbService.getBreadcrumbs(startTime, endTime) - - val endSessionWithAllErrors = endSession.copy(exceptionError = exceptionService.currentExceptionError) - - return SessionMessage( - session = endSessionWithAllErrors, - userInfo = endSessionWithAllErrors.user, - appInfo = appInfo, - deviceInfo = deviceInfo, - performanceInfo = performanceInfo.copy(), - breadcrumbs = breadcrumbs, - spans = spans - ) - } - - private fun buildStartSessionMessage(session: Session) = SessionMessage( - session = session, - appInfo = metadataService.getAppInfo(), - deviceInfo = metadataService.getDeviceInfo() - ) - /** * It builds an end active session message, it sanitizes it, it performs all types of memory cleaning, * it updates cache and it sends it to our servers. @@ -387,7 +256,7 @@ internal class SessionHandler( return } - val fullEndSessionMessage = buildEndSessionMessage( + val fullEndSessionMessage = sessionMessageCollator.buildEndSessionMessage( /* we are previously checking in allowSessionToEnd that originSession != null */ originSession!!, endedCleanly = true, @@ -439,7 +308,7 @@ internal class SessionHandler( // let's not overwrite the crash info with the periodic caching stopPeriodicSessionCaching() - val fullEndSessionMessage = buildEndSessionMessage( + val fullEndSessionMessage = sessionMessageCollator.buildEndSessionMessage( originSession, endedCleanly = false, forceQuit = false, @@ -470,7 +339,7 @@ internal class SessionHandler( return null } - val fullEndSessionMessage = buildEndSessionMessage( + val fullEndSessionMessage = sessionMessageCollator.buildEndSessionMessage( activeSession, endedCleanly = false, forceQuit = true, @@ -572,13 +441,3 @@ internal class SessionHandler( } } } - -/** - * Signals to the API the start of a session. - */ -internal const val MESSAGE_TYPE_START = "st" - -/** - * Signals to the API the end of a session. - */ -private const val MESSAGE_TYPE_END = "en" diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/SessionMessageCollator.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/SessionMessageCollator.kt new file mode 100644 index 000000000..d58bc1bdb --- /dev/null +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/SessionMessageCollator.kt @@ -0,0 +1,164 @@ +package io.embrace.android.embracesdk.session + +import io.embrace.android.embracesdk.anr.ndk.NativeThreadSamplerService +import io.embrace.android.embracesdk.capture.PerformanceInfoService +import io.embrace.android.embracesdk.capture.crumbs.BreadcrumbService +import io.embrace.android.embracesdk.capture.crumbs.activity.ActivityLifecycleBreadcrumbService +import io.embrace.android.embracesdk.capture.metadata.MetadataService +import io.embrace.android.embracesdk.capture.thermalstate.ThermalStatusService +import io.embrace.android.embracesdk.capture.user.UserService +import io.embrace.android.embracesdk.capture.webview.WebViewService +import io.embrace.android.embracesdk.config.ConfigService +import io.embrace.android.embracesdk.event.EmbraceRemoteLogger +import io.embrace.android.embracesdk.event.EventService +import io.embrace.android.embracesdk.internal.clock.Clock +import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData +import io.embrace.android.embracesdk.logging.EmbraceInternalErrorService +import io.embrace.android.embracesdk.payload.BetaFeatures +import io.embrace.android.embracesdk.payload.Session +import io.embrace.android.embracesdk.payload.SessionMessage + +internal class SessionMessageCollator( + private val configService: ConfigService, + private val metadataService: MetadataService, + private val eventService: EventService, + private val remoteLogger: EmbraceRemoteLogger, + private val exceptionService: EmbraceInternalErrorService, + private val performanceInfoService: PerformanceInfoService, + private val webViewService: WebViewService, + private val activityLifecycleBreadcrumbService: ActivityLifecycleBreadcrumbService?, + private val thermalStatusService: ThermalStatusService, + private val nativeThreadSamplerService: NativeThreadSamplerService?, + private val breadcrumbService: BreadcrumbService, + private val userService: UserService, + private val clock: Clock +) { + + @Suppress("ComplexMethod") + internal fun buildEndSessionMessage( + originSession: Session, + endedCleanly: Boolean, + forceQuit: Boolean, + crashId: String?, + endType: Session.SessionLifeEventType, + sessionProperties: EmbraceSessionProperties, + sdkStartupDuration: Long, + endTime: Long, + spans: List? = null + ): SessionMessage { + val startTime: Long = originSession.startTime + + // if it's a crash session, then add the stacktrace to the session payload + val crashReportId = when { + !crashId.isNullOrEmpty() -> crashId + else -> null + } + val terminationTime = when { + forceQuit -> endTime + else -> null + } + val receivedTermination = when { + forceQuit -> true + else -> null + } + // We don't set end time for force-quit, as the API interprets this to be a clean + // termination + val endTimeVal = when { + forceQuit -> null + else -> endTime + } + + val sdkStartDuration = when (originSession.isColdStart) { + true -> sdkStartupDuration + false -> null + } + + val startupEventInfo = eventService.getStartupMomentInfo() + + val startupDuration = when (originSession.isColdStart && startupEventInfo != null) { + true -> startupEventInfo.duration + false -> null + } + val startupThreshold = when (originSession.isColdStart && startupEventInfo != null) { + true -> startupEventInfo.threshold + false -> null + } + + val betaFeatures = when (configService.sdkModeBehavior.isBetaFeaturesEnabled()) { + false -> null + else -> BetaFeatures( + thermalStates = thermalStatusService.getCapturedData(), + activityLifecycleBreadcrumbs = activityLifecycleBreadcrumbService?.getCapturedData() + ) + } + + val endSession = originSession.copy( + isEndedCleanly = endedCleanly, + appState = EmbraceSessionService.APPLICATION_STATE_FOREGROUND, + messageType = MESSAGE_TYPE_END, + eventIds = eventService.findEventIdsForSession(startTime, endTime), + infoLogIds = remoteLogger.findInfoLogIds(startTime, endTime), + warningLogIds = remoteLogger.findWarningLogIds(startTime, endTime), + errorLogIds = remoteLogger.findErrorLogIds(startTime, endTime), + networkLogIds = remoteLogger.findNetworkLogIds(startTime, endTime), + infoLogsAttemptedToSend = remoteLogger.getInfoLogsAttemptedToSend(), + warnLogsAttemptedToSend = remoteLogger.getWarnLogsAttemptedToSend(), + errorLogsAttemptedToSend = remoteLogger.getErrorLogsAttemptedToSend(), + lastHeartbeatTime = clock.now(), + properties = sessionProperties.get(), + endType = endType, + unhandledExceptions = remoteLogger.getUnhandledExceptionsSent(), + webViewInfo = webViewService.getCapturedData(), + crashReportId = crashReportId, + terminationTime = terminationTime, + isReceivedTermination = receivedTermination, + endTime = endTimeVal, + sdkStartupDuration = sdkStartDuration, + startupDuration = startupDuration, + startupThreshold = startupThreshold, + user = userService.getUserInfo(), + betaFeatures = betaFeatures, + symbols = nativeThreadSamplerService?.getNativeSymbols() + ) + + val performanceInfo = performanceInfoService.getSessionPerformanceInfo( + startTime, + endTime, + originSession.isColdStart, + originSession.isReceivedTermination + ) + + val appInfo = metadataService.getAppInfo() + val deviceInfo = metadataService.getDeviceInfo() + val breadcrumbs = breadcrumbService.getBreadcrumbs(startTime, endTime) + + val endSessionWithAllErrors = + endSession.copy(exceptionError = exceptionService.currentExceptionError) + + return SessionMessage( + session = endSessionWithAllErrors, + userInfo = endSessionWithAllErrors.user, + appInfo = appInfo, + deviceInfo = deviceInfo, + performanceInfo = performanceInfo.copy(), + breadcrumbs = breadcrumbs, + spans = spans + ) + } + + internal fun buildStartSessionMessage(session: Session) = SessionMessage( + session = session, + appInfo = metadataService.getAppInfo(), + deviceInfo = metadataService.getDeviceInfo() + ) +} + +/** + * Signals to the API the start of a session. + */ +internal const val MESSAGE_TYPE_START = "st" + +/** + * Signals to the API the end of a session. + */ +internal const val MESSAGE_TYPE_END = "en" diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/injection/FakeSessionModule.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/injection/FakeSessionModule.kt index 028d1e080..3ee5c843c 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/injection/FakeSessionModule.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/injection/FakeSessionModule.kt @@ -4,6 +4,7 @@ import io.embrace.android.embracesdk.FakeSessionService import io.embrace.android.embracesdk.injection.SessionModule import io.embrace.android.embracesdk.session.BackgroundActivityService import io.embrace.android.embracesdk.session.SessionHandler +import io.embrace.android.embracesdk.session.SessionMessageCollator import io.embrace.android.embracesdk.session.SessionService internal class FakeSessionModule( @@ -12,4 +13,7 @@ internal class FakeSessionModule( ) : SessionModule { override val sessionHandler: SessionHandler get() = TODO("Not yet implemented") + + override val sessionMessageCollator: SessionMessageCollator + get() = TODO("Not yet implemented") } 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 5ff10cfe3..842b34012 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 @@ -148,6 +148,21 @@ internal class SessionHandlerTest { ) gatingService = FakeGatingService(configService = configService) deliveryService = FakeDeliveryService() + val sessionMessageCollator = SessionMessageCollator( + configService, + metadataService, + mockEventService, + mockRemoteLogger, + mockExceptionService, + mockPerformanceInfoService, + mockWebViewservice, + null, + NoOpThermalStatusService(), + null, + mockBreadcrumbService, + mockUserService, + clock + ) sessionHandler = SessionHandler( logger, configService, @@ -158,16 +173,10 @@ internal class SessionHandlerTest { mockBreadcrumbService, activityLifecycleTracker, mockNdkService, - mockEventService, - mockRemoteLogger, mockExceptionService, - mockPerformanceInfoService, mockMemoryCleanerService, deliveryService, - mockWebViewservice, - null, - NoOpThermalStatusService(), - null, + sessionMessageCollator, clock, automaticSessionStopper = mockAutomaticSessionStopper, sessionPeriodicCacheExecutorService = mockSessionPeriodicCacheExecutorService diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/SessionModuleImplTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/SessionModuleImplTest.kt index 3ab742e9d..2c360fee5 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/SessionModuleImplTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/session/SessionModuleImplTest.kt @@ -46,6 +46,7 @@ internal class SessionModuleImplTest { ) assertNotNull(module.sessionHandler) assertNotNull(module.sessionService) + assertNotNull(module.sessionMessageCollator) assertNull(module.backgroundActivityService) } @@ -67,6 +68,7 @@ internal class SessionModuleImplTest { ) assertNotNull(module.sessionHandler) assertNotNull(module.sessionService) + assertNotNull(module.sessionMessageCollator) assertNotNull(module.backgroundActivityService) }