diff --git a/.brazil.json b/.brazil.json index dbb2135d1b..56055e11d1 100644 --- a/.brazil.json +++ b/.brazil.json @@ -7,6 +7,7 @@ "com.squareup.okhttp3:okhttp:5.*": "OkHttp3-5.x", "com.squareup.okio:okio-jvm:3.*": "OkioJvm-3.x", "io.opentelemetry:opentelemetry-api:1.*": "Maven-io-opentelemetry_opentelemetry-api-1.x", + "io.opentelemetry:opentelemetry-extension-kotlin:1.*": "Maven-io-opentelemetry_opentelemetry-extension-kotlin-1.x", "org.slf4j:slf4j-api:2.*": "Maven-org-slf4j_slf4j-api-2.x", "aws.sdk.kotlin.crt:aws-crt-kotlin:0.9.*": "AwsCrtKotlin-0.9.x", "aws.sdk.kotlin.crt:aws-crt-kotlin:0.8.*": "AwsCrtKotlin-0.8.x", diff --git a/.changes/ec884c41-7bda-4033-a80b-5da20836a64b.json b/.changes/ec884c41-7bda-4033-a80b-5da20836a64b.json new file mode 100644 index 0000000000..78aff6e839 --- /dev/null +++ b/.changes/ec884c41-7bda-4033-a80b-5da20836a64b.json @@ -0,0 +1,8 @@ +{ + "id": "ec884c41-7bda-4033-a80b-5da20836a64b", + "type": "bugfix", + "description": "Fix OpenTelemetry span concurrency by using Span.asContextElement() instead of Span.makeCurrent()", + "issues": [ + "https://github.com/smithy-lang/smithy-kotlin/issues/1211" + ] +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bdc0c8e98b..235d2a5266 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -57,6 +57,7 @@ okhttp4 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp4-versi okhttp-coroutines = { module = "com.squareup.okhttp3:okhttp-coroutines", version.ref = "okhttp-version" } opentelemetry-api = { module = "io.opentelemetry:opentelemetry-api", version.ref = "otel-version" } opentelemetry-sdk-testing = {module = "io.opentelemetry:opentelemetry-sdk-testing", version.ref = "otel-version" } +opentelemetry-kotlin-extension = { module = "io.opentelemetry:opentelemetry-extension-kotlin", version.ref = "otel-version" } slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-version" } slf4j-api-v1x = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-v1x-version" } slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j-version" } diff --git a/runtime/observability/telemetry-api/api/telemetry-api.api b/runtime/observability/telemetry-api/api/telemetry-api.api index 33adef236a..cf14c4c7c0 100644 --- a/runtime/observability/telemetry-api/api/telemetry-api.api +++ b/runtime/observability/telemetry-api/api/telemetry-api.api @@ -364,6 +364,7 @@ public final class aws/smithy/kotlin/runtime/telemetry/metrics/UpDownCounter$Def public abstract class aws/smithy/kotlin/runtime/telemetry/trace/AbstractTraceSpan : aws/smithy/kotlin/runtime/telemetry/trace/TraceSpan { public fun ()V + public fun asContextElement ()Lkotlin/coroutines/CoroutineContext; public fun close ()V public fun emitEvent (Ljava/lang/String;Laws/smithy/kotlin/runtime/collections/Attributes;)V public fun getSpanContext ()Laws/smithy/kotlin/runtime/telemetry/trace/SpanContext; @@ -424,6 +425,7 @@ public final class aws/smithy/kotlin/runtime/telemetry/trace/SpanStatus : java/l public abstract interface class aws/smithy/kotlin/runtime/telemetry/trace/TraceSpan : aws/smithy/kotlin/runtime/telemetry/context/Scope { public static final field Companion Laws/smithy/kotlin/runtime/telemetry/trace/TraceSpan$Companion; + public abstract fun asContextElement ()Lkotlin/coroutines/CoroutineContext; public abstract fun close ()V public abstract fun emitEvent (Ljava/lang/String;Laws/smithy/kotlin/runtime/collections/Attributes;)V public abstract fun getSpanContext ()Laws/smithy/kotlin/runtime/telemetry/trace/SpanContext; @@ -437,6 +439,7 @@ public final class aws/smithy/kotlin/runtime/telemetry/trace/TraceSpan$Companion } public final class aws/smithy/kotlin/runtime/telemetry/trace/TraceSpan$DefaultImpls { + public static fun asContextElement (Laws/smithy/kotlin/runtime/telemetry/trace/TraceSpan;)Lkotlin/coroutines/CoroutineContext; public static synthetic fun emitEvent$default (Laws/smithy/kotlin/runtime/telemetry/trace/TraceSpan;Ljava/lang/String;Laws/smithy/kotlin/runtime/collections/Attributes;ILjava/lang/Object;)V } diff --git a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/AbstractTraceSpan.kt b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/AbstractTraceSpan.kt index fbc6369ec3..1892098b7b 100644 --- a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/AbstractTraceSpan.kt +++ b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/AbstractTraceSpan.kt @@ -6,6 +6,8 @@ package aws.smithy.kotlin.runtime.telemetry.trace import aws.smithy.kotlin.runtime.collections.AttributeKey import aws.smithy.kotlin.runtime.collections.Attributes +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext /** * An abstract implementation of a trace span. By default, this class uses no-op implementations for all members unless @@ -18,4 +20,5 @@ public abstract class AbstractTraceSpan : TraceSpan { override operator fun set(key: AttributeKey, value: T) { } override fun mergeAttributes(attributes: Attributes) { } override fun close() { } + override fun asContextElement(): CoroutineContext = EmptyCoroutineContext } diff --git a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/CoroutineContextTraceExt.kt b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/CoroutineContextTraceExt.kt index cb6795fe9c..73e84dd756 100644 --- a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/CoroutineContextTraceExt.kt +++ b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/CoroutineContextTraceExt.kt @@ -71,7 +71,7 @@ public suspend inline fun withSpan( // or else traces may be disconnected from their parent val updatedCtx = coroutineContext[TelemetryProviderContext]?.provider?.contextManager?.current() val telemetryCtxElement = (updatedCtx?.let { TelemetryContextElement(it) } ?: coroutineContext[TelemetryContextElement]) ?: EmptyCoroutineContext - withContext(context + TraceSpanContext(span) + telemetryCtxElement) { + withContext(context + TraceSpanContext(span) + telemetryCtxElement + span.asContextElement()) { block(span) } } catch (ex: Exception) { diff --git a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/TraceSpan.kt b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/TraceSpan.kt index d3c7972a85..78a92e6057 100644 --- a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/TraceSpan.kt +++ b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/TraceSpan.kt @@ -9,6 +9,8 @@ import aws.smithy.kotlin.runtime.collections.AttributeKey import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.telemetry.context.Scope +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext /** * Represents a single operation/task within a trace. Each trace contains a root span and @@ -27,6 +29,11 @@ public interface TraceSpan : Scope { */ public val spanContext: SpanContext + /** + * A representation of this span as a [CoroutineContext] element + */ + public fun asContextElement(): CoroutineContext = EmptyCoroutineContext + /** * Set an attribute on the span * @param key the attribute key to use diff --git a/runtime/observability/telemetry-provider-otel/build.gradle.kts b/runtime/observability/telemetry-provider-otel/build.gradle.kts index 8792275766..bdee5b1d6a 100644 --- a/runtime/observability/telemetry-provider-otel/build.gradle.kts +++ b/runtime/observability/telemetry-provider-otel/build.gradle.kts @@ -18,6 +18,7 @@ kotlin { jvmMain { dependencies { api(libs.opentelemetry.api) + api(libs.opentelemetry.kotlin.extension) } } all { diff --git a/runtime/observability/telemetry-provider-otel/jvm/src/aws/smithy/kotlin/runtime/telemetry/otel/OtelTracerProvider.kt b/runtime/observability/telemetry-provider-otel/jvm/src/aws/smithy/kotlin/runtime/telemetry/otel/OtelTracerProvider.kt index cf6eb39dfd..20ef72f139 100644 --- a/runtime/observability/telemetry-provider-otel/jvm/src/aws/smithy/kotlin/runtime/telemetry/otel/OtelTracerProvider.kt +++ b/runtime/observability/telemetry-provider-otel/jvm/src/aws/smithy/kotlin/runtime/telemetry/otel/OtelTracerProvider.kt @@ -10,6 +10,8 @@ import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.telemetry.context.Context import aws.smithy.kotlin.runtime.telemetry.trace.* import io.opentelemetry.api.OpenTelemetry +import io.opentelemetry.extension.kotlin.asContextElement +import kotlin.coroutines.CoroutineContext import io.opentelemetry.api.trace.Span as OtelSpan import io.opentelemetry.api.trace.SpanContext as OtelSpanContext import io.opentelemetry.api.trace.SpanKind as OtelSpanKind @@ -62,11 +64,11 @@ private class OtelSpanContextImpl(private val otelSpanContext: OtelSpanContext) internal class OtelTraceSpanImpl( private val otelSpan: OtelSpan, ) : TraceSpan { - - private val spanScope = otelSpan.makeCurrent() - override val spanContext: SpanContext get() = OtelSpanContextImpl(otelSpan.spanContext) + + override fun asContextElement(): CoroutineContext = otelSpan.asContextElement() + override fun set(key: AttributeKey, value: T) { key.otelAttrKeyOrNull(value)?.let { otelKey -> otelSpan.setAttribute(otelKey, value) @@ -90,7 +92,6 @@ internal class OtelTraceSpanImpl( override fun close() { otelSpan.end() - spanScope.close() } }