Skip to content

Commit

Permalink
feat: internal errors as otel
Browse files Browse the repository at this point in the history
  • Loading branch information
fractalwrench committed May 24, 2024
1 parent d4e6744 commit e56cf47
Show file tree
Hide file tree
Showing 29 changed files with 56 additions and 293 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -359,11 +359,9 @@ public void testAddSpanExporter() {

private void assertError(@NonNull String functionName) {
assertInternalErrorLogged(
IntegrationTestRuleExtensionsKt.internalErrorService().getCapturedData(),
testRule.bootstrapper,
IllegalArgumentException.class.getCanonicalName(),
functionName + NULL_PARAMETER_ERROR_MESSAGE_TEMPLATE,
IntegrationTestRule.DEFAULT_SDK_START_TIME_MS
functionName + NULL_PARAMETER_ERROR_MESSAGE_TEMPLATE
);
IntegrationTestRuleExtensionsKt.internalErrorService().getCapturedData();
}
}
Original file line number Diff line number Diff line change
@@ -1,43 +1,35 @@
package io.embrace.android.embracesdk.assertions

import io.embrace.android.embracesdk.payload.LegacyExceptionError
import io.embrace.android.embracesdk.IntegrationTestRule
import org.junit.Assert.assertTrue
import io.embrace.android.embracesdk.FakeDeliveryService
import io.embrace.android.embracesdk.findLogAttribute
import io.embrace.android.embracesdk.injection.ModuleInitBootstrapper
import org.junit.Assert.fail

/**
* Return true if at least one exception matching the expected time, exception type, and error message is found in the internal errors
*/
internal fun assertInternalErrorLogged(
exceptionError: LegacyExceptionError?,
bootstrapper: ModuleInitBootstrapper,
exceptionClassName: String,
errorMessage: String,
errorTimeMs: Long = IntegrationTestRule.DEFAULT_SDK_START_TIME_MS
errorMessage: String
) {
requireNotNull(exceptionError) { "No internal errors found" }
var foundErrorMatch = false
var foundErrorAtTime = false
val unmatchedDetails: MutableList<String> = mutableListOf()
val errors = exceptionError.exceptionErrors.toList()
assertTrue("No exception errors found", errors.isNotEmpty())
errors.forEach { error ->
if (errorTimeMs == error.timestamp) {
foundErrorAtTime = true
val firstExceptionInfo = checkNotNull(error.exceptions).first()
with(firstExceptionInfo) {
if (exceptionClassName == name && errorMessage == message) {
foundErrorMatch = true
} else {
unmatchedDetails.add("'$exceptionClassName' is not '$name' OR '$errorMessage' is not '$message' \n")
}
}
bootstrapper.customerLogModule.logOrchestrator.flush(false)
val deliveryService = bootstrapper.deliveryModule.deliveryService as FakeDeliveryService
val logs = deliveryService.lastSentLogPayloads.mapNotNull { it.data.logs }
.flatten()
.filter { log ->
log.findLogAttribute("emb.type") == "sys.internal"
}
}

assertTrue("No internal error found matching the expected time", foundErrorAtTime)
if (logs.isEmpty()) {
fail("No internal errors found")
}

assertTrue(
"Expected exception not found. " +
"Found following ${unmatchedDetails.size} exceptions in ${errors.size} errors instead: $unmatchedDetails",
foundErrorMatch
)
val matchingLogs = logs.filter { log ->
log.findLogAttribute("exception.type") == exceptionClassName &&
log.findLogAttribute("exception.message") == errorMessage
}
if (matchingLogs.isEmpty()) {
fail("No internal errors found matching the expected exception")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,7 @@ internal class EmbraceImpl @JvmOverloads constructor(
val metadataService by embraceImplInject { bootstrapper.essentialServiceModule.metadataService }
val activityService by embraceImplInject { bootstrapper.essentialServiceModule.processStateService }
val activityLifecycleTracker by embraceImplInject { bootstrapper.essentialServiceModule.activityLifecycleTracker }
val internalErrorService by embraceImplInject {
bootstrapper.initModule.internalErrorService.also {
it.configService = bootstrapper.essentialServiceModule.configService
}
}
val internalErrorService by embraceImplInject { bootstrapper.initModule.internalErrorService }

private val anrService by embraceImplInject { bootstrapper.anrModule.anrService }
private val configService by embraceImplInject { bootstrapper.essentialServiceModule.configService }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ 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.capture.internal.errors.InternalErrorService
import io.embrace.android.embracesdk.internal.payload.SessionPayload
import io.embrace.android.embracesdk.internal.payload.Span
import io.embrace.android.embracesdk.internal.payload.toNewPayload
Expand All @@ -18,7 +17,6 @@ import io.embrace.android.embracesdk.session.properties.SessionPropertiesService
import io.embrace.android.embracesdk.spans.PersistableEmbraceSpan

internal class SessionPayloadSourceImpl(
private val internalErrorService: InternalErrorService,
private val nativeThreadSamplerService: NativeThreadSamplerService?,
private val spanSink: SpanSink,
private val currentSessionSpan: CurrentSessionSpan,
Expand All @@ -29,15 +27,13 @@ internal class SessionPayloadSourceImpl(

override fun getSessionPayload(endType: SessionSnapshotType): SessionPayload {
val sharedLibSymbolMapping = captureDataSafely(logger) { nativeThreadSamplerService?.getNativeSymbols() }
val internalErrors = captureDataSafely(logger) { internalErrorService.getCapturedData()?.toNewPayload() }
val snapshots = retrieveSpanSnapshotData()

// Ensure the span retrieving is last as that potentially ends the session span, which effectively ends the session
val spans = retrieveSpanData(endType)
return SessionPayload(
spans = spans,
spanSnapshots = snapshots,
internalError = internalErrors,
sharedLibSymbolMapping = sharedLibSymbolMapping
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,15 @@
package io.embrace.android.embracesdk.capture.internal.errors

import io.embrace.android.embracesdk.config.ConfigService
import io.embrace.android.embracesdk.internal.clock.Clock
import io.embrace.android.embracesdk.payload.LegacyExceptionError
import io.embrace.android.embracesdk.internal.utils.Provider

/**
* Intercepts Embrace SDK's exceptions errors and forwards them to the Embrace API.
*/
internal class EmbraceInternalErrorService(
private val clock: Clock
) : InternalErrorService {

private var err: LegacyExceptionError = LegacyExceptionError()
internal class EmbraceInternalErrorService : InternalErrorService {

override fun handleInternalError(throwable: Throwable) {
// if the config service has not been set yet, capture the exception
if (configService == null || configService?.dataCaptureEventBehavior?.isInternalExceptionCaptureEnabled() == true) {
err.addException(
throwable,
clock
)
}
internalErrorDataSource()?.handleInternalError(throwable)
}

override var configService: ConfigService? = null

override fun getCapturedData(): LegacyExceptionError? = when {
err.occurrences > 0 -> err
else -> null
}

override fun cleanCollections() {
err = LegacyExceptionError()
}
override var internalErrorDataSource: Provider<InternalErrorDataSource?> = { null }
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package io.embrace.android.embracesdk.capture.internal.errors

import io.embrace.android.embracesdk.arch.DataCaptureService
import io.embrace.android.embracesdk.config.ConfigService
import io.embrace.android.embracesdk.payload.LegacyExceptionError
import io.embrace.android.embracesdk.internal.utils.Provider

/**
* Reports an internal error to Embrace. An internal error is defined as an exception that was
* caught within Embrace code & logged to [EmbLogger].
*/
internal interface InternalErrorService : DataCaptureService<LegacyExceptionError?>, InternalErrorHandler {
var configService: ConfigService?
internal interface InternalErrorService : InternalErrorHandler {
var internalErrorDataSource: Provider<InternalErrorDataSource?>
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import io.embrace.android.embracesdk.capture.crumbs.RnActionDataSource
import io.embrace.android.embracesdk.capture.crumbs.TapDataSource
import io.embrace.android.embracesdk.capture.crumbs.ViewDataSource
import io.embrace.android.embracesdk.capture.crumbs.WebViewUrlDataSource
import io.embrace.android.embracesdk.capture.internal.errors.InternalErrorDataSource
import io.embrace.android.embracesdk.capture.internal.errors.InternalErrorDataSourceImpl
import io.embrace.android.embracesdk.capture.memory.MemoryWarningDataSource
import io.embrace.android.embracesdk.capture.powersave.LowPowerDataSource
import io.embrace.android.embracesdk.capture.session.SessionPropertiesDataSource
Expand Down Expand Up @@ -55,6 +57,7 @@ internal interface DataSourceModule {
val rnActionDataSource: DataSourceState<RnActionDataSource>
val thermalStateDataSource: DataSourceState<ThermalStateDataSource>?
val webViewDataSource: DataSourceState<WebViewDataSource>
val internalErrorDataSource: DataSourceState<InternalErrorDataSource>
}

internal class DataSourceModuleImpl(
Expand Down Expand Up @@ -268,6 +271,18 @@ internal class DataSourceModuleImpl(
)
}

override val internalErrorDataSource: DataSourceState<InternalErrorDataSource> by dataSourceState {
DataSourceState(
factory = {
InternalErrorDataSourceImpl(
logWriter = essentialServiceModule.logWriter,
logger = initModule.logger,
)
},
configGate = { configService.dataCaptureEventBehavior.isInternalExceptionCaptureEnabled() }
)
}

private val configService = essentialServiceModule.configService

override fun getDataSources(): List<DataSourceState<*>> = values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ internal class InitModuleImpl(
override val systemInfo: SystemInfo = SystemInfo()
) : InitModule {

override val internalErrorService: InternalErrorService = EmbraceInternalErrorService(clock)
override val internalErrorService: InternalErrorService = EmbraceInternalErrorService()

init {
logger.internalErrorService = internalErrorService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,6 @@ internal class ModuleInitBootstrapper(
)
}
postInit(EssentialServiceModule::class) {
initModule.internalErrorService.configService = essentialServiceModule.configService
serviceRegistry.registerServices(
essentialServiceModule.processStateService,
essentialServiceModule.metadataService,
Expand Down Expand Up @@ -227,6 +226,7 @@ internal class ModuleInitBootstrapper(
anrModule
)
}
initModule.internalErrorService.internalErrorDataSource = { dataSourceModule.internalErrorDataSource.dataSource }

dataCaptureServiceModule = init(DataCaptureServiceModule::class) {
dataCaptureServiceModuleSupplier(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ internal class PayloadModuleImpl(

private val sessionPayloadSource by singleton {
SessionPayloadSourceImpl(
initModule.internalErrorService,
nativeModule.nativeThreadSamplerService,
otelModule.spanSink,
otelModule.currentSessionSpan,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ internal class SessionModuleImpl(
essentialServiceModule.metadataService,
dataContainerModule.eventService,
customerLogModule.logMessageService,
initModule.internalErrorService,
dataContainerModule.performanceInfoService,
dataCaptureServiceModule.webviewService,
nativeModule.nativeThreadSamplerService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import com.squareup.moshi.JsonClass
* span, which contains metadata about the session represented by this payload. The spans included
* here may not have started in this session, but they ended during it.
* @param spanSnapshots A list of spans that are still active at the time of the session's end.
* @param internalError
* @param sharedLibSymbolMapping A map of symbols that are associated with the session. We use this
* to associate the symbolication files that have been uploaded with UUIDs with the stacktrace module
* names, which don’t have UUIDs in them. Previous name: s.sb
Expand All @@ -28,9 +27,6 @@ internal data class SessionPayload(
@Json(name = "span_snapshots")
val spanSnapshots: List<Span>? = null,

@Json(name = "internal_error")
val internalError: InternalError? = null,

/**
* A map of symbols that are associated with the session.
* We use this to associate the symbolication files that have been uploaded with UUIDs with the stacktrace module names,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,6 @@ internal data class Session @JvmOverloads internal constructor(
@Json(name = "lec")
val errorLogsAttemptedToSend: Int? = null,

@Json(name = "e")
val exceptionError: LegacyExceptionError? = null,

@Json(name = "ri")
val crashReportId: String? = null,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ 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.internal.errors.InternalErrorService
import io.embrace.android.embracesdk.capture.metadata.MetadataService
import io.embrace.android.embracesdk.capture.startup.StartupService
import io.embrace.android.embracesdk.capture.user.UserService
Expand All @@ -31,7 +30,6 @@ internal class V1PayloadMessageCollator(
private val metadataService: MetadataService,
private val eventService: EventService,
private val logMessageService: LogMessageService,
private val internalErrorService: InternalErrorService,
private val performanceInfoService: PerformanceInfoService,
private val webViewService: WebViewService,
private val nativeThreadSamplerService: NativeThreadSamplerService?,
Expand Down Expand Up @@ -136,7 +134,6 @@ internal class V1PayloadMessageCollator(
infoLogsAttemptedToSend = captureDataSafely(logger, logMessageService::getInfoLogsAttemptedToSend),
warnLogsAttemptedToSend = captureDataSafely(logger, logMessageService::getWarnLogsAttemptedToSend),
errorLogsAttemptedToSend = captureDataSafely(logger, logMessageService::getErrorLogsAttemptedToSend),
exceptionError = captureDataSafely(logger, internalErrorService::getCapturedData),
lastHeartbeatTime = endTime,
endType = lifeEventType,
unhandledExceptions = captureDataSafely(logger, logMessageService::getUnhandledExceptionsSent),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.embrace.android.embracesdk

import com.squareup.moshi.JsonDataException
import io.embrace.android.embracesdk.payload.LegacyExceptionError
import io.embrace.android.embracesdk.payload.Orientation
import io.embrace.android.embracesdk.payload.Session
import io.embrace.android.embracesdk.payload.Session.LifeEventType
Expand Down Expand Up @@ -40,7 +39,6 @@ internal class SessionTest {
startupThreshold = 5000,
sdkStartupDuration = 109,
unhandledExceptions = 1,
exceptionError = LegacyExceptionError(),
orientations = listOf(Orientation(1, 16092342200)),
properties = mapOf("fake-key" to "fake-value"),
symbols = mapOf("fake-native-key" to "fake-native-value"),
Expand Down Expand Up @@ -90,7 +88,6 @@ internal class SessionTest {
assertEquals(5000L, startupThreshold)
assertEquals(109L, sdkStartupDuration)
assertEquals(1, unhandledExceptions)
assertEquals(LegacyExceptionError(), exceptionError)
assertEquals(listOf(Orientation(1, 16092342200)), orientations)
assertEquals(mapOf("fake-key" to "fake-value"), properties)
assertEquals(mapOf("fake-native-key" to "fake-native-value"), symbols)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
package io.embrace.android.embracesdk.capture.envelope.session

import io.embrace.android.embracesdk.FakeSessionPropertiesService
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.FakePersistableEmbraceSpan
import io.embrace.android.embracesdk.fakes.FakeSpanData
import io.embrace.android.embracesdk.internal.payload.SessionPayload
import io.embrace.android.embracesdk.internal.spans.SpanRepository
import io.embrace.android.embracesdk.internal.spans.SpanSinkImpl
import io.embrace.android.embracesdk.logging.EmbLoggerImpl
import io.embrace.android.embracesdk.payload.LegacyExceptionError
import io.embrace.android.embracesdk.session.orchestrator.SessionSnapshotType
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
Expand All @@ -29,11 +26,6 @@ internal class SessionPayloadSourceImplTest {

@Before
fun setUp() {
val errorService = FakeInternalErrorService().apply {
data = LegacyExceptionError().apply {
addException(RuntimeException(), FakeClock())
}
}
sink = SpanSinkImpl().apply {
storeCompletedSpans(listOf(cacheSpan))
}
Expand All @@ -44,7 +36,6 @@ internal class SessionPayloadSourceImplTest {
spanRepository = SpanRepository()
spanRepository.trackStartedSpan(activeSpan)
impl = SessionPayloadSourceImpl(
errorService,
FakeNativeThreadSamplerService(),
sink,
currentSessionSpan,
Expand Down Expand Up @@ -76,8 +67,6 @@ internal class SessionPayloadSourceImplTest {
}

private fun assertPayloadPopulated(payload: SessionPayload) {
val err = checkNotNull(payload.internalError)
assertEquals(1, err.count)
assertEquals(mapOf("armeabi-v7a" to "my-symbols"), payload.sharedLibSymbolMapping)
val snapshots = checkNotNull(payload.spanSnapshots)
assertEquals(1, snapshots.size)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import io.embrace.android.embracesdk.capture.crumbs.RnActionDataSource
import io.embrace.android.embracesdk.capture.crumbs.TapDataSource
import io.embrace.android.embracesdk.capture.crumbs.ViewDataSource
import io.embrace.android.embracesdk.capture.crumbs.WebViewUrlDataSource
import io.embrace.android.embracesdk.capture.internal.errors.InternalErrorDataSource
import io.embrace.android.embracesdk.capture.memory.MemoryWarningDataSource
import io.embrace.android.embracesdk.capture.powersave.LowPowerDataSource
import io.embrace.android.embracesdk.capture.session.SessionPropertiesDataSource
Expand All @@ -35,4 +36,5 @@ internal class FakeDataSourceModule : DataSourceModule {
override val rnActionDataSource: DataSourceState<RnActionDataSource> = DataSourceState({ null })
override val thermalStateDataSource: DataSourceState<ThermalStateDataSource> = DataSourceState({ null })
override val webViewDataSource: DataSourceState<WebViewDataSource> = DataSourceState({ null })
override val internalErrorDataSource: DataSourceState<InternalErrorDataSource> = DataSourceState({ null })
}
Loading

0 comments on commit e56cf47

Please sign in to comment.