Skip to content

Commit

Permalink
feat: add otel log data source for internal errors
Browse files Browse the repository at this point in the history
  • Loading branch information
fractalwrench committed May 24, 2024
1 parent c74d8cc commit d4e6744
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ internal sealed class EmbType(type: String, subtype: String?) : TelemetryType {

internal object Exception : System("exception")

internal object InternalError : System("internal")

internal object FlutterException : System("flutter_exception") {
/**
* Attribute name for the exception context in a log representing an exception
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,4 +347,15 @@ internal sealed class SchemaType(
"status" to status.toString()
)
}

internal class InternalError(throwable: Throwable) : SchemaType(
telemetryType = EmbType.System.InternalError,
fixedObjectName = "internal-error"
) {
override val schemaAttributes = mapOf(
"exception.type" to throwable.javaClass.name,
"exception.stacktrace" to throwable.stackTrace.joinToString("\n", transform = StackTraceElement::toString),
"exception.message" to (throwable.message ?: "")
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.embrace.android.embracesdk.capture.internal.errors

import io.embrace.android.embracesdk.arch.datasource.LogDataSource

internal interface InternalErrorDataSource : LogDataSource, InternalErrorHandler
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.embrace.android.embracesdk.capture.internal.errors

import io.embrace.android.embracesdk.Severity
import io.embrace.android.embracesdk.arch.datasource.LogDataSourceImpl
import io.embrace.android.embracesdk.arch.datasource.NoInputValidation
import io.embrace.android.embracesdk.arch.destination.LogEventData
import io.embrace.android.embracesdk.arch.destination.LogWriter
import io.embrace.android.embracesdk.arch.limits.UpToLimitStrategy
import io.embrace.android.embracesdk.arch.schema.SchemaType
import io.embrace.android.embracesdk.logging.EmbLogger

/**
* Tracks internal errors & sends them as OTel logs.
*/
internal class InternalErrorDataSourceImpl(
logWriter: LogWriter,
logger: EmbLogger,
) : InternalErrorDataSource,
LogDataSourceImpl(
destination = logWriter,
logger = logger,
limitStrategy = UpToLimitStrategy { 10 },
) {

override fun handleInternalError(throwable: Throwable) {
alterSessionSpan(NoInputValidation) {
this.addLog(throwable, true) {
val schemaType = SchemaType.InternalError(throwable)
LogEventData(schemaType, Severity.ERROR, "")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.embrace.android.embracesdk.capture.internal.errors

internal interface InternalErrorHandler {
fun handleInternalError(throwable: Throwable)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import io.embrace.android.embracesdk.payload.LegacyExceptionError
* 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?> {
internal interface InternalErrorService : DataCaptureService<LegacyExceptionError?>, InternalErrorHandler {
var configService: ConfigService?
fun handleInternalError(throwable: Throwable)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.embrace.android.embracesdk.capture.crash

import io.embrace.android.embracesdk.Severity
import io.embrace.android.embracesdk.arch.destination.LogEventData
import io.embrace.android.embracesdk.arch.schema.EmbType
import io.embrace.android.embracesdk.capture.internal.errors.InternalErrorDataSourceImpl
import io.embrace.android.embracesdk.fakes.FakeLogWriter
import io.embrace.android.embracesdk.logging.EmbLogger
import io.embrace.android.embracesdk.logging.EmbLoggerImpl
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Test

internal class InternalErrorDataSourceImplTest {

private lateinit var dataSource: InternalErrorDataSourceImpl
private lateinit var logWriter: FakeLogWriter
private lateinit var logger: EmbLogger

@Before
fun setUp() {
logWriter = FakeLogWriter()
logger = EmbLoggerImpl()
dataSource = InternalErrorDataSourceImpl(
logWriter,
logger
)
}

@Test
fun `handle throwable with no message`() {
dataSource.handleInternalError(IllegalStateException())
val data = logWriter.logEvents.single()
val attrs = assertInternalErrorLogged(data)
assertEquals("java.lang.IllegalStateException", attrs["exception.type"])
assertEquals("", attrs["exception.message"])
assertNotNull(attrs["exception.stacktrace"])
}

@Test
fun `handle throwable with message`() {
dataSource.handleInternalError(IllegalArgumentException("Whoops!"))
val data = logWriter.logEvents.single()
val attrs = assertInternalErrorLogged(data)
assertEquals("java.lang.IllegalArgumentException", attrs["exception.type"])
assertEquals("Whoops!", attrs["exception.message"])
assertNotNull(attrs["exception.stacktrace"])
}

@Test
fun `limit not exceeded`() {
repeat(15) {
dataSource.handleInternalError(IllegalStateException())
}
assertEquals(10, logWriter.logEvents.size)
}

private fun assertInternalErrorLogged(data: LogEventData): Map<String, String> {
assertEquals(Severity.ERROR, data.severity)
assertEquals("", data.message)
assertEquals(EmbType.System.InternalError, data.schemaType.telemetryType)
assertEquals("internal-error", data.schemaType.fixedObjectName)

val attrs = data.schemaType.attributes()
assertEquals("sys.internal", attrs["emb.type"])
return attrs
}
}

0 comments on commit d4e6744

Please sign in to comment.