From 7c0e814fbf93133bb03297aec15bf6cc7b01f6e8 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 25 Oct 2025 10:18:20 -0700 Subject: [PATCH 1/3] Add test --- .../opentelemetryapi/TracerTest.java | 96 +++++++++++++++++++ .../testing/internal/TelemetryConverter.java | 4 +- 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/instrumentation/opentelemetry-api/opentelemetry-api-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/opentelemetryapi/TracerTest.java b/instrumentation/opentelemetry-api/opentelemetry-api-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/opentelemetryapi/TracerTest.java index e954442b338a..f09a8674d83b 100644 --- a/instrumentation/opentelemetry-api/opentelemetry-api-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/opentelemetryapi/TracerTest.java +++ b/instrumentation/opentelemetry-api/opentelemetry-api-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/opentelemetryapi/TracerTest.java @@ -20,12 +20,14 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.trace.data.LinkData; import io.opentelemetry.sdk.trace.data.StatusData; import java.io.PrintWriter; import java.io.StringWriter; @@ -322,4 +324,98 @@ void testTracerBuilder() { .hasInstrumentationScopeInfo( InstrumentationScopeInfo.builder("test").setVersion("1.2.3").build()))); } + + @Test + @DisplayName("capture span link without attributes") + void captureSpanLinkWithoutAttributes() { + // When + Tracer tracer = GlobalOpenTelemetry.getTracer("test"); + Span linkedSpan = tracer.spanBuilder("linked").startSpan(); + linkedSpan.end(); + SpanContext linkedSpanContext = linkedSpan.getSpanContext(); + + Span testSpan = + tracer.spanBuilder("test").addLink(linkedSpanContext).startSpan(); + testSpan.end(); + + // Then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("linked").hasNoParent().hasTotalAttributeCount(0)), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("test") + .hasNoParent() + .hasTotalAttributeCount(0) + .hasLinksSatisfying( + links -> { + assertThat(links).hasSize(1); + LinkData link = links.get(0); + // Compare SpanContext fields individually due to classloader isolation + assertThat(link.getSpanContext().getTraceId()) + .isEqualTo(linkedSpanContext.getTraceId()); + assertThat(link.getSpanContext().getSpanId()) + .isEqualTo(linkedSpanContext.getSpanId()); + assertThat(link.getSpanContext().getTraceFlags().asByte()) + .isEqualTo(linkedSpanContext.getTraceFlags().asByte()); + assertThat(link.getSpanContext().isRemote()) + .isEqualTo(linkedSpanContext.isRemote()); + assertThat(link.getAttributes().size()).isEqualTo(0); + }))); + } @Test + @DisplayName("capture span link with attributes") + void captureSpanLinkWithAttributes() { + // When + Tracer tracer = GlobalOpenTelemetry.getTracer("test"); + Span linkedSpan = tracer.spanBuilder("linked").startSpan(); + linkedSpan.end(); + SpanContext linkedSpanContext = linkedSpan.getSpanContext(); + + Attributes linkAttributes = + Attributes.builder() + .put("string", "1") + .put("long", 2L) + .put("double", 3.0) + .put("boolean", true) + .build(); + Span testSpan = + tracer + .spanBuilder("test") + .addLink(linkedSpanContext, linkAttributes) + .startSpan(); + testSpan.end(); + + // Then + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("linked").hasNoParent().hasTotalAttributeCount(0)), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("test") + .hasNoParent() + .hasTotalAttributeCount(0) + .hasLinksSatisfying( + links -> { + assertThat(links).hasSize(1); + LinkData link = links.get(0); + assertThat(link.getSpanContext().getTraceId()) + .isEqualTo(linkedSpanContext.getTraceId()); + assertThat(link.getSpanContext().getSpanId()) + .isEqualTo(linkedSpanContext.getSpanId()); + assertThat(link.getSpanContext().getTraceFlags().asByte()) + .isEqualTo(linkedSpanContext.getTraceFlags().asByte()); + assertThat(link.getSpanContext().isRemote()) + .isEqualTo(linkedSpanContext.isRemote()); + assertThat(link.getTotalAttributeCount()).isEqualTo(4); + Attributes attrs = link.getAttributes(); + assertThat(attrs.get(stringKey("string"))).isEqualTo("1"); + assertThat(attrs.get(longKey("long"))).isEqualTo(2L); + assertThat(attrs.get(doubleKey("double"))).isEqualTo(3.0); + assertThat(attrs.get(booleanKey("boolean"))).isEqualTo(true); + }))); + } } diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/TelemetryConverter.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/TelemetryConverter.java index 0390ce6f78f2..b5e2c6e10044 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/TelemetryConverter.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/TelemetryConverter.java @@ -109,7 +109,7 @@ public static List getSpanData(Collection allResourceSp SpanContext.create( traceId, bytesToHex(span.getSpanId().toByteArray()), - TraceFlags.getDefault(), + TraceFlags.fromByte((byte) span.getFlags()), extractTraceState(span.getTraceState()))) // TODO is it ok to use default trace flags and default trace state here? .setParentSpanContext( @@ -152,7 +152,7 @@ public static List getSpanData(Collection allResourceSp SpanContext.create( bytesToHex(link.getTraceId().toByteArray()), bytesToHex(link.getSpanId().toByteArray()), - TraceFlags.getDefault(), + TraceFlags.fromByte((byte) link.getFlags()), extractTraceState(link.getTraceState())), fromProto(link.getAttributesList()), link.getDroppedAttributesCount() + link.getAttributesCount())) From 298bfb6d45b7ddb699ceb2057944d5ee5005e520 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 25 Oct 2025 13:27:17 -0700 Subject: [PATCH 2/3] fix --- .../trace/ApplicationSpanBuilder.java | 3 ++- .../opentelemetryapi/TracerTest.java | 13 +++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/instrumentation/opentelemetry-api/opentelemetry-api-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opentelemetryapi/trace/ApplicationSpanBuilder.java b/instrumentation/opentelemetry-api/opentelemetry-api-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opentelemetryapi/trace/ApplicationSpanBuilder.java index ce669fc3ab04..2650883ac706 100644 --- a/instrumentation/opentelemetry-api/opentelemetry-api-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opentelemetryapi/trace/ApplicationSpanBuilder.java +++ b/instrumentation/opentelemetry-api/opentelemetry-api-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opentelemetryapi/trace/ApplicationSpanBuilder.java @@ -50,7 +50,8 @@ public SpanBuilder addLink(SpanContext applicationSpanContext) { @Override @CanIgnoreReturnValue public SpanBuilder addLink(SpanContext applicationSpanContext, Attributes applicationAttributes) { - agentBuilder.addLink(Bridging.toAgent(applicationSpanContext)); + agentBuilder.addLink( + Bridging.toAgent(applicationSpanContext), Bridging.toAgent(applicationAttributes)); return this; } diff --git a/instrumentation/opentelemetry-api/opentelemetry-api-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/opentelemetryapi/TracerTest.java b/instrumentation/opentelemetry-api/opentelemetry-api-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/opentelemetryapi/TracerTest.java index f09a8674d83b..ce440cc65664 100644 --- a/instrumentation/opentelemetry-api/opentelemetry-api-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/opentelemetryapi/TracerTest.java +++ b/instrumentation/opentelemetry-api/opentelemetry-api-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/opentelemetryapi/TracerTest.java @@ -334,8 +334,7 @@ void captureSpanLinkWithoutAttributes() { linkedSpan.end(); SpanContext linkedSpanContext = linkedSpan.getSpanContext(); - Span testSpan = - tracer.spanBuilder("test").addLink(linkedSpanContext).startSpan(); + Span testSpan = tracer.spanBuilder("test").addLink(linkedSpanContext).startSpan(); testSpan.end(); // Then @@ -353,7 +352,6 @@ void captureSpanLinkWithoutAttributes() { links -> { assertThat(links).hasSize(1); LinkData link = links.get(0); - // Compare SpanContext fields individually due to classloader isolation assertThat(link.getSpanContext().getTraceId()) .isEqualTo(linkedSpanContext.getTraceId()); assertThat(link.getSpanContext().getSpanId()) @@ -364,7 +362,9 @@ void captureSpanLinkWithoutAttributes() { .isEqualTo(linkedSpanContext.isRemote()); assertThat(link.getAttributes().size()).isEqualTo(0); }))); - } @Test + } + + @Test @DisplayName("capture span link with attributes") void captureSpanLinkWithAttributes() { // When @@ -381,10 +381,7 @@ void captureSpanLinkWithAttributes() { .put("boolean", true) .build(); Span testSpan = - tracer - .spanBuilder("test") - .addLink(linkedSpanContext, linkAttributes) - .startSpan(); + tracer.spanBuilder("test").addLink(linkedSpanContext, linkAttributes).startSpan(); testSpan.end(); // Then From 0d8536ad94a6f93244c63edaad010f18e82fbdd4 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 25 Oct 2025 14:03:36 -0700 Subject: [PATCH 3/3] fix --- .../testing/internal/TelemetryConverter.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/TelemetryConverter.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/TelemetryConverter.java index b5e2c6e10044..58de32e4f3ef 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/TelemetryConverter.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/TelemetryConverter.java @@ -307,8 +307,8 @@ private static LogRecordData createLogData( SpanContext.create( bytesToHex(logRecord.getTraceId().toByteArray()), bytesToHex(logRecord.getSpanId().toByteArray()), - TraceFlags.getDefault(), - TraceState.getDefault())) + TraceFlags.fromByte((byte) logRecord.getFlags()), + TraceState.getDefault())) // logs proto doesn't have trace state .setSeverity(fromProto(logRecord.getSeverityNumber())) .setSeverityText(logRecord.getSeverityText()) .setAttributes(fromProto(logRecord.getAttributesList())); @@ -333,8 +333,8 @@ private static LogRecordData createExtendedLogData( SpanContext.create( bytesToHex(logRecord.getTraceId().toByteArray()), bytesToHex(logRecord.getSpanId().toByteArray()), - TraceFlags.getDefault(), - TraceState.getDefault())) + TraceFlags.fromByte((byte) logRecord.getFlags()), + TraceState.getDefault())) // logs proto doesn't have trace state .setSeverity(fromProto(logRecord.getSeverityNumber())) .setSeverityText(logRecord.getSeverityText()) .setEventName(logRecord.getEventName())