diff --git a/opentracing-shim/build.gradle.kts b/opentracing-shim/build.gradle.kts index 9947bc7f5c5..98129994fbc 100644 --- a/opentracing-shim/build.gradle.kts +++ b/opentracing-shim/build.gradle.kts @@ -12,6 +12,8 @@ dependencies { api("io.opentracing:opentracing-api") implementation(project(":semconv")) + annotationProcessor("com.google.auto.value:auto-value") + testImplementation(project(":sdk:testing")) } diff --git a/opentracing-shim/src/main/java/io/opentelemetry/opentracingshim/SpanBuilderShim.java b/opentracing-shim/src/main/java/io/opentelemetry/opentracingshim/SpanBuilderShim.java index 5ed3baa8bf1..9c3f8db4946 100644 --- a/opentracing-shim/src/main/java/io/opentelemetry/opentracingshim/SpanBuilderShim.java +++ b/opentracing-shim/src/main/java/io/opentelemetry/opentracingshim/SpanBuilderShim.java @@ -10,32 +10,35 @@ import static io.opentelemetry.api.common.AttributeKey.longKey; import static io.opentelemetry.api.common.AttributeKey.stringKey; +import com.google.auto.value.AutoValue; import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.BaggageBuilder; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.context.Context; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import io.opentracing.References; import io.opentracing.Span; import io.opentracing.SpanContext; import io.opentracing.Tracer.SpanBuilder; import io.opentracing.tag.Tag; import io.opentracing.tag.Tags; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; final class SpanBuilderShim extends BaseShimObject implements SpanBuilder { private final String spanName; - // The parent will be either a Span or a SpanContext. - // Inherited baggage is supported only for the main parent. - @Nullable private SpanShim parentSpan; - @Nullable private SpanContextShim parentSpanContext; + // *All* parents are saved in this list. + private List allParents = Collections.emptyList(); private boolean ignoreActiveSpan; - private final List parentLinks = new ArrayList<>(); - @SuppressWarnings("rawtypes") private final List spanBuilderAttributeKeys = new ArrayList<>(); @@ -44,6 +47,15 @@ final class SpanBuilderShim extends BaseShimObject implements SpanBuilder { private boolean error; private long startTimestampMicros; + private static final Attributes CHILD_OF_ATTR = + Attributes.of( + SemanticAttributes.OPENTRACING_REF_TYPE, + SemanticAttributes.OpentracingRefTypeValues.CHILD_OF); + private static final Attributes FOLLOWS_FROM_ATTR = + Attributes.of( + SemanticAttributes.OPENTRACING_REF_TYPE, + SemanticAttributes.OpentracingRefTypeValues.FOLLOWS_FROM); + public SpanBuilderShim(TelemetryInfo telemetryInfo, String spanName) { super(telemetryInfo); this.spanName = spanName; @@ -57,19 +69,12 @@ public SpanBuilder asChildOf(Span parent) { // TODO - Verify we handle a no-op Span SpanShim spanShim = ShimUtil.getSpanShim(parent); - - if (parentSpan == null && parentSpanContext == null) { - parentSpan = spanShim; - } else { - parentLinks.add(spanShim.getSpan().getSpanContext()); - } - - return this; + return addReference(References.CHILD_OF, spanShim.context()); } @Override public SpanBuilder asChildOf(SpanContext parent) { - return addReference(null, parent); + return addReference(References.CHILD_OF, parent); } @Override @@ -78,13 +83,30 @@ public SpanBuilder addReference(@Nullable String referenceType, SpanContext refe return this; } - // TODO - Use referenceType + ReferenceType refType; + if (References.CHILD_OF.equals(referenceType)) { + refType = ReferenceType.CHILD_OF; + } else if (References.FOLLOWS_FROM.equals(referenceType)) { + refType = ReferenceType.FOLLOWS_FROM; + } else { + // Discard references with unrecognized type. + return this; + } + SpanContextShim contextShim = ShimUtil.getContextShim(referencedContext); - if (parentSpan == null && parentSpanContext == null) { - parentSpanContext = contextShim; + // Optimization for 99% situations, when there is only one parent. + if (allParents.size() == 0) { + allParents = + Collections.singletonList( + SpanParentInfo.create( + contextShim.getSpanContext(), contextShim.getBaggage(), refType)); } else { - parentLinks.add(contextShim.getSpanContext()); + if (allParents.size() == 1) { + allParents = new ArrayList<>(allParents); + } + allParents.add( + SpanParentInfo.create(contextShim.getSpanContext(), contextShim.getBaggage(), refType)); } return this; @@ -186,26 +208,26 @@ public SpanBuilder withStartTimestamp(long microseconds) { @SuppressWarnings({"rawtypes", "unchecked"}) @Override public Span start() { - Baggage baggage = Baggage.empty(); + Baggage baggage; io.opentelemetry.api.trace.SpanBuilder builder = tracer().spanBuilder(spanName); + io.opentelemetry.api.trace.SpanContext mainParent = getMainParent(allParents); - if (ignoreActiveSpan && parentSpan == null && parentSpanContext == null) { + if (ignoreActiveSpan && mainParent == null) { builder.setNoParent(); - } else if (parentSpan != null) { - builder.setParent(Context.root().with(parentSpan.getSpan())); - baggage = ((SpanContextShim) parentSpan.context()).getBaggage(); - } else if (parentSpanContext != null) { - builder.setParent( - Context.root() - .with(io.opentelemetry.api.trace.Span.wrap(parentSpanContext.getSpanContext()))); - baggage = parentSpanContext.getBaggage(); + baggage = Baggage.empty(); + } else if (mainParent != null) { + builder.setParent(Context.root().with(io.opentelemetry.api.trace.Span.wrap(mainParent))); + baggage = getAllBaggage(allParents); } else { // No explicit parent Span, but extracted baggage may be available. baggage = Baggage.current(); } - for (io.opentelemetry.api.trace.SpanContext link : parentLinks) { - builder.addLink(link); + // *All* parents are processed as Links, in order to keep the reference type value. + for (SpanParentInfo parentInfo : allParents) { + builder.addLink( + parentInfo.getSpanContext(), + parentInfo.getRefType() == ReferenceType.CHILD_OF ? CHILD_OF_ATTR : FOLLOWS_FROM_ATTR); } if (spanKind != null) { @@ -232,4 +254,62 @@ public Span start() { return new SpanShim(telemetryInfo(), span, baggage); } + + // The first SpanContext with Child Of type in the entire list is used as parent, + // else the first SpanContext is used as parent. + @Nullable + static io.opentelemetry.api.trace.SpanContext getMainParent(List parents) { + if (parents.size() == 0) { + return null; + } + + SpanParentInfo mainParent = parents.get(0); + for (SpanParentInfo parentInfo : parents) { + if (parentInfo.getRefType() == ReferenceType.CHILD_OF) { + mainParent = parentInfo; + break; + } + } + + return mainParent.getSpanContext(); + } + + static Baggage getAllBaggage(List parents) { + if (parents.size() == 0) { + return Baggage.empty(); + } + + if (parents.size() == 1) { + return parents.get(0).getBaggage(); + } + + BaggageBuilder builder = Baggage.builder(); + for (SpanParentInfo parent : parents) { + parent.getBaggage().forEach((key, entry) -> builder.put(key, entry.getValue())); + } + + return builder.build(); + } + + @AutoValue + @Immutable + abstract static class SpanParentInfo { + private static SpanParentInfo create( + io.opentelemetry.api.trace.SpanContext spanContext, + Baggage baggage, + ReferenceType refType) { + return new AutoValue_SpanBuilderShim_SpanParentInfo(spanContext, baggage, refType); + } + + abstract io.opentelemetry.api.trace.SpanContext getSpanContext(); + + abstract Baggage getBaggage(); + + abstract ReferenceType getRefType(); + } + + enum ReferenceType { + CHILD_OF, + FOLLOWS_FROM + } } diff --git a/opentracing-shim/src/test/java/io/opentelemetry/opentracingshim/SpanBuilderShimTest.java b/opentracing-shim/src/test/java/io/opentelemetry/opentracingshim/SpanBuilderShimTest.java index 06fc3891d8c..05a412478c3 100644 --- a/opentracing-shim/src/test/java/io/opentelemetry/opentracingshim/SpanBuilderShimTest.java +++ b/opentracing-shim/src/test/java/io/opentelemetry/opentracingshim/SpanBuilderShimTest.java @@ -20,6 +20,8 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.samplers.Sampler; import io.opentelemetry.sdk.trace.samplers.SamplingResult; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import io.opentracing.References; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -38,6 +40,143 @@ void setUp() { GlobalOpenTelemetry.resetForTest(); } + @Test + void parent_single() { + SpanShim parentSpan = (SpanShim) new SpanBuilderShim(telemetryInfo, SPAN_NAME).start(); + try { + SpanShim childSpan = + (SpanShim) new SpanBuilderShim(telemetryInfo, SPAN_NAME).asChildOf(parentSpan).start(); + + try { + SpanData spanData = ((ReadableSpan) childSpan.getSpan()).toSpanData(); + assertThat(parentSpan.context().toSpanId()).isEqualTo(spanData.getParentSpanId()); + } finally { + childSpan.finish(); + } + } finally { + parentSpan.finish(); + } + } + + @Test + void parent_multipleFollowsFrom() { + SpanShim parentSpan1 = (SpanShim) new SpanBuilderShim(telemetryInfo, SPAN_NAME).start(); + SpanShim parentSpan2 = (SpanShim) new SpanBuilderShim(telemetryInfo, SPAN_NAME).start(); + + try { + SpanShim childSpan = + (SpanShim) + new SpanBuilderShim(telemetryInfo, SPAN_NAME) + .addReference(References.FOLLOWS_FROM, parentSpan1.context()) + .addReference(References.FOLLOWS_FROM, parentSpan2.context()) + .start(); + + try { + // If no parent of CHILD_OF type exists, use the first value as main parent. + SpanData spanData = ((ReadableSpan) childSpan.getSpan()).toSpanData(); + assertThat(parentSpan1.context().toSpanId()).isEqualTo(spanData.getParentSpanId()); + } finally { + childSpan.finish(); + } + } finally { + parentSpan1.finish(); + parentSpan2.finish(); + } + } + + @Test + void parent_multipleDifferentRefType() { + SpanShim parentSpan1 = (SpanShim) new SpanBuilderShim(telemetryInfo, SPAN_NAME).start(); + SpanShim parentSpan2 = (SpanShim) new SpanBuilderShim(telemetryInfo, SPAN_NAME).start(); + SpanShim parentSpan3 = (SpanShim) new SpanBuilderShim(telemetryInfo, SPAN_NAME).start(); + + try { + SpanShim childSpan = + (SpanShim) + new SpanBuilderShim(telemetryInfo, SPAN_NAME) + .addReference(References.FOLLOWS_FROM, parentSpan1.context()) + .addReference(References.CHILD_OF, parentSpan2.context()) + .addReference(References.CHILD_OF, parentSpan3.context()) + .start(); + + try { + // The first parent with CHILD_OF becomes the direct parent (if any). + SpanData spanData = ((ReadableSpan) childSpan.getSpan()).toSpanData(); + assertThat(parentSpan2.context().toSpanId()).isEqualTo(spanData.getParentSpanId()); + } finally { + childSpan.finish(); + } + } finally { + parentSpan1.finish(); + parentSpan2.finish(); + parentSpan3.finish(); + } + } + + @Test + void parent_multipleLinks() { + SpanShim parentSpan1 = (SpanShim) new SpanBuilderShim(telemetryInfo, SPAN_NAME).start(); + SpanShim parentSpan2 = (SpanShim) new SpanBuilderShim(telemetryInfo, SPAN_NAME).start(); + SpanShim parentSpan3 = (SpanShim) new SpanBuilderShim(telemetryInfo, SPAN_NAME).start(); + + try { + SpanShim childSpan = + (SpanShim) + new SpanBuilderShim(telemetryInfo, SPAN_NAME) + .addReference(References.FOLLOWS_FROM, parentSpan1.context()) + .addReference(References.CHILD_OF, parentSpan2.context()) + .addReference(References.FOLLOWS_FROM, parentSpan3.context()) + .start(); + + try { + SpanData spanData = ((ReadableSpan) childSpan.getSpan()).toSpanData(); + List links = spanData.getLinks(); + assertThat(links).hasSize(3); + + assertThat(links.get(0).getSpanContext()).isEqualTo(parentSpan1.getSpan().getSpanContext()); + assertThat(links.get(1).getSpanContext()).isEqualTo(parentSpan2.getSpan().getSpanContext()); + assertThat(links.get(2).getSpanContext()).isEqualTo(parentSpan3.getSpan().getSpanContext()); + assertThat(links.get(0).getAttributes().get(SemanticAttributes.OPENTRACING_REF_TYPE)) + .isEqualTo(SemanticAttributes.OpentracingRefTypeValues.FOLLOWS_FROM); + assertThat(links.get(1).getAttributes().get(SemanticAttributes.OPENTRACING_REF_TYPE)) + .isEqualTo(SemanticAttributes.OpentracingRefTypeValues.CHILD_OF); + assertThat(links.get(2).getAttributes().get(SemanticAttributes.OPENTRACING_REF_TYPE)) + .isEqualTo(SemanticAttributes.OpentracingRefTypeValues.FOLLOWS_FROM); + + } finally { + childSpan.finish(); + } + } finally { + parentSpan1.finish(); + parentSpan2.finish(); + parentSpan3.finish(); + } + } + + @Test + void parent_wrongRefType() { + SpanShim parentSpan = (SpanShim) new SpanBuilderShim(telemetryInfo, SPAN_NAME).start(); + try { + SpanShim childSpan = + (SpanShim) + new SpanBuilderShim(telemetryInfo, SPAN_NAME) + .addReference("wrongreftype-value", parentSpan.context()) + .start(); + + try { + // Incorrect typeref values get discarded. + SpanData spanData = ((ReadableSpan) childSpan.getSpan()).toSpanData(); + assertThat(spanData.getParentSpanContext()) + .isEqualTo(io.opentelemetry.api.trace.SpanContext.getInvalid()); + assertThat(spanData.getLinks()).isEmpty(); + } finally { + childSpan.finish(); + } + } finally { + parentSpan.finish(); + } + } + @Test void baggage_parent() { SpanShim parentSpan = (SpanShim) new SpanBuilderShim(telemetryInfo, SPAN_NAME).start(); @@ -79,6 +218,32 @@ void baggage_parentContext() { } } + @Test + void baggage_multipleParents() { + SpanShim parentSpan1 = (SpanShim) new SpanBuilderShim(telemetryInfo, SPAN_NAME).start(); + SpanShim parentSpan2 = (SpanShim) new SpanBuilderShim(telemetryInfo, SPAN_NAME).start(); + try { + parentSpan1.setBaggageItem("key1", "value1"); + parentSpan2.setBaggageItem("key2", "value2"); + + SpanShim childSpan = + (SpanShim) + new SpanBuilderShim(telemetryInfo, SPAN_NAME) + .addReference(References.FOLLOWS_FROM, parentSpan1.context()) + .addReference(References.CHILD_OF, parentSpan2.context()) + .start(); + try { + assertThat(childSpan.getBaggageItem("key1")).isEqualTo("value1"); + assertThat(childSpan.getBaggageItem("key2")).isEqualTo("value2"); + } finally { + childSpan.finish(); + } + } finally { + parentSpan1.finish(); + parentSpan2.finish(); + } + } + @Test void baggage_spanWithInvalidSpan() { io.opentelemetry.api.baggage.Baggage baggage =