Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@
* Logging: Output event name in `SystemOutLogRecordExporter`
([#8609](https://github.com/open-telemetry/opentelemetry-java/pull/8609))

### Shims

* OpenTracing: Return the extracted `SpanContext` when it is sampled but invalid
([#8634](https://github.com/open-telemetry/opentelemetry-java/pull/8634))
* 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public PrometheusHttpServerBuilder setOtelScopeLabelsEnabled(boolean otelScopeLa
return this;
}

/** Set if the {@code otel_target_info} metric is generated. Default is {@code true}. */
/** Set if the {@code target_info} metric is generated. Default is {@code true}. */
public PrometheusHttpServerBuilder setTargetInfoMetricEnabled(boolean targetInfoMetricEnabled) {
metricReaderBuilder.setTargetInfoMetricEnabled(targetInfoMetricEnabled);
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ private void handleResponse(Response response, Consumer<GrpcResponse> onResponse
compressed = body.source().readByte() != 0;
body.source().skip(4); // message length — we bound reads by EOF instead
} catch (IOException e) {
logger.log(Level.FINE, "Invalid gRPC response frame");
logger.log(Level.FINE, "Invalid gRPC response frame", e);
onResponse.accept(
ImmutableGrpcResponse.create(grpcStatus(response), grpcMessage(response), new byte[0]));
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ public <C> Context extract(Context context, @Nullable C carrier, TextMapGetter<C
break;
}
String lowercaseKey = key.toLowerCase(Locale.ROOT);
if (!lowercaseKey.startsWith(PREFIX_BAGGAGE_HEADER)) {
if (!lowercaseKey.startsWith(PREFIX_BAGGAGE_HEADER)
|| lowercaseKey.length() == PREFIX_BAGGAGE_HEADER.length()) {
continue;
}
String value = getter.get(carrier, key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,19 @@ void extract_Baggage_CapitalizedHeaders() {
assertThat(Baggage.fromContext(context)).isEqualTo(expectedBaggage);
}

@Test
void extract_Baggage_EmptyKey() {
Map<String, String> carrier = new LinkedHashMap<>();
carrier.put(OtTracePropagator.TRACE_ID_HEADER, TRACE_ID);
carrier.put(OtTracePropagator.SPAN_ID_HEADER, SPAN_ID);
carrier.put(OtTracePropagator.SAMPLED_HEADER, Common.TRUE_INT);
carrier.put(OtTracePropagator.PREFIX_BAGGAGE_HEADER, "value"); // Not really a valid key.

Context context = propagator.extract(Context.current(), carrier, getter);

assertThat(Baggage.fromContext(context)).isEqualTo(Baggage.empty());
}

@Test
void extract_Baggage_InvalidContext() {
Map<String, String> carrier = new LinkedHashMap<>();
Expand Down
5 changes: 0 additions & 5 deletions opencensus-shim/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,21 @@
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;
import io.opentelemetry.api.trace.SpanContext;
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<Span.Options> 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;

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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() {
Expand All @@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ <C> SpanContextShim extractTextMap(Format<C> format, TextMapExtract carrier) {

Span span = Span.fromContext(context);
Baggage baggage = Baggage.fromContext(context);
if (!span.getSpanContext().isValid() && baggage.isEmpty()) {
if (!span.getSpanContext().isValid()
&& !span.getSpanContext().isSampled()
&& baggage.isEmpty()) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,15 @@
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.baggage.Baggage;
import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator;
import io.opentelemetry.api.trace.SpanId;
import io.opentelemetry.api.trace.TraceFlags;
import io.opentelemetry.api.trace.TraceId;
import io.opentelemetry.api.trace.TraceState;
import io.opentelemetry.api.trace.TracerProvider;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapGetter;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.context.propagation.TextMapSetter;
import io.opentelemetry.internal.testing.slf4j.SuppressLogger;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension;
Expand All @@ -32,9 +38,11 @@
import io.opentracing.tag.StringTag;
import io.opentracing.tag.Tag;
import io.opentracing.tag.Tags;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -350,6 +358,21 @@ void extract_onlyBaggage() {
assertThat(spanContextShim.getBaggage()).isEqualTo(baggage);
}

@Test
void extract_invalidButSampledSpanContext() {
tracerShim =
new TracerShim(provider, new InvalidButSampledPropagator(), TextMapPropagator.noop());

SpanContext spanContext =
tracerShim.extract(Format.Builtin.TEXT_MAP, new TextMapAdapter(new HashMap<>()));

assertThat(spanContext).isNotNull();
SpanContextShim spanContextShim = (SpanContextShim) spanContext;
assertThat(spanContextShim.getSpanContext().isValid()).isFalse();
assertThat(spanContextShim.getSpanContext().isSampled()).isTrue();
assertThat(spanContextShim.getBaggage().isEmpty()).isTrue();
}

@Test
void extract_emptyCarrier_withActiveSpan_returnsNull() {
Span span = tracerShim.buildSpan("one").start();
Expand Down Expand Up @@ -467,4 +490,26 @@ void noopDoesNotCrash() {

assertThat(((SpanShim) span).getSpan().isRecording()).isFalse();
}

/** Extracts an invalid but sampled {@code SpanContext}, as {@code jaeger-debug-id} headers do. */
private static class InvalidButSampledPropagator implements TextMapPropagator {
@Override
public Collection<String> fields() {
return Collections.emptyList();
}

@Override
public <C> void inject(Context context, @Nullable C carrier, TextMapSetter<C> setter) {}

@Override
public <C> Context extract(Context context, @Nullable C carrier, TextMapGetter<C> getter) {
return context.with(
io.opentelemetry.api.trace.Span.wrap(
io.opentelemetry.api.trace.SpanContext.create(
TraceId.getInvalid(),
SpanId.getInvalid(),
TraceFlags.getSampled(),
TraceState.getDefault())));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
Expand Down Expand Up @@ -45,14 +46,19 @@ public final class JaegerRemoteSampler implements Sampler {
private final AtomicBoolean isShutdown = new AtomicBoolean();

private final GrpcSender grpcSender;
private final URI endpoint;
private final int pollingIntervalMs;

JaegerRemoteSampler(
GrpcSender grpcSender,
URI endpoint,
@Nullable String serviceName,
int pollingIntervalMs,
Sampler initialSampler) {
this.serviceName = serviceName != null ? serviceName : "";
this.grpcSender = grpcSender;
this.endpoint = endpoint;
this.pollingIntervalMs = pollingIntervalMs;
this.sampler = initialSampler;
pollExecutor = Executors.newScheduledThreadPool(1, new DaemonThreadFactory(WORKER_THREAD_NAME));
pollFuture =
Expand Down Expand Up @@ -161,7 +167,13 @@ private static Sampler updateSampler(SamplingStrategyResponse response) throws I

@Override
public String getDescription() {
return String.format("JaegerRemoteSampler{%s}", this.sampler);
return "JaegerRemoteSampler{sampler="
+ this.sampler
+ ", endpoint="
+ this.endpoint
+ ", pollingIntervalMs="
+ this.pollingIntervalMs
+ "}";
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ public JaegerRemoteSamplerBuilder setChannel(ManagedChannel channel) {
*/
public JaegerRemoteSampler build() {
GrpcSender grpcSender = resolveGrpcSender();
return new JaegerRemoteSampler(grpcSender, serviceName, pollingIntervalMillis, initialSampler);
return new JaegerRemoteSampler(
grpcSender, endpoint, serviceName, pollingIntervalMillis, initialSampler);
}

private GrpcSender resolveGrpcSender() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public Sampler create(DeclarativeConfigProperties config) {
}
builder.setInitialSampler(DeclarativeConfiguration.createSampler(initialSamplerModel));

Long pollingIntervalMs = config.getLong("internal");
Long pollingIntervalMs = config.getLong("interval");
if (pollingIntervalMs != null) {
builder.setPollingInterval(Duration.ofMillis(pollingIntervalMs));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,8 @@ void description() {
assertThat(sampler).extracting("grpcSender").isInstanceOf(OkHttpGrpcSender.class);

assertThat(sampler.getDescription())
.startsWith("JaegerRemoteSampler{ParentBased{root:TraceIdRatioBased{0.001000}");
.startsWith("JaegerRemoteSampler{sampler=ParentBased{root:TraceIdRatioBased{0.001000}")
.contains("endpoint=" + server.httpUri(), "pollingIntervalMs=1000");

// wait until the sampling strategy is retrieved before exiting test method
await().untilAsserted(samplerIsType(sampler, RateLimitingSampler.class));
Expand All @@ -301,7 +302,8 @@ void initialSampler() {
.build()) {
assertThat(sampler).extracting("grpcSender").isInstanceOf(OkHttpGrpcSender.class);

assertThat(sampler.getDescription()).startsWith("JaegerRemoteSampler{AlwaysOnSampler}");
assertThat(sampler.getDescription())
.startsWith("JaegerRemoteSampler{sampler=AlwaysOnSampler");
}
}

Expand Down Expand Up @@ -400,7 +402,7 @@ void perOperationSampling() {
() -> {
assertThat(sampler.getDescription())
.startsWith(
"JaegerRemoteSampler{ParentBased{root:PerOperationSampler{default=TraceIdRatioBased{0.550000}, perOperation={foo=TraceIdRatioBased{0.900000}, bar=TraceIdRatioBased{0.700000}}}");
"JaegerRemoteSampler{sampler=ParentBased{root:PerOperationSampler{default=TraceIdRatioBased{0.550000}, perOperation={foo=TraceIdRatioBased{0.900000}, bar=TraceIdRatioBased{0.700000}}}");
assertThat(sampler.getDescription()).contains("bar");
});
}
Expand All @@ -419,7 +421,7 @@ void internal_error_server_response() {
assertThat(sampler).extracting("grpcSender").isInstanceOf(OkHttpGrpcSender.class);

assertThat(sampler.getDescription())
.startsWith("JaegerRemoteSampler{ParentBased{root:TraceIdRatioBased{0.001000}");
.startsWith("JaegerRemoteSampler{sampler=ParentBased{root:TraceIdRatioBased{0.001000}");

await()
.untilAsserted(
Expand All @@ -444,7 +446,7 @@ void unavailable_error_server_response() {
assertThat(sampler).extracting("grpcSender").isInstanceOf(OkHttpGrpcSender.class);

assertThat(sampler.getDescription())
.startsWith("JaegerRemoteSampler{ParentBased{root:TraceIdRatioBased{0.001000}");
.startsWith("JaegerRemoteSampler{sampler=ParentBased{root:TraceIdRatioBased{0.001000}");

await()
.untilAsserted(
Expand All @@ -468,7 +470,7 @@ void unimplemented_error_server_response() {
assertThat(sampler).extracting("grpcSender").isInstanceOf(OkHttpGrpcSender.class);

assertThat(sampler.getDescription())
.startsWith("JaegerRemoteSampler{ParentBased{root:TraceIdRatioBased{0.001000}");
.startsWith("JaegerRemoteSampler{sampler=ParentBased{root:TraceIdRatioBased{0.001000}");

await()
.untilAsserted(
Expand Down
Loading