diff --git a/CHANGELOG.md b/CHANGELOG.md index 386c20ba941..2f3a37d4694 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ * Logging: Output event name in `SystemOutLogRecordExporter` ([#8609](https://github.com/open-telemetry/opentelemetry-java/pull/8609)) +### Shims + +* Add OpenCensus links to the OpenTelemetry span instead of logging a warning and dropping them + ([#8635](https://github.com/open-telemetry/opentelemetry-java/pull/8635)) + ## Version 1.64.0 (2026-07-10) ### API diff --git a/opencensus-shim/README.md b/opencensus-shim/README.md index 69c9f3319cd..b124e4d2c76 100644 --- a/opencensus-shim/README.md +++ b/opencensus-shim/README.md @@ -38,8 +38,3 @@ SdkMeterProvider.builder() .registerMetricReader(OpenCensusMetrics.attachTo(PeriodicMetricReader.create(metricExporter))) .build(); ``` - -## Known Problems - -* OpenCensus links added after an OpenCensus span is created will not be -exported, as OpenTelemetry only supports links added when a span is created. diff --git a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImpl.java b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImpl.java index 87c41b1ff07..5e4c76a386b 100644 --- a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImpl.java +++ b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImpl.java @@ -39,6 +39,8 @@ import io.opencensus.trace.MessageEvent; import io.opencensus.trace.Span; import io.opencensus.trace.Status; +import io.opencensus.trace.TraceOptions; +import io.opencensus.trace.Tracestate; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; @@ -46,13 +48,12 @@ import io.opentelemetry.api.trace.StatusCode; import java.util.EnumSet; import java.util.Map; -import java.util.logging.Logger; class OpenTelemetrySpanImpl extends Span implements io.opentelemetry.api.trace.Span, DelegatingSpan { - private static final Logger LOGGER = Logger.getLogger(OpenTelemetrySpanImpl.class.getName()); private static final EnumSet RECORD_EVENTS_SPAN_OPTIONS = EnumSet.of(Span.Options.RECORD_EVENTS); + private static final Tracestate OC_TRACESTATE_DEFAULT = Tracestate.builder().build(); private final io.opentelemetry.api.trace.Span otelSpan; @@ -104,7 +105,20 @@ public void addAnnotation(Annotation annotation) { @Override public void addLink(Link link) { - LOGGER.warning("OpenTelemetry does not support links added after a span is created."); + Preconditions.checkNotNull(link, "link"); + AttributesBuilder attributesBuilder = Attributes.builder(); + mapAttributes(link.getAttributes(), attributesBuilder); + // DelegatingSpan does not override addLink, so DelegatingSpan.super.addLink(..) would call the + // no-op default method of Span and drop the link. Call the delegate directly instead. + getDelegate() + .addLink( + mapSpanContext( + io.opencensus.trace.SpanContext.create( + link.getTraceId(), + link.getSpanId(), + TraceOptions.DEFAULT, + OC_TRACESTATE_DEFAULT)), + attributesBuilder.build()); } @Override diff --git a/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImplTest.java b/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImplTest.java index 448c032a3f8..e700d5b30f6 100644 --- a/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImplTest.java +++ b/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImplTest.java @@ -5,16 +5,27 @@ package io.opentelemetry.opencensusshim; +import static io.opentelemetry.api.common.AttributeKey.stringKey; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import io.opencensus.trace.AttributeValue; +import io.opencensus.trace.Link; +import io.opencensus.trace.SpanId; import io.opencensus.trace.Status; +import io.opencensus.trace.TraceId; +import io.opencensus.trace.TraceOptions; +import io.opencensus.trace.Tracestate; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; +import java.util.Collections; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -26,6 +37,9 @@ @MockitoSettings(strictness = Strictness.LENIENT) class OpenTelemetrySpanImplTest { + private static final String TRACE_ID = "0123456789abcdef0123456789abcdef"; + private static final String SPAN_ID = "fedcba9876543210"; + @Mock private Span otelSpan; private OpenTelemetrySpanImpl shimSpan() { @@ -47,4 +61,35 @@ void setStatus_withoutDescription_setsCodeOnly() { verify(otelSpan).setStatus(StatusCode.OK); verify(otelSpan, never()).setStatus(any(StatusCode.class), anyString()); } + + @Test + void addLink_delegatesToOtelSpan() { + shimSpan().addLink(Link.fromSpanContext(ocSpanContext(), Link.Type.CHILD_LINKED_SPAN)); + + verify(otelSpan).addLink(expectedOtelSpanContext(), Attributes.empty()); + } + + @Test + void addLink_mapsAttributes() { + shimSpan() + .addLink( + Link.fromSpanContext( + ocSpanContext(), + Link.Type.PARENT_LINKED_SPAN, + Collections.singletonMap("key", AttributeValue.stringAttributeValue("value")))); + + verify(otelSpan).addLink(expectedOtelSpanContext(), Attributes.of(stringKey("key"), "value")); + } + + private static io.opencensus.trace.SpanContext ocSpanContext() { + return io.opencensus.trace.SpanContext.create( + TraceId.fromLowerBase16(TRACE_ID), + SpanId.fromLowerBase16(SPAN_ID), + TraceOptions.DEFAULT, + Tracestate.builder().build()); + } + + private static SpanContext expectedOtelSpanContext() { + return SpanContext.create(TRACE_ID, SPAN_ID, TraceFlags.getDefault(), TraceState.getDefault()); + } }