August Lilleaas
11/26/2024, 9:58 AMPiotr Krzemiński
11/30/2024, 3:36 PMAugust Lilleaas
12/01/2024, 9:24 PMAugust Lilleaas
12/01/2024, 9:24 PMDominik Sandjaja
12/02/2024, 12:47 PMimport io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.api.common.Attributes
import io.opentelemetry.api.trace.Span
import io.opentelemetry.api.trace.SpanBuilder
import io.opentelemetry.api.trace.StatusCode
import io.opentelemetry.api.trace.Tracer
import io.opentelemetry.extension.kotlin.asContextElement
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.withContext
import kotlin.coroutines.cancellation.CancellationException
import kotlin.coroutines.coroutineContext
/**
* Executes [block] in a tracing span with optional SpanBuilder [parameters].
*
* [parameters] example: `parameters = { setParent(parentContext); addLink(span1.spanContext) }`
*
* The span will be
* - a child of a parent context, if set via [parameters], or
* - a child of the current span (from the current coroutine context), or
* - a top-level span.
*
* Guidelines:
* - [Trace Semantic Conventions](<<https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/>>)
* - [Attribute Naming](<<https://opentelemetry.io/docs/reference/specification/common/attribute-naming/>>)
*/
suspend fun <Result> withSpan(
tracer: Tracer,
name: String,
parameters: (SpanBuilder.() -> Unit)? = null,
exceptionIsError: (Throwable) -> Boolean = { it !is CancellationException },
block: suspend (span: Span?) -> Result,
): Result {
val span: Span = tracer.spanBuilder(name).run {
if (parameters != null) {
parameters()
}
coroutineContext[CoroutineName]?.let {
setAttribute("coroutine.name", it.name)
}
startSpan()
}
return withContext(span.asContextElement()) {
try {
block(span).also {
span.setStatus(StatusCode.OK)
}
} catch (throwable: Throwable) {
if (exceptionIsError(throwable)) {
span.setStatus(StatusCode.ERROR)
span.recordException(throwable)
} else {
span.addEvent(
"Completed with exception",
Attributes.of(
AttributeKey.stringKey("exception.type"),
throwable.javaClass.name,
AttributeKey.stringKey("exception.message"),
(throwable.message ?: "(none)"),
),
)
span.setStatus(StatusCode.OK)
}
throw throwable
} finally {
span.end()
}
}
}
And the usage:
val tracer = GlobalOpenTelemetry.get().getTracer("somename", "1.0.0")
val someData = withSpan(tracer, "fetching data", { setAttribute("someId", id) }) {
myService.findById(id)
?: throw InvalidArgumentException(message = "No data found for the provided id.")
}
In Sentry (which we use) this looks like this:August Lilleaas
12/02/2024, 12:47 PMAugust Lilleaas
12/02/2024, 12:48 PMDominik Sandjaja
12/02/2024, 12:50 PMAugust Lilleaas
12/02/2024, 12:50 PM