add SdkTracerProvider.setScopeConfigurator() and support (#7021)

Co-authored-by: Jack Berg <jberg@newrelic.com>
This commit is contained in:
jackshirazi 2025-01-29 15:59:41 +00:00 committed by GitHub
parent 05bf32c5df
commit 8e31bf4f28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 161 additions and 17 deletions

View File

@ -12,16 +12,12 @@ import io.opentelemetry.sdk.trace.internal.TracerConfig;
/** {@link ExtendedSdkTracer} is SDK implementation of {@link ExtendedTracer}. */
final class ExtendedSdkTracer extends SdkTracer implements ExtendedTracer {
// TODO: add dedicated API for updating scope config.
@SuppressWarnings("FieldCanBeFinal") // For now, allow updating reflectively.
private boolean tracerEnabled;
ExtendedSdkTracer(
TracerSharedState sharedState,
InstrumentationScopeInfo instrumentationScopeInfo,
TracerConfig tracerConfig) {
super(sharedState, instrumentationScopeInfo, tracerConfig);
this.tracerEnabled = tracerConfig.isEnabled();
}
@Override

View File

@ -30,10 +30,9 @@ class SdkTracer implements Tracer {
private final TracerSharedState sharedState;
private final InstrumentationScopeInfo instrumentationScopeInfo;
// TODO: add dedicated API for updating scope config.
@SuppressWarnings("FieldCanBeFinal") // For now, allow updating reflectively.
private boolean tracerEnabled;
// deliberately not volatile because of performance concerns
// - which means its eventually consistent
protected boolean tracerEnabled;
SdkTracer(
TracerSharedState sharedState,
@ -79,4 +78,13 @@ class SdkTracer implements Tracer {
InstrumentationScopeInfo getInstrumentationScopeInfo() {
return instrumentationScopeInfo;
}
// Visible for testing
boolean isEnabled() {
return tracerEnabled;
}
void updateTracerConfig(TracerConfig tracerConfig) {
this.tracerEnabled = tracerConfig.isEnabled();
}
}

View File

@ -14,6 +14,7 @@ import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ComponentRegistry;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.internal.SdkTracerProviderUtil;
import io.opentelemetry.sdk.trace.internal.TracerConfig;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import java.io.Closeable;
@ -30,7 +31,9 @@ public final class SdkTracerProvider implements TracerProvider, Closeable {
static final String DEFAULT_TRACER_NAME = "";
private final TracerSharedState sharedState;
private final ComponentRegistry<SdkTracer> tracerSdkComponentRegistry;
private final ScopeConfigurator<TracerConfig> tracerConfigurator;
// deliberately not volatile because of performance concerns
// - which means its eventually consistent
private ScopeConfigurator<TracerConfig> tracerConfigurator;
/**
* Returns a new {@link SdkTracerProviderBuilder} for {@link SdkTracerProvider}.
@ -100,6 +103,25 @@ public final class SdkTracerProvider implements TracerProvider, Closeable {
return sharedState.getSampler();
}
/**
* Updates the tracer configurator, which computes {@link TracerConfig} for each {@link
* InstrumentationScopeInfo}.
*
* <p>This method is experimental so not public. You may reflectively call it using {@link
* SdkTracerProviderUtil#setTracerConfigurator(SdkTracerProvider, ScopeConfigurator)}.
*
* @see TracerConfig#configuratorBuilder()
*/
void setTracerConfigurator(ScopeConfigurator<TracerConfig> tracerConfigurator) {
this.tracerConfigurator = tracerConfigurator;
this.tracerSdkComponentRegistry
.getComponents()
.forEach(
sdkTracer ->
sdkTracer.updateTracerConfig(
getTracerConfig(sdkTracer.getInstrumentationScopeInfo())));
}
/**
* Attempts to stop all the activity for {@link Tracer}s created by this provider. Calls {@link
* SpanProcessor#shutdown()} for all registered {@link SpanProcessor}s.

View File

@ -7,6 +7,7 @@ package io.opentelemetry.sdk.trace.internal;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@ -24,6 +25,21 @@ public final class SdkTracerProviderUtil {
private SdkTracerProviderUtil() {}
/** Reflectively set the {@link ScopeConfigurator} to the {@link SdkTracerProvider}. */
public static void setTracerConfigurator(
SdkTracerProvider sdkTracerProvider, ScopeConfigurator<TracerConfig> scopeConfigurator) {
try {
Method method =
SdkTracerProvider.class.getDeclaredMethod(
"setTracerConfigurator", ScopeConfigurator.class);
method.setAccessible(true);
method.invoke(sdkTracerProvider, scopeConfigurator);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(
"Error calling setTracerConfigurator on SdkTracerProvider", e);
}
}
/** Reflectively set the {@link ScopeConfigurator} to the {@link SdkTracerProviderBuilder}. */
public static void setTracerConfigurator(
SdkTracerProviderBuilder sdkTracerProviderBuilder,

View File

@ -18,7 +18,10 @@ import io.opentelemetry.internal.testing.slf4j.SuppressLogger;
import io.opentelemetry.sdk.common.Clock;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.internal.SdkTracerProviderUtil;
import io.opentelemetry.sdk.trace.internal.TracerConfig;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import java.util.function.Supplier;
import org.junit.jupiter.api.BeforeEach;
@ -184,6 +187,35 @@ class SdkTracerProviderTest {
assertThat(((SdkTracer) tracer).getInstrumentationScopeInfo()).isEqualTo(expected);
}
@Test
void propagatesEnablementToTracerDirectly() {
propagatesEnablementToTracer(true);
}
@Test
void propagatesEnablementToTracerByUtil() {
propagatesEnablementToTracer(false);
}
void propagatesEnablementToTracer(boolean directly) {
SdkTracer tracer = (SdkTracer) tracerFactory.get("test");
boolean isEnabled = tracer.isEnabled();
ScopeConfigurator<TracerConfig> flipConfigurator =
new ScopeConfigurator<TracerConfig>() {
@Override
public TracerConfig apply(InstrumentationScopeInfo scopeInfo) {
return isEnabled ? TracerConfig.disabled() : TracerConfig.enabled();
}
};
// all in the same thread, so should see enablement change immediately
if (directly) {
tracerFactory.setTracerConfigurator(flipConfigurator);
} else {
SdkTracerProviderUtil.setTracerConfigurator(tracerFactory, flipConfigurator);
}
assertThat(tracer.isEnabled()).isEqualTo(!isEnabled);
}
@Test
void build_SpanLimits() {
SpanLimits initialSpanLimits = SpanLimits.builder().build();

View File

@ -16,6 +16,7 @@ import io.opentelemetry.sdk.trace.StressTestRunner.OperationUpdater;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.opentelemetry.sdk.trace.internal.TracerConfig;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.jupiter.api.Test;
@ -50,6 +51,14 @@ class SdkTracerTest {
assertThat(tracer.getInstrumentationScopeInfo()).isEqualTo(instrumentationScopeInfo);
}
@Test
void updateEnabled() {
tracer.updateTracerConfig(TracerConfig.disabled());
assertThat(tracer.isEnabled()).isFalse();
tracer.updateTracerConfig(TracerConfig.enabled());
assertThat(tracer.isEnabled()).isTrue();
}
@Test
void propagatesInstrumentationScopeInfoToSpan() {
ReadableSpan readableSpan = (ReadableSpan) tracer.spanBuilder("spanName").startSpan();

View File

@ -13,10 +13,8 @@ import static io.opentelemetry.sdk.trace.internal.TracerConfig.disabled;
import static io.opentelemetry.sdk.trace.internal.TracerConfig.enabled;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.incubator.trace.ExtendedTracer;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanId;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
@ -42,9 +40,9 @@ class TracerConfigTest {
.addSpanProcessor(SimpleSpanProcessor.create(exporter))
.build();
Tracer tracerA = tracerProvider.get("tracerA");
Tracer tracerB = tracerProvider.get("tracerB");
Tracer tracerC = tracerProvider.get("tracerC");
ExtendedSdkTracer tracerA = (ExtendedSdkTracer) tracerProvider.get("tracerA");
ExtendedSdkTracer tracerB = (ExtendedSdkTracer) tracerProvider.get("tracerB");
ExtendedSdkTracer tracerC = (ExtendedSdkTracer) tracerProvider.get("tracerC");
Span parent;
Span child;
@ -92,9 +90,9 @@ class TracerConfigTest {
.hasParentSpanId(parent.getSpanContext().getSpanId())
.hasAttributes(Attributes.builder().put("c", "1").build()));
// tracerA and tracerC are enabled, tracerB is disabled.
assertThat(((ExtendedTracer) tracerA).isEnabled()).isTrue();
assertThat(((ExtendedTracer) tracerB).isEnabled()).isFalse();
assertThat(((ExtendedTracer) tracerA).isEnabled()).isTrue();
assertThat(tracerA.isEnabled()).isTrue();
assertThat(tracerB.isEnabled()).isFalse();
assertThat(tracerC.isEnabled()).isTrue();
}
@ParameterizedTest
@ -158,4 +156,67 @@ class TracerConfigTest {
Arguments.of(enableStartsWithD, scopeDog, enabled()),
Arguments.of(enableStartsWithD, scopeDuck, enabled()));
}
@Test
void setScopeConfigurator() {
// 1. Initially, configure all tracers to be enabled except tracerB
InMemorySpanExporter exporter = InMemorySpanExporter.create();
SdkTracerProvider tracerProvider =
SdkTracerProvider.builder()
.addTracerConfiguratorCondition(nameEquals("tracerB"), disabled())
.addSpanProcessor(SimpleSpanProcessor.create(exporter))
.build();
ExtendedSdkTracer tracerA = (ExtendedSdkTracer) tracerProvider.get("tracerA");
ExtendedSdkTracer tracerB = (ExtendedSdkTracer) tracerProvider.get("tracerB");
ExtendedSdkTracer tracerC = (ExtendedSdkTracer) tracerProvider.get("tracerC");
// verify isEnabled()
assertThat(tracerA.isEnabled()).isTrue();
assertThat(tracerB.isEnabled()).isFalse();
assertThat(tracerC.isEnabled()).isTrue();
// verify spans are emitted as expected
tracerA.spanBuilder("spanA").startSpan().end();
tracerB.spanBuilder("spanB").startSpan().end();
tracerC.spanBuilder("spanC").startSpan().end();
assertThat(exporter.getFinishedSpanItems())
.satisfiesExactlyInAnyOrder(
span -> assertThat(span).hasName("spanA"), span -> assertThat(span).hasName("spanC"));
exporter.reset();
// 2. Update config to disable all tracers
tracerProvider.setTracerConfigurator(
ScopeConfigurator.<TracerConfig>builder().setDefault(TracerConfig.disabled()).build());
// verify isEnabled()
assertThat(tracerA.isEnabled()).isFalse();
assertThat(tracerB.isEnabled()).isFalse();
assertThat(tracerC.isEnabled()).isFalse();
// verify spans are emitted as expected
tracerA.spanBuilder("spanA").startSpan().end();
tracerB.spanBuilder("spanB").startSpan().end();
tracerC.spanBuilder("spanC").startSpan().end();
assertThat(exporter.getFinishedSpanItems()).isEmpty();
// 3. Update config to restore original
tracerProvider.setTracerConfigurator(
ScopeConfigurator.<TracerConfig>builder()
.addCondition(nameEquals("tracerB"), disabled())
.build());
// verify isEnabled()
assertThat(tracerA.isEnabled()).isTrue();
assertThat(tracerB.isEnabled()).isFalse();
assertThat(tracerC.isEnabled()).isTrue();
// verify spans are emitted as expected
tracerA.spanBuilder("spanA").startSpan().end();
tracerB.spanBuilder("spanB").startSpan().end();
tracerC.spanBuilder("spanC").startSpan().end();
assertThat(exporter.getFinishedSpanItems())
.satisfiesExactly(
span -> assertThat(span).hasName("spanA"), span -> assertThat(span).hasName("spanC"));
}
}