diff --git a/extensions/opentelemetry/runtime/pom.xml b/extensions/opentelemetry/runtime/pom.xml index 57677019dddab8..7befd1be1b261e 100644 --- a/extensions/opentelemetry/runtime/pom.xml +++ b/extensions/opentelemetry/runtime/pom.xml @@ -177,6 +177,11 @@ assertj-core test + + org.mockito + mockito-core + test + diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryUtil.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryUtil.java index 6ab6ffac431124..f5ffef2b15eb02 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryUtil.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryUtil.java @@ -1,9 +1,12 @@ package io.quarkus.opentelemetry.runtime; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; @@ -16,6 +19,7 @@ public final class OpenTelemetryUtil { public static final String SPAN_ID = "spanId"; public static final String SAMPLED = "sampled"; public static final String PARENT_ID = "parentId"; + private static final Set SPAN_DATA_KEYS = Set.of(TRACE_ID, SPAN_ID, SAMPLED, PARENT_ID); private OpenTelemetryUtil() { } @@ -54,22 +58,43 @@ public static Map convertKeyValueListToMap(List headers) * @param vertxContext vertx context */ public static void setMDCData(Context context, io.vertx.core.Context vertxContext) { + setMDCData(getSpanData(context), vertxContext); + } + + public static void setMDCData(Map spanData, io.vertx.core.Context vertxContext) { + if (spanData != null && !spanData.isEmpty()) { + for (Entry entry : spanData.entrySet()) { + if (SPAN_DATA_KEYS.contains(entry.getKey())) { + VertxMDC.INSTANCE.put(entry.getKey(), entry.getValue(), vertxContext); + } + } + } + } + + /** + * Gets current span data from the MDC context. + * + * @param context opentelemetry context + */ + public static Map getSpanData(Context context) { + if (context == null) { + return Collections.emptyMap(); + } Span span = Span.fromContextOrNull(context); + Map spanData = new HashMap<>(); if (span != null) { SpanContext spanContext = span.getSpanContext(); - VertxMDC vertxMDC = VertxMDC.INSTANCE; - vertxMDC.put(SPAN_ID, spanContext.getSpanId(), vertxContext); - vertxMDC.put(TRACE_ID, spanContext.getTraceId(), vertxContext); - vertxMDC.put(SAMPLED, Boolean.toString(spanContext.isSampled()), vertxContext); + spanData.put(SPAN_ID, spanContext.getSpanId()); + spanData.put(TRACE_ID, spanContext.getTraceId()); + spanData.put(SAMPLED, Boolean.toString(spanContext.isSampled())); if (span instanceof ReadableSpan) { SpanContext parentSpanContext = ((ReadableSpan) span).getParentSpanContext(); - if (parentSpanContext.isValid()) { - vertxMDC.put(PARENT_ID, parentSpanContext.getSpanId(), vertxContext); - } else { - vertxMDC.remove(PARENT_ID, vertxContext); + if (parentSpanContext != null && parentSpanContext.isValid()) { + spanData.put(PARENT_ID, parentSpanContext.getSpanId()); } } } + return spanData; } /** diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/QuarkusContextStorage.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/QuarkusContextStorage.java index 7a30e5bcdae2df..ebba2e69aee641 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/QuarkusContextStorage.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/QuarkusContextStorage.java @@ -3,6 +3,8 @@ import static io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle.setContextSafe; import static io.smallrye.common.vertx.VertxContext.isDuplicatedContext; +import java.util.Map; + import org.jboss.logging.Logger; import io.opentelemetry.context.Context; @@ -64,14 +66,18 @@ public Scope attach(io.vertx.core.Context vertxContext, Context toAttach) { return Scope.noop(); } vertxContext.putLocal(OTEL_CONTEXT, toAttach); - OpenTelemetryUtil.setMDCData(toAttach, vertxContext); + final Map spanDataToAttach = OpenTelemetryUtil.getSpanData(toAttach); + OpenTelemetryUtil.setMDCData(spanDataToAttach, vertxContext); return new Scope() { @Override public void close() { - if (getContext(vertxContext) != toAttach) { - log.warn("Context in storage not the expected context, Scope.close was not called correctly"); + final Context before = getContext(vertxContext); + if (before != toAttach) { + log.warn("Context in storage not the expected context, Scope.close was not called correctly. Details:" + + " OTel context before: " + OpenTelemetryUtil.getSpanData(before) + + ". OTel context toAttach: " + spanDataToAttach); } if (beforeAttach == null) { diff --git a/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/OpenTelemetryUtilTest.java b/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/OpenTelemetryUtilTest.java index f1842ccc980a57..eebe37eee8a85f 100644 --- a/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/OpenTelemetryUtilTest.java +++ b/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/OpenTelemetryUtilTest.java @@ -1,5 +1,8 @@ package io.quarkus.opentelemetry.runtime; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + import java.util.AbstractMap; import java.util.Arrays; import java.util.Collections; @@ -8,6 +11,13 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.SpanProcessor; + public class OpenTelemetryUtilTest { @Test @@ -58,4 +68,54 @@ public void testConvertKeyValueListToMap_empty_value() { .convertKeyValueListToMap(Collections.emptyList()); Assertions.assertThat(actual).containsExactly(); } + + @Test + public void testGetSpanData() { + SpanProcessor mockedSpanProcessor = mock(SpanProcessor.class); + + SdkTracerProvider tracerSdkFactory = SdkTracerProvider.builder() + .addSpanProcessor(mockedSpanProcessor) + .build(); + Tracer spanBuilderSdkTest = tracerSdkFactory.get("SpanBuilderSdkTest"); + SpanBuilder spanBuilder = spanBuilderSdkTest.spanBuilder("SpanName"); + + Span parent = spanBuilder.startSpan(); + Context contextParent = Context.current().with(parent); + + Span child = spanBuilder.setParent(contextParent).startSpan(); + Context contextChild = Context.current().with(child); + + Map actual = OpenTelemetryUtil.getSpanData(contextChild); + assertEquals(4, actual.size()); + assertEquals(child.getSpanContext().getSpanId(), actual.get("spanId")); + assertEquals(child.getSpanContext().getTraceId(), actual.get("traceId")); + assertEquals("true", actual.get("sampled")); + assertEquals(parent.getSpanContext().getSpanId(), actual.get("parentId")); + } + + @Test + public void testGetSpanData_noParent() { + SpanProcessor mockedSpanProcessor = mock(SpanProcessor.class); + SdkTracerProvider tracerSdkFactory = SdkTracerProvider.builder() + .addSpanProcessor(mockedSpanProcessor) + .build(); + Tracer spanBuilderSdkTest = tracerSdkFactory.get("SpanBuilderSdkTest"); + + SpanBuilder spanBuilder = spanBuilderSdkTest.spanBuilder("SpanName"); + + Span child = spanBuilder.startSpan(); + Context contextChild = Context.current().with(child); + + Map actual = OpenTelemetryUtil.getSpanData(contextChild); + assertEquals(3, actual.size()); + assertEquals(child.getSpanContext().getSpanId(), actual.get("spanId")); + assertEquals(child.getSpanContext().getTraceId(), actual.get("traceId")); + assertEquals("true", actual.get("sampled")); + } + + @Test + public void testGetSpanData_nullValue() { + Map actual = OpenTelemetryUtil.getSpanData(null); + assertEquals(0, actual.size()); + } }