Skip to content

Commit

Permalink
add span payload models
Browse files Browse the repository at this point in the history
  • Loading branch information
fractalwrench committed Mar 8, 2024
1 parent a7b954f commit 5c198c4
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.embrace.android.embracesdk.internal.payload

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

/**
* A key-value pair that provides additional context to the span
*
* @param key The name of the attribute
* @param data The value of the attribute
*/
@JsonClass(generateAdapter = true)
internal data class Attribute(

/* The name of the attribute */
@Json(name = "key")
val key: String? = null,

/* The value of the attribute */
@Json(name = "value")
val data: String? = null
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package io.embrace.android.embracesdk.internal.payload

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

/**
* A span represents a single unit of work done in the app. It can be a network request, a database
* query, a view transition, etc. It has a start time, an end time, and attributes that describe it.
*
* @param traceId The ID of the trace that this span is part of
* @param spanId A value that uniquely identifies a span instance
* @param parentSpanId A value that uniquely identifies the parent span
* @param name The name of the span
* @param startTimeUnixNano The time the span started, in nanoseconds since the Unix epoch
* @param endTimeUnixNano The time the span ended, in nanoseconds since the Unix epoch
* @param status The status of the span. Can be one of 'Unset', 'Error', or 'Ok'
* @param events
* @param attributes
*/
@JsonClass(generateAdapter = true)
internal data class Span(

/* The ID of the trace that this span is part of */
@Json(name = "trace_id")
val traceId: String? = null,

/* A value that uniquely identifies a span instance */
@Json(name = "span_id")
val spanId: String? = null,

/* A value that uniquely identifies the parent span */
@Json(name = "parent_span_id")
val parentSpanId: String? = null,

/* The name of the span */
@Json(name = "name")
val name: String? = null,

/* The time the span started, in nanoseconds since the Unix epoch */
@Json(name = "start_time_unix_nano")
val startTimeUnixNano: Long? = null,

/* The time the span ended, in nanoseconds since the Unix epoch */
@Json(name = "end_time_unix_nano")
val endTimeUnixNano: Long? = null,

/* The status of the span. Can be one of 'Unset', 'Error', or 'Ok' */
@Json(name = "status")
val status: Status? = null,

@Json(name = "events")
val events: List<SpanEvent>? = null,

@Json(name = "attributes")
val attributes: List<Attribute>? = null
) {

/**
* The status of the span. Can be one of 'Unset', 'Error', or 'Ok'
*
* Values: UNSET,ERROR,OK
*/
internal enum class Status(val value: String) {
@Json(name = "Unset")
UNSET("Unset"),

@Json(name = "Error")
ERROR("Error"),

@Json(name = "Ok")
OK("Ok")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.embrace.android.embracesdk.internal.payload

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

/**
* An event that occurred during a span
*
* @param name The name of the event
* @param timeUnixNano The time the event occurred, in nanoseconds since the Unix epoch
* @param attributes
*/
@JsonClass(generateAdapter = true)
internal data class SpanEvent(

/* The name of the event */
@Json(name = "name")
val name: String? = null,

/* The time the event occurred, in nanoseconds since the Unix epoch */
@Json(name = "time_unix_nano")
val timeUnixNano: Long? = null,

@Json(name = "attributes")
val attributes: List<Attribute>? = null
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.embrace.android.embracesdk.internal.payload

import io.opentelemetry.api.common.Attributes
import io.opentelemetry.api.trace.StatusCode
import io.opentelemetry.sdk.trace.data.EventData
import io.opentelemetry.sdk.trace.data.SpanData

internal fun SpanData.toNewPayload() = Span(
traceId = traceId,
spanId = spanId,
parentSpanId = parentSpanId,
name = name,
startTimeUnixNano = startEpochNanos,
endTimeUnixNano = endEpochNanos,
status = when (status.statusCode) {
StatusCode.UNSET -> Span.Status.UNSET
StatusCode.OK -> Span.Status.OK
StatusCode.ERROR -> Span.Status.ERROR
else -> Span.Status.UNSET

Check warning on line 19 in embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/payload/SpanMapper.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/payload/SpanMapper.kt#L17-L19

Added lines #L17 - L19 were not covered by tests
},
events = events.map(EventData::toNewPayload),
attributes = attributes.toNewPayload()
)

internal fun EventData.toNewPayload() = SpanEvent(
name = name,
timeUnixNano = epochNanos,
attributes = attributes.toNewPayload()
)

internal fun Attributes.toNewPayload(): List<Attribute> =
asMap().map { (key, value) -> Attribute(key.key, value.toString()) }
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.embrace.android.embracesdk.fakes

import io.opentelemetry.api.common.Attributes
import io.opentelemetry.api.trace.SpanContext
import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo
import io.opentelemetry.sdk.resources.Resource
import io.opentelemetry.sdk.trace.data.EventData
import io.opentelemetry.sdk.trace.data.LinkData
import io.opentelemetry.sdk.trace.data.SpanData
import io.opentelemetry.sdk.trace.data.StatusData

internal class FakeSpanData(
private var name: String = "fake-span",
private var kind: SpanKind = SpanKind.INTERNAL,
private var spanContext: SpanContext = SpanContext.getInvalid(),
private var parentSpanContext: SpanContext = SpanContext.getInvalid(),
private var status: StatusData = StatusData.unset(),
private var startEpochNanos: Long = 0L,
private var attributes: Attributes = Attributes.builder().put("my-key", "my-value").build(),
private var events: MutableList<EventData> = mutableListOf(
EventData.create(
0L,
"fake-event",
Attributes.builder().put("my-key", "my-value").build()
)
),
private var links: MutableList<LinkData> = mutableListOf(),
private var endEpochNanos: Long = 0L,
private var hasEnded: Boolean = true,
private var resource: Resource = Resource.empty()
) : SpanData {
override fun getName(): String = name
override fun getKind(): SpanKind = kind
override fun getSpanContext(): SpanContext = spanContext
override fun getParentSpanContext(): SpanContext = parentSpanContext
override fun getStatus(): StatusData = status
override fun getStartEpochNanos(): Long = startEpochNanos
override fun getAttributes(): Attributes = attributes
override fun getEvents(): MutableList<EventData> = events
override fun getLinks(): MutableList<LinkData> = links
override fun getEndEpochNanos(): Long = endEpochNanos
override fun hasEnded(): Boolean = hasEnded
override fun getTotalRecordedEvents(): Int = events.size
override fun getTotalRecordedLinks(): Int = links.size
override fun getTotalAttributeCount(): Int = attributes.size()
override fun getInstrumentationLibraryInfo() = InstrumentationLibraryInfo.empty()
override fun getResource(): Resource = resource
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.embrace.android.embracesdk.internal.payload

import io.embrace.android.embracesdk.fakes.FakeSpanData
import io.opentelemetry.sdk.trace.data.SpanData
import org.junit.Assert.assertEquals
import org.junit.Test

internal class SpanMapperTest {

@Test
fun toSpan() {
val input: SpanData = FakeSpanData()
val output = input.toNewPayload()

assertEquals(input.traceId, output.traceId)
assertEquals(input.spanId, output.spanId)
assertEquals(input.parentSpanId, output.parentSpanId)
assertEquals(input.name, output.name)
assertEquals(input.startEpochNanos, output.startTimeUnixNano)
assertEquals(input.endEpochNanos, output.endTimeUnixNano)
assertEquals(input.status.statusCode.name, checkNotNull(output.status).name)

// validate event copied
val inputEvent = input.events.single()
val outputEvent = checkNotNull(output.events).single()
assertEquals(inputEvent.name, outputEvent.name)
assertEquals(inputEvent.epochNanos, outputEvent.timeUnixNano)

// test event attributes
val inputEventAttrs = checkNotNull(inputEvent.attributes?.asMap())
val outputEventAttrs = checkNotNull(outputEvent.attributes?.single())
assertEquals(inputEventAttrs.keys.single().key, outputEventAttrs.key)
assertEquals(inputEventAttrs.values.single(), outputEventAttrs.data)

// test attributes
val inputAttrs = checkNotNull(input.attributes?.asMap())
val outputAttrs = checkNotNull(output.attributes?.single())
assertEquals(inputAttrs.keys.single().key, outputAttrs.key)
assertEquals(inputAttrs.values.single(), outputAttrs.data)
}
}

0 comments on commit 5c198c4

Please sign in to comment.