Skip to content

Commit

Permalink
Add in extension functions useful when working with EmbraceAttributes (
Browse files Browse the repository at this point in the history
…#533)

## Goal

Add and use extension functions to test EmbraceAttributes values within generated data

Note: I accidentally merged in a commit that converts `emb.key` to an EmbraceAttribute, and since the changes were intermingled in the same file..... I'm just going to leave it and apologize 😅 

## Testing

Existing test cover this
  • Loading branch information
bidetofevil committed Mar 11, 2024
2 parents 2a1b4ba + 587bfd3 commit 7c19640
Show file tree
Hide file tree
Showing 23 changed files with 211 additions and 125 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package io.embrace.android.embracesdk.assertions

import io.embrace.android.embracesdk.arch.assertError
import io.embrace.android.embracesdk.arch.assertIsKeySpan
import io.embrace.android.embracesdk.arch.assertNotKeySpan
import io.embrace.android.embracesdk.arch.assertSuccessful
import io.embrace.android.embracesdk.internal.clock.nanosToMillis
import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData
import io.embrace.android.embracesdk.internal.spans.isKey
import io.embrace.android.embracesdk.internal.spans.isPrivate
import io.embrace.android.embracesdk.spans.EmbraceSpanEvent
import io.embrace.android.embracesdk.spans.ErrorCode
Expand Down Expand Up @@ -36,18 +39,20 @@ internal fun assertEmbraceSpanData(
assertEquals(32, traceId.length)
}
assertEquals(expectedStatus, status)
errorCode?.run {
val errorCodeAttribute = fromErrorCode()
assertEquals(
errorCodeAttribute.attributeValue,
attributes[errorCodeAttribute.otelAttributeName()]
)
if (errorCode == null) {
assertSuccessful()
} else {
assertError(errorCode)
}
expectedCustomAttributes.forEach { entry ->
assertEquals(entry.value, attributes[entry.key])
}
assertEquals(expectedEvents, events)
assertEquals(private, isPrivate())
assertEquals(key, isKey())
if (key) {
assertIsKeySpan()
} else {
assertNotKeySpan()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ internal class TracingApiTest {
expectedParentId = traceRootSpan.spanId,
expectedTraceId = traceRootSpan.traceId,
expectedStatus = StatusCode.ERROR,
errorCode = ErrorCode.FAILURE,
expectedCustomAttributes = mapOf(Pair("test-attr", "false")),
expectedEvents = listOf(
checkNotNull(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ internal class LogEventData(
val severity: Severity,
val message: String
) {
val attributes = schemaType.attrs.plus(Pair(schemaType.telemetryType.otelAttributeName(), schemaType.telemetryType.attributeValue))
val attributes = schemaType.attrs.plus(schemaType.telemetryType.toOTelKeyValuePair())
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ internal class SpanEventData(
val schemaType: SchemaType,
val spanStartTimeMs: Long
) {
val attributes = schemaType.attrs.plus(Pair(schemaType.telemetryType.otelAttributeName(), schemaType.telemetryType.attributeValue))
val attributes = schemaType.attrs.plus(schemaType.telemetryType.toOTelKeyValuePair())
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ internal class StartSpanData(
val schemaType: SchemaType,
val spanStartTimeMs: Long,
) {
val attributes = schemaType.attrs.plus(Pair(schemaType.telemetryType.otelAttributeName(), schemaType.telemetryType.attributeValue))
val attributes = schemaType.attrs.plus(schemaType.telemetryType.toOTelKeyValuePair())
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,13 @@ internal interface EmbraceAttribute {
*/
val attributeValue: String

/**
* Return the appropriate key name for this attribute to use in an OpenTelemetry attribute
*/
fun otelAttributeName(): String = attributeName.toEmbraceAttributeName()

/**
* Return attribute as a key-value pair appropriate to use as an OpenTelemetry attribute
*/
fun toOTelKeyValuePair() = Pair(otelAttributeName(), attributeValue)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.embrace.android.embracesdk.arch.schema

/**
* Denotes an important span to be aggregated and displayed as such in the platform.
*/
internal object KeySpan : EmbraceAttribute {
override val attributeName: String = "key"
override val attributeValue: String = "true"
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.embrace.android.embracesdk.arch.destination.SpanEventData
import io.embrace.android.embracesdk.arch.schema.AppTerminationCause
import io.embrace.android.embracesdk.arch.schema.EmbType
import io.embrace.android.embracesdk.internal.clock.nanosToMillis
import io.embrace.android.embracesdk.internal.spans.EmbraceSpanImpl.Companion.setEmbraceAttribute
import io.embrace.android.embracesdk.internal.utils.Provider
import io.embrace.android.embracesdk.spans.EmbraceSpan
import io.embrace.android.embracesdk.telemetry.TelemetryService
Expand Down Expand Up @@ -82,10 +83,7 @@ internal class CurrentSessionSpanImpl(
spanRepository.clearCompletedSpans()
sessionSpan.set(startSessionSpan(clock.now().nanosToMillis()))
} else {
endingSessionSpan.addAttribute(
appTerminationCause.otelAttributeName(),
appTerminationCause.attributeValue
)
endingSessionSpan.setEmbraceAttribute(appTerminationCause)
endingSessionSpan.stop()
}
return spanSink.flushSpans()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.embrace.android.embracesdk.internal.spans

import io.embrace.android.embracesdk.arch.schema.EmbraceAttribute
import io.embrace.android.embracesdk.arch.schema.KeySpan
import io.embrace.android.embracesdk.arch.schema.TelemetryType
import io.embrace.android.embracesdk.internal.utils.Provider
import io.embrace.android.embracesdk.spans.EmbraceSpan
Expand Down Expand Up @@ -40,11 +42,6 @@ private const val EMBRACE_USAGE_ATTRIBUTE_NAME_PREFIX = "emb.usage."
*/
private const val SEQUENCE_ID_ATTRIBUTE_NAME = EMBRACE_ATTRIBUTE_NAME_PREFIX + "sequence_id"

/**
* Denotes an important span to be listed in the Spans listing page in the UI. Currently defined as any spans that are the root of a trace
*/
private const val KEY_SPAN_ATTRIBUTE_NAME = EMBRACE_ATTRIBUTE_NAME_PREFIX + "key"

/**
* Denotes a private span logged by Embrace for diagnostic purposes and should not be displayed to customers in the dashboard, but
* should be shown to Embrace employees.
Expand All @@ -70,7 +67,7 @@ internal fun createEmbraceSpanBuilder(
name: String,
type: TelemetryType,
internal: Boolean = true
): SpanBuilder = tracer.embraceSpanBuilder(name, internal).setType(type)
): SpanBuilder = tracer.embraceSpanBuilder(name, internal).setEmbraceAttribute(type)

/**
* Create a [SpanBuilder] that will create a root [Span] with the appropriate custom Embrace attributes
Expand All @@ -83,10 +80,10 @@ internal fun createRootSpanBuilder(
): SpanBuilder = createEmbraceSpanBuilder(tracer = tracer, name = name, type = type, internal = internal).setNoParent()

/**
* Sets and returns the [TelemetryType] attribute for the given [SpanBuilder]
* Sets an [EmbraceAttribute] on the given [SpanBuilder] and return it
*/
internal fun SpanBuilder.setType(value: TelemetryType): SpanBuilder {
setAttribute(value.otelAttributeName(), value.attributeValue)
internal fun SpanBuilder.setEmbraceAttribute(embraceAttribute: EmbraceAttribute): SpanBuilder {
setAttribute(embraceAttribute.otelAttributeName(), embraceAttribute.attributeValue)
return this
}

Expand All @@ -95,7 +92,7 @@ internal fun SpanBuilder.setType(value: TelemetryType): SpanBuilder {
* for any spans that are the root of a trace.
*/
internal fun SpanBuilder.makeKey(): SpanBuilder {
setAttribute(KEY_SPAN_ATTRIBUTE_NAME, true)
setEmbraceAttribute(KeySpan)
return this
}

Expand Down Expand Up @@ -172,6 +169,10 @@ internal fun Span.setSequenceId(id: Long): Span {
return this
}

internal fun Span.setEmbraceAttribute(embraceAttribute: EmbraceAttribute) {
setAttribute(embraceAttribute.otelAttributeName(), embraceAttribute.attributeValue)
}

/**
* Ends the given [Span], and setting the correct properties per the optional [ErrorCode] passed in. If [errorCode]
* is not specified, it means the [Span] completed successfully, and no [ErrorCode] will be set.
Expand All @@ -181,8 +182,7 @@ internal fun Span.endSpan(errorCode: ErrorCode? = null, endTimeMs: Long? = null)
setStatus(StatusCode.OK)
} else {
setStatus(StatusCode.ERROR)
val errorCodeAttribute = errorCode.fromErrorCode()
setAttribute(errorCodeAttribute.otelAttributeName(), errorCodeAttribute.attributeValue)
setEmbraceAttribute(errorCode.fromErrorCode())
}

if (endTimeMs != null) {
Expand All @@ -204,11 +204,6 @@ internal fun AttributesBuilder.fromMap(attributes: Map<String, String>): Attribu
return this
}

/**
* Returns true if the Span is a Key Span
*/
internal fun EmbraceSpanData.isKey(): Boolean = attributes[KEY_SPAN_ATTRIBUTE_NAME] == true.toString()

/**
* Returns true if the Span is private
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.embrace.android.embracesdk.internal.spans

import io.embrace.android.embracesdk.arch.schema.EmbraceAttribute
import io.embrace.android.embracesdk.internal.clock.normalizeTimestampAsMillis
import io.embrace.android.embracesdk.spans.EmbraceSpan
import io.embrace.android.embracesdk.spans.EmbraceSpanEvent
Expand Down Expand Up @@ -144,5 +145,10 @@ internal class EmbraceSpanImpl(

internal fun attributeValid(key: String, value: String) =
key.length <= MAX_ATTRIBUTE_KEY_LENGTH && value.length <= MAX_ATTRIBUTE_VALUE_LENGTH

internal fun EmbraceSpan.setEmbraceAttribute(embraceAttribute: EmbraceAttribute): EmbraceSpan {
addAttribute(embraceAttribute.otelAttributeName(), embraceAttribute.attributeValue)
return this
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package io.embrace.android.embracesdk.arch

import io.embrace.android.embracesdk.arch.destination.LogEventData
import io.embrace.android.embracesdk.arch.destination.StartSpanData
import io.embrace.android.embracesdk.arch.schema.EmbType
import io.embrace.android.embracesdk.arch.schema.EmbraceAttribute
import io.embrace.android.embracesdk.arch.schema.ErrorCodeAttribute
import io.embrace.android.embracesdk.arch.schema.KeySpan
import io.embrace.android.embracesdk.arch.schema.TelemetryType
import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData
import io.embrace.android.embracesdk.spans.ErrorCode
import io.opentelemetry.api.trace.StatusCode
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertNull
import org.junit.Assert.assertFalse

/**
* Assert [EmbraceSpanData] is of type [EmbType.Performance.Default]
*/
internal fun EmbraceSpanData.assertIsTypePerformance() = assertIsType(EmbType.Performance.Default)

/**
* Assert [EmbraceSpanData] is of type [telemetryType]
*/
internal fun EmbraceSpanData.assertIsType(telemetryType: TelemetryType) = assertHasEmbraceAttribute(telemetryType)

internal fun EmbraceSpanData.assertIsKeySpan() = assertHasEmbraceAttribute(KeySpan)

internal fun EmbraceSpanData.assertNotKeySpan() = assertDoesNotHaveEmbraceAttribute(KeySpan)

/**
* Assert [EmbraceSpanData] has the [EmbraceAttribute] defined by [embraceAttribute]
*/
internal fun EmbraceSpanData.assertHasEmbraceAttribute(embraceAttribute: EmbraceAttribute) {
assertEquals(embraceAttribute.attributeValue, attributes[embraceAttribute.otelAttributeName()])
}

internal fun EmbraceSpanData.assertDoesNotHaveEmbraceAttribute(embraceAttribute: EmbraceAttribute) {
assertFalse(attributes[embraceAttribute.otelAttributeName()]?.equals(embraceAttribute.attributeValue) ?: false)
}

/**
* Assert [EmbraceSpanData] has ended with the error defined by [errorCode]
*/
internal fun EmbraceSpanData.assertError(errorCode: ErrorCode) {
assertEquals(StatusCode.ERROR, status)
assertHasEmbraceAttribute(errorCode.fromErrorCode())
}

/**
* Assert [EmbraceSpanData] has ended successfully
*/
internal fun EmbraceSpanData.assertSuccessful() {
assertEquals(StatusCode.OK, status)
assertNull(attributes[ErrorCodeAttribute.Failure.otelAttributeName()])
}

/**
* Assert [StartSpanData] is of type [telemetryType]
*/
internal fun StartSpanData.assertIsType(telemetryType: TelemetryType) = assertHasEmbraceAttribute(telemetryType)

/**
* Assert [StartSpanData] has the [EmbraceAttribute] defined by [embraceAttribute]
*/
internal fun StartSpanData.assertHasEmbraceAttribute(embraceAttribute: EmbraceAttribute) {
assertEquals(embraceAttribute.attributeValue, attributes[embraceAttribute.otelAttributeName()])
}

/**
* Assert [LogEventData] is of type [telemetryType]
*/
internal fun LogEventData.assertIsType(telemetryType: TelemetryType) = assertHasEmbraceAttribute(telemetryType)

/**
* Assert [LogEventData] has the [EmbraceAttribute] defined by [embraceAttribute]
*/
internal fun LogEventData.assertHasEmbraceAttribute(embraceAttribute: EmbraceAttribute) {
assertEquals(embraceAttribute.attributeValue, attributes[embraceAttribute.otelAttributeName()])
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ internal class SpanDataSourceKtTest {
SchemaType.ViewBreadcrumb("my-view"),
1500000000000
)
assertEquals(EmbType.Ux.View.attributeValue, data.attributes[EmbType.Ux.View.otelAttributeName()])
data.assertIsType(EmbType.Ux.View)
assertEquals("my-view", data.attributes["view.name"])

val span = service.startSpanCapture("") { data }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.app.ActivityManager
import android.app.ApplicationExitInfo
import com.google.common.util.concurrent.MoreExecutors
import io.embrace.android.embracesdk.Severity
import io.embrace.android.embracesdk.arch.assertIsType
import io.embrace.android.embracesdk.arch.schema.EmbType
import io.embrace.android.embracesdk.config.remote.AppExitInfoConfig
import io.embrace.android.embracesdk.config.remote.RemoteConfig
Expand Down Expand Up @@ -361,7 +362,7 @@ internal class AeiDataSourceImplTest {
val logEventData = logWriter.logEvents.single()
assertEquals("aei-record", logEventData.schemaType.name)
assertEquals(Severity.INFO, logEventData.severity)
assertEquals(EmbType.System.Exit.attributeValue, logEventData.attributes[EmbType.System.Exit.otelAttributeName()])
logEventData.assertIsType(EmbType.System.Exit)
return logEventData.attributes
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ internal class CustomBreadcrumbDataSourceTest {
assertEquals(15000000000.millisToNanos(), spanStartTimeMs)
assertEquals(
mapOf(
EmbType.System.Breadcrumb.otelAttributeName() to EmbType.System.Breadcrumb.attributeValue,
EmbType.System.Breadcrumb.toOTelKeyValuePair(),
"message" to "Hello, world!"
),
attributes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal class FragmentBreadcrumbDataSourceTest {
assertEquals(
mapOf(
"view.name" to "my_fragment",
EmbType.Ux.View.otelAttributeName() to EmbType.Ux.View.attributeValue,
EmbType.Ux.View.toOTelKeyValuePair(),
),
span.attributes
)
Expand All @@ -59,7 +59,7 @@ internal class FragmentBreadcrumbDataSourceTest {
assertEquals(
mapOf(
"view.name" to "my_fragment",
EmbType.Ux.View.otelAttributeName() to EmbType.Ux.View.attributeValue,
EmbType.Ux.View.toOTelKeyValuePair()
),
span.attributes
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.embrace.android.embracesdk.capture.startup

import io.embrace.android.embracesdk.arch.schema.EmbType
import io.embrace.android.embracesdk.arch.assertIsTypePerformance
import io.embrace.android.embracesdk.concurrency.BlockableExecutorService
import io.embrace.android.embracesdk.fakes.FakeClock
import io.embrace.android.embracesdk.fakes.injection.FakeInitModule
Expand Down Expand Up @@ -50,10 +50,7 @@ internal class StartupServiceImplTest {
assertEquals(SpanId.getInvalid(), parentSpanId)
assertEquals(startTimeMillis, startTimeNanos.nanosToMillis())
assertEquals(endTimeMillis, endTimeNanos.nanosToMillis())
assertEquals(
EmbType.Performance.Default.attributeValue,
attributes[EmbType.Performance.Default.otelAttributeName()]
)
assertIsTypePerformance()
assertTrue(isPrivate())
assertEquals(StatusCode.OK, status)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ internal val testSpan = EmbraceSpanData(
),
attributes = mapOf(
Pair("emb.sequence_id", "3"),
EmbType.Performance.Default.otelAttributeName() to EmbType.Performance.Default.attributeValue,
EmbType.Performance.Default.toOTelKeyValuePair(),
)
)

Expand Down
Loading

0 comments on commit 7c19640

Please sign in to comment.