OpenTracing Shim: Full multiple parent support. (#4916)

* Full multiple parent support.

This includes:

* The Baggage union of ALL parents is used.
* All parents are added as Links, in order to preserve
  the OpenTracing reference type as an attribute
  (either CHILD_OF or FOLLOWS_FROM).

* Update opentracing-shim/src/main/java/io/opentelemetry/opentracingshim/SpanBuilderShim.java

Co-authored-by: jack-berg <34418638+jack-berg@users.noreply.github.com>

* Update opentracing-shim/src/main/java/io/opentelemetry/opentracingshim/SpanBuilderShim.java

Co-authored-by: jack-berg <34418638+jack-berg@users.noreply.github.com>

* Update opentracing-shim/src/main/java/io/opentelemetry/opentracingshim/SpanBuilderShim.java

Co-authored-by: jack-berg <34418638+jack-berg@users.noreply.github.com>

* Apply feedback.

* More feedback.

Co-authored-by: jack-berg <34418638+jack-berg@users.noreply.github.com>
This commit is contained in:
Carlos Alberto Cortez 2022-11-10 00:02:28 +01:00 committed by GitHub
parent ef076c2880
commit f83def7a4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 278 additions and 31 deletions

View File

@ -12,6 +12,8 @@ dependencies {
api("io.opentracing:opentracing-api")
implementation(project(":semconv"))
annotationProcessor("com.google.auto.value:auto-value")
testImplementation(project(":sdk:testing"))
}

View File

@ -10,32 +10,35 @@ import static io.opentelemetry.api.common.AttributeKey.doubleKey;
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<SpanParentInfo> allParents = Collections.emptyList();
private boolean ignoreActiveSpan;
private final List<io.opentelemetry.api.trace.SpanContext> parentLinks = new ArrayList<>();
@SuppressWarnings("rawtypes")
private final List<AttributeKey> 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 @@ final class SpanBuilderShim extends BaseShimObject implements SpanBuilder {
// 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 @@ final class SpanBuilderShim extends BaseShimObject implements SpanBuilder {
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 @@ final class SpanBuilderShim extends BaseShimObject implements SpanBuilder {
@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 @@ final class SpanBuilderShim extends BaseShimObject implements SpanBuilder {
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<SpanParentInfo> 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<SpanParentInfo> 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
}
}

View File

@ -20,6 +20,8 @@ import io.opentelemetry.sdk.trace.data.LinkData;
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 @@ class SpanBuilderShimTest {
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<LinkData> 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 @@ class SpanBuilderShimTest {
}
}
@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 =