Scope config (#6375)

This commit is contained in:
jack-berg 2024-04-18 16:36:40 -05:00 committed by GitHub
parent 1623a80d4c
commit c33febbea6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 1533 additions and 122 deletions

View File

@ -0,0 +1,237 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk;
import static io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder.nameEquals;
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.logs.Logger;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder;
import io.opentelemetry.sdk.logs.data.LogRecordData;
import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor;
import io.opentelemetry.sdk.logs.internal.LoggerConfig;
import io.opentelemetry.sdk.logs.internal.SdkLoggerProviderUtil;
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.internal.MeterConfig;
import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter;
import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import io.opentelemetry.sdk.trace.internal.SdkTracerProviderUtil;
import io.opentelemetry.sdk.trace.internal.TracerConfig;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
class ScopeConfiguratorTest {
private final InMemoryLogRecordExporter logRecordExporter = InMemoryLogRecordExporter.create();
private final InMemoryMetricReader metricReader = InMemoryMetricReader.create();
private final InMemorySpanExporter spanExporter = InMemorySpanExporter.create();
private static final InstrumentationScopeInfo scopeA = InstrumentationScopeInfo.create("scopeA");
private static final InstrumentationScopeInfo scopeB = InstrumentationScopeInfo.create("scopeB");
private static final InstrumentationScopeInfo scopeC = InstrumentationScopeInfo.create("scopeC");
/** Disable "scopeB". All other scopes are enabled by default. */
@Test
void disableScopeB() {
// Configuration ergonomics will improve after APIs stabilize
SdkTracerProviderBuilder tracerProviderBuilder = SdkTracerProvider.builder();
SdkTracerProviderUtil.addTracerConfiguratorCondition(
tracerProviderBuilder, nameEquals(scopeB.getName()), TracerConfig.disabled());
SdkMeterProviderBuilder meterProviderBuilder = SdkMeterProvider.builder();
SdkMeterProviderUtil.addMeterConfiguratorCondition(
meterProviderBuilder, nameEquals(scopeB.getName()), MeterConfig.disabled());
SdkLoggerProviderBuilder loggerProviderBuilder = SdkLoggerProvider.builder();
SdkLoggerProviderUtil.addLoggerConfiguratorCondition(
loggerProviderBuilder, nameEquals(scopeB.getName()), LoggerConfig.disabled());
OpenTelemetrySdk sdk =
OpenTelemetrySdk.builder()
.setTracerProvider(
tracerProviderBuilder
.addSpanProcessor(SimpleSpanProcessor.create(spanExporter))
.build())
.setMeterProvider(meterProviderBuilder.registerMetricReader(metricReader).build())
.setLoggerProvider(
loggerProviderBuilder
.addLogRecordProcessor(SimpleLogRecordProcessor.create(logRecordExporter))
.build())
.build();
simulateInstrumentation(sdk);
// Collect all the telemetry. Ensure we don't see any from scopeB, and that the telemetry from
// scopeA and scopeC is valid.
assertThat(spanExporter.getFinishedSpanItems())
.satisfies(
spans -> {
Map<InstrumentationScopeInfo, List<SpanData>> spansByScope =
spans.stream()
.collect(Collectors.groupingBy(SpanData::getInstrumentationScopeInfo));
assertThat(spansByScope.get(scopeA)).hasSize(1);
assertThat(spansByScope.get(scopeB)).isNull();
assertThat(spansByScope.get(scopeC)).hasSize(1);
});
assertThat(metricReader.collectAllMetrics())
.satisfies(
metrics -> {
Map<InstrumentationScopeInfo, List<MetricData>> metricsByScope =
metrics.stream()
.collect(Collectors.groupingBy(MetricData::getInstrumentationScopeInfo));
assertThat(metricsByScope.get(scopeA)).hasSize(1);
assertThat(metricsByScope.get(scopeB)).isNull();
assertThat(metricsByScope.get(scopeC)).hasSize(1);
});
assertThat(logRecordExporter.getFinishedLogRecordItems())
.satisfies(
logs -> {
Map<InstrumentationScopeInfo, List<LogRecordData>> logsByScope =
logs.stream()
.collect(Collectors.groupingBy(LogRecordData::getInstrumentationScopeInfo));
assertThat(logsByScope.get(scopeA)).hasSize(1);
assertThat(logsByScope.get(scopeB)).isNull();
assertThat(logsByScope.get(scopeC)).hasSize(1);
});
}
/** Disable all scopes by default and enable a single scope. */
@Test
void disableAllScopesExceptB() {
// Configuration ergonomics will improve after APIs stabilize
SdkTracerProviderBuilder tracerProviderBuilder = SdkTracerProvider.builder();
SdkTracerProviderUtil.setTracerConfigurator(
tracerProviderBuilder,
TracerConfig.configuratorBuilder()
.setDefault(TracerConfig.disabled())
.addCondition(nameEquals(scopeB.getName()), TracerConfig.enabled())
.build());
SdkMeterProviderBuilder meterProviderBuilder = SdkMeterProvider.builder();
SdkMeterProviderUtil.setMeterConfigurator(
meterProviderBuilder,
MeterConfig.configuratorBuilder()
.setDefault(MeterConfig.disabled())
.addCondition(nameEquals(scopeB.getName()), MeterConfig.enabled())
.build());
SdkLoggerProviderBuilder loggerProviderBuilder = SdkLoggerProvider.builder();
SdkLoggerProviderUtil.setLoggerConfigurator(
loggerProviderBuilder,
LoggerConfig.configuratorBuilder()
.setDefault(LoggerConfig.disabled())
.addCondition(nameEquals(scopeB.getName()), LoggerConfig.enabled())
.build());
OpenTelemetrySdk sdk =
OpenTelemetrySdk.builder()
.setTracerProvider(
tracerProviderBuilder
.addSpanProcessor(SimpleSpanProcessor.create(spanExporter))
.build())
.setMeterProvider(meterProviderBuilder.registerMetricReader(metricReader).build())
.setLoggerProvider(
loggerProviderBuilder
.addLogRecordProcessor(SimpleLogRecordProcessor.create(logRecordExporter))
.build())
.build();
simulateInstrumentation(sdk);
// Collect all the telemetry. Ensure we only see telemetry from scopeB, since other scopes have
// been disabled by default.
assertThat(spanExporter.getFinishedSpanItems())
.satisfies(
spans -> {
Map<InstrumentationScopeInfo, List<SpanData>> spansByScope =
spans.stream()
.collect(Collectors.groupingBy(SpanData::getInstrumentationScopeInfo));
assertThat(spansByScope.get(scopeA)).isNull();
assertThat(spansByScope.get(scopeB)).hasSize(1);
assertThat(spansByScope.get(scopeC)).isNull();
});
assertThat(metricReader.collectAllMetrics())
.satisfies(
metrics -> {
Map<InstrumentationScopeInfo, List<MetricData>> metricsByScope =
metrics.stream()
.collect(Collectors.groupingBy(MetricData::getInstrumentationScopeInfo));
assertThat(metricsByScope.get(scopeA)).isNull();
assertThat(metricsByScope.get(scopeB)).hasSize(1);
assertThat(metricsByScope.get(scopeC)).isNull();
});
assertThat(logRecordExporter.getFinishedLogRecordItems())
.satisfies(
logs -> {
Map<InstrumentationScopeInfo, List<LogRecordData>> logsByScope =
logs.stream()
.collect(Collectors.groupingBy(LogRecordData::getInstrumentationScopeInfo));
assertThat(logsByScope.get(scopeA)).isNull();
assertThat(logsByScope.get(scopeB)).hasSize(1);
assertThat(logsByScope.get(scopeC)).isNull();
});
}
/**
* Emit spans, metrics and logs in a hierarchy of 3 scopes: scopeA -> scopeB -> scopeC. Exercise
* the scope config which is common across all signals.
*/
private static void simulateInstrumentation(OpenTelemetry openTelemetry) {
// Start scopeA
Tracer scopeATracer = openTelemetry.getTracer(scopeA.getName());
Meter scopeAMeter = openTelemetry.getMeter(scopeA.getName());
Logger scopeALogger = openTelemetry.getLogsBridge().get(scopeA.getName());
Span spanA = scopeATracer.spanBuilder("spanA").startSpan();
try (Scope spanAScope = spanA.makeCurrent()) {
scopeALogger.logRecordBuilder().setBody("scopeA log message").emit();
// Start scopeB
Tracer scopeBTracer = openTelemetry.getTracer(scopeB.getName());
Meter scopeBMeter = openTelemetry.getMeter(scopeB.getName());
Logger scopeBLogger = openTelemetry.getLogsBridge().get(scopeB.getName());
Span spanB = scopeBTracer.spanBuilder("spanB").startSpan();
try (Scope spanBScope = spanB.makeCurrent()) {
scopeBLogger.logRecordBuilder().setBody("scopeB log message").emit();
// Start scopeC
Tracer scopeCTracer = openTelemetry.getTracer(scopeC.getName());
Meter scopeCMeter = openTelemetry.getMeter(scopeC.getName());
Logger scopeCLogger = openTelemetry.getLogsBridge().get(scopeC.getName());
Span spanC = scopeCTracer.spanBuilder("spanC").startSpan();
try (Scope spanCScope = spanB.makeCurrent()) {
scopeCLogger.logRecordBuilder().setBody("scopeC log message").emit();
} finally {
spanC.end();
scopeCMeter.counterBuilder("scopeCCounter").build().add(1);
}
// End scopeC
} finally {
spanB.end();
scopeBMeter.counterBuilder("scopeBCounter").build().add(1);
}
// End scopeB
} finally {
spanA.end();
scopeAMeter.counterBuilder("scopeACounter").build().add(1);
}
// End scopeA
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.internal;
import java.util.function.Predicate;
import java.util.regex.Pattern;
/**
* Utilities for glob pattern matching.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public final class GlobUtil {
private GlobUtil() {}
/**
* Return a predicate that returns {@code true} if a string matches the {@code globPattern}.
*
* <p>{@code globPattern} may contain the wildcard characters {@code *} and {@code ?} with the
* following matching criteria:
*
* <ul>
* <li>{@code *} matches 0 or more instances of any character
* <li>{@code ?} matches exactly one instance of any character
* </ul>
*/
public static Predicate<String> toGlobPatternPredicate(String globPattern) {
// Match all
if (globPattern.equals("*")) {
return unused -> true;
}
// If globPattern contains '*' or '?', convert it to a regex and return corresponding predicate
for (int i = 0; i < globPattern.length(); i++) {
char c = globPattern.charAt(i);
if (c == '*' || c == '?') {
Pattern pattern = toRegexPattern(globPattern);
return string -> pattern.matcher(string).matches();
}
}
// Exact match, ignoring case
return globPattern::equalsIgnoreCase;
}
/**
* Transform the {@code globPattern} to a regex by converting {@code *} to {@code .*}, {@code ?}
* to {@code .}, and escaping other regex special characters.
*/
private static Pattern toRegexPattern(String globPattern) {
int tokenStart = -1;
StringBuilder patternBuilder = new StringBuilder();
for (int i = 0; i < globPattern.length(); i++) {
char c = globPattern.charAt(i);
if (c == '*' || c == '?') {
if (tokenStart != -1) {
patternBuilder.append(Pattern.quote(globPattern.substring(tokenStart, i)));
tokenStart = -1;
}
if (c == '*') {
patternBuilder.append(".*");
} else {
// c == '?'
patternBuilder.append(".");
}
} else {
if (tokenStart == -1) {
tokenStart = i;
}
}
}
if (tokenStart != -1) {
patternBuilder.append(Pattern.quote(globPattern.substring(tokenStart)));
}
return Pattern.compile(patternBuilder.toString());
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.internal;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import java.util.function.Function;
/**
* A {@link ScopeConfigurator} computes configuration for a given {@link InstrumentationScopeInfo}.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
@FunctionalInterface
public interface ScopeConfigurator<T> extends Function<InstrumentationScopeInfo, T> {
/** Create a new builder. */
static <T> ScopeConfiguratorBuilder<T> builder() {
return new ScopeConfiguratorBuilder<>(unused -> null);
}
/**
* Convert this {@link ScopeConfigurator} to a builder. Additional added matchers only apply when
* {@link #apply(Object)} returns {@code null}. If this configurator contains {@link
* ScopeConfiguratorBuilder#setDefault(Object)}, additional matchers are never applied.
*/
default ScopeConfiguratorBuilder<T> toBuilder() {
return new ScopeConfiguratorBuilder<>(this);
}
}

View File

@ -0,0 +1,114 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.internal;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import javax.annotation.Nullable;
/**
* Builder for {@link ScopeConfigurator}.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*
* @param <T> The scope configuration object, e.g. {@code TracerConfig}, {@code LoggerConfig},
* {@code MeterConfig}.
*/
public final class ScopeConfiguratorBuilder<T> {
private final ScopeConfigurator<T> baseScopeConfigurator;
@Nullable private T defaultScopeConfig;
private final List<Condition<T>> conditions = new ArrayList<>();
ScopeConfiguratorBuilder(ScopeConfigurator<T> baseScopeConfigurator) {
this.baseScopeConfigurator = baseScopeConfigurator;
}
/**
* Set the default scope config, which is returned by {@link ScopeConfigurator#apply(Object)} if a
* {@link InstrumentationScopeInfo} does not match any {@link #addCondition(Predicate, Object)
* conditions}. If a default is not set, an SDK defined default is used.
*/
public ScopeConfiguratorBuilder<T> setDefault(T defaultScopeConfig) {
this.defaultScopeConfig = defaultScopeConfig;
return this;
}
/**
* Add a condition. Conditions are evaluated in order. The {@code scopeConfig} for the first match
* is returned by {@link ScopeConfigurator#apply(Object)}.
*
* @param scopePredicate predicate that {@link InstrumentationScopeInfo}s are evaluated against
* @param scopeConfig the scope config to use when this condition is the first matching {@code
* scopePredicate}
* @see #nameMatchesGlob(String)
* @see #nameEquals(String)
*/
public ScopeConfiguratorBuilder<T> addCondition(
Predicate<InstrumentationScopeInfo> scopePredicate, T scopeConfig) {
conditions.add(new Condition<>(scopePredicate, scopeConfig));
return this;
}
/**
* Helper function for pattern matching {@link InstrumentationScopeInfo#getName()} against the
* {@code globPattern}.
*
* <p>{@code globPattern} may contain the wildcard characters {@code *} and {@code ?} with the
* following matching criteria:
*
* <ul>
* <li>{@code *} matches 0 or more instances of any character
* <li>{@code ?} matches exactly one instance of any character
* </ul>
*
* @see #addCondition(Predicate, Object)
*/
public static Predicate<InstrumentationScopeInfo> nameMatchesGlob(String globPattern) {
Predicate<String> globPredicate = GlobUtil.toGlobPatternPredicate(globPattern);
return scopeInfo -> globPredicate.test(scopeInfo.getName());
}
/**
* Helper function for exact matching {@link InstrumentationScopeInfo#getName()} against the
* {@code scopeName}.
*
* @see #addCondition(Predicate, Object)
*/
public static Predicate<InstrumentationScopeInfo> nameEquals(String scopeName) {
return scopeInfo -> scopeInfo.getName().equals(scopeName);
}
/** Build a {@link ScopeConfigurator} with the configuration of this builder. */
public ScopeConfigurator<T> build() {
// TODO: return an instance with toString implementation which self describes rules
return scopeInfo -> {
T scopeConfig = baseScopeConfigurator.apply(scopeInfo);
if (scopeConfig != null) {
return scopeConfig;
}
for (Condition<T> condition : conditions) {
if (condition.scopeMatcher.test(scopeInfo)) {
return condition.scopeConfig;
}
}
return defaultScopeConfig;
};
}
private static final class Condition<T> {
private final Predicate<InstrumentationScopeInfo> scopeMatcher;
private final T scopeConfig;
private Condition(Predicate<InstrumentationScopeInfo> scopeMatcher, T scopeConfig) {
this.scopeMatcher = scopeMatcher;
this.scopeConfig = scopeConfig;
}
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.internal;
import static io.opentelemetry.sdk.internal.GlobUtil.toGlobPatternPredicate;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
class GlobUtilTest {
@Test
void matchesName() {
assertThat(toGlobPatternPredicate("foo").test("foo")).isTrue();
assertThat(toGlobPatternPredicate("foo").test("Foo")).isTrue();
assertThat(toGlobPatternPredicate("foo").test("bar")).isFalse();
assertThat(toGlobPatternPredicate("fo?").test("foo")).isTrue();
assertThat(toGlobPatternPredicate("fo??").test("fooo")).isTrue();
assertThat(toGlobPatternPredicate("fo?").test("fob")).isTrue();
assertThat(toGlobPatternPredicate("fo?").test("fooo")).isFalse();
assertThat(toGlobPatternPredicate("*").test("foo")).isTrue();
assertThat(toGlobPatternPredicate("*").test("bar")).isTrue();
assertThat(toGlobPatternPredicate("*").test("baz")).isTrue();
assertThat(toGlobPatternPredicate("*").test("foo.bar.baz")).isTrue();
assertThat(toGlobPatternPredicate("*").test(null)).isTrue();
assertThat(toGlobPatternPredicate("*").test("")).isTrue();
assertThat(toGlobPatternPredicate("fo*").test("fo")).isTrue();
assertThat(toGlobPatternPredicate("fo*").test("foo")).isTrue();
assertThat(toGlobPatternPredicate("fo*").test("fooo")).isTrue();
assertThat(toGlobPatternPredicate("fo*").test("foo.bar.baz")).isTrue();
assertThat(toGlobPatternPredicate("*bar").test("sandbar")).isTrue();
assertThat(toGlobPatternPredicate("fo*b*").test("foobar")).isTrue();
assertThat(toGlobPatternPredicate("fo*b*").test("foob")).isTrue();
assertThat(toGlobPatternPredicate("fo*b*").test("foo bar")).isTrue();
assertThat(toGlobPatternPredicate("fo? b??").test("foo bar")).isTrue();
assertThat(toGlobPatternPredicate("fo? b??").test("fooo bar")).isFalse();
assertThat(toGlobPatternPredicate("fo* ba?").test("foo is not bar")).isTrue();
assertThat(toGlobPatternPredicate("fo? b*").test("fox beetles for lunch")).isTrue();
assertThat(toGlobPatternPredicate("f()[]$^.{}|").test("f()[]$^.{}|")).isTrue();
assertThat(toGlobPatternPredicate("f()[]$^.{}|?").test("f()[]$^.{}|o")).isTrue();
assertThat(toGlobPatternPredicate("f()[]$^.{}|*").test("f()[]$^.{}|ooo")).isTrue();
}
}

View File

@ -7,23 +7,34 @@ package io.opentelemetry.sdk.logs;
import io.opentelemetry.api.logs.LogRecordBuilder;
import io.opentelemetry.api.logs.Logger;
import io.opentelemetry.api.logs.LoggerProvider;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.logs.internal.LoggerConfig;
/** SDK implementation of {@link Logger}. */
final class SdkLogger implements Logger {
private static final Logger NOOP_LOGGER = LoggerProvider.noop().get("noop");
private final LoggerSharedState loggerSharedState;
private final InstrumentationScopeInfo instrumentationScopeInfo;
private final LoggerConfig loggerConfig;
SdkLogger(
LoggerSharedState loggerSharedState, InstrumentationScopeInfo instrumentationScopeInfo) {
LoggerSharedState loggerSharedState,
InstrumentationScopeInfo instrumentationScopeInfo,
LoggerConfig loggerConfig) {
this.loggerSharedState = loggerSharedState;
this.instrumentationScopeInfo = instrumentationScopeInfo;
this.loggerConfig = loggerConfig;
}
@Override
public LogRecordBuilder logRecordBuilder() {
return new SdkLogRecordBuilder(loggerSharedState, instrumentationScopeInfo);
if (loggerConfig.isEnabled()) {
return new SdkLogRecordBuilder(loggerSharedState, instrumentationScopeInfo);
}
return NOOP_LOGGER.logRecordBuilder();
}
// VisibleForTesting

View File

@ -11,7 +11,10 @@ import io.opentelemetry.api.logs.LoggerBuilder;
import io.opentelemetry.api.logs.LoggerProvider;
import io.opentelemetry.sdk.common.Clock;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ComponentRegistry;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.logs.internal.LoggerConfig;
import io.opentelemetry.sdk.resources.Resource;
import java.io.Closeable;
import java.util.List;
@ -33,6 +36,7 @@ public final class SdkLoggerProvider implements LoggerProvider, Closeable {
private final LoggerSharedState sharedState;
private final ComponentRegistry<SdkLogger> loggerComponentRegistry;
private final ScopeConfigurator<LoggerConfig> loggerConfigurator;
private final boolean isNoopLogRecordProcessor;
/**
@ -48,16 +52,27 @@ public final class SdkLoggerProvider implements LoggerProvider, Closeable {
Resource resource,
Supplier<LogLimits> logLimitsSupplier,
List<LogRecordProcessor> processors,
Clock clock) {
Clock clock,
ScopeConfigurator<LoggerConfig> loggerConfigurator) {
LogRecordProcessor logRecordProcessor = LogRecordProcessor.composite(processors);
this.sharedState =
new LoggerSharedState(resource, logLimitsSupplier, logRecordProcessor, clock);
this.loggerComponentRegistry =
new ComponentRegistry<>(
instrumentationScopeInfo -> new SdkLogger(sharedState, instrumentationScopeInfo));
instrumentationScopeInfo ->
new SdkLogger(
sharedState,
instrumentationScopeInfo,
getLoggerConfig(instrumentationScopeInfo)));
this.loggerConfigurator = loggerConfigurator;
this.isNoopLogRecordProcessor = logRecordProcessor instanceof NoopLogRecordProcessor;
}
private LoggerConfig getLoggerConfig(InstrumentationScopeInfo instrumentationScopeInfo) {
LoggerConfig loggerConfig = loggerConfigurator.apply(instrumentationScopeInfo);
return loggerConfig == null ? LoggerConfig.defaultConfig() : loggerConfig;
}
@Override
public Logger get(String instrumentationScopeName) {
return loggerComponentRegistry.get(

View File

@ -11,11 +11,16 @@ import io.opentelemetry.api.logs.LogRecordBuilder;
import io.opentelemetry.api.logs.Logger;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.common.Clock;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder;
import io.opentelemetry.sdk.logs.data.LogRecordData;
import io.opentelemetry.sdk.logs.internal.LoggerConfig;
import io.opentelemetry.sdk.resources.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
@ -29,6 +34,8 @@ public final class SdkLoggerProviderBuilder {
private Resource resource = Resource.getDefault();
private Supplier<LogLimits> logLimitsSupplier = LogLimits::getDefault;
private Clock clock = Clock.getDefault();
private ScopeConfiguratorBuilder<LoggerConfig> loggerConfiguratorBuilder =
LoggerConfig.configuratorBuilder();
SdkLoggerProviderBuilder() {}
@ -100,12 +107,47 @@ public final class SdkLoggerProviderBuilder {
return this;
}
/**
* Set the logger configurator, which computes {@link LoggerConfig} for each {@link
* InstrumentationScopeInfo}.
*
* <p>Overrides any matchers added via {@link #addLoggerConfiguratorCondition(Predicate,
* LoggerConfig)}.
*
* @see LoggerConfig#configuratorBuilder()
*/
SdkLoggerProviderBuilder setLoggerConfigurator(
ScopeConfigurator<LoggerConfig> loggerConfigurator) {
this.loggerConfiguratorBuilder = loggerConfigurator.toBuilder();
return this;
}
/**
* Adds a condition to the logger configurator, which computes {@link LoggerConfig} for each
* {@link InstrumentationScopeInfo}.
*
* <p>Applies after any previously added conditions.
*
* <p>If {@link #setLoggerConfigurator(ScopeConfigurator)} was previously called, this condition
* will only be applied if the {@link ScopeConfigurator#apply(Object)} returns null for the
* matched {@link InstrumentationScopeInfo}(s).
*
* @see ScopeConfiguratorBuilder#nameEquals(String)
* @see ScopeConfiguratorBuilder#nameMatchesGlob(String)
*/
SdkLoggerProviderBuilder addLoggerConfiguratorCondition(
Predicate<InstrumentationScopeInfo> scopeMatcher, LoggerConfig loggerConfig) {
this.loggerConfiguratorBuilder.addCondition(scopeMatcher, loggerConfig);
return this;
}
/**
* Create a {@link SdkLoggerProvider} instance.
*
* @return an instance configured with the provided options
*/
public SdkLoggerProvider build() {
return new SdkLoggerProvider(resource, logLimitsSupplier, logRecordProcessors, clock);
return new SdkLoggerProvider(
resource, logLimitsSupplier, logRecordProcessors, clock, loggerConfiguratorBuilder.build());
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.logs.internal;
import com.google.auto.value.AutoValue;
import io.opentelemetry.api.logs.Logger;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder;
import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder;
import java.util.function.Predicate;
import javax.annotation.concurrent.Immutable;
/**
* A collection of configuration options which define the behavior of a {@link Logger}.
*
* @see SdkLoggerProviderUtil#setLoggerConfigurator(SdkLoggerProviderBuilder, ScopeConfigurator)
* @see SdkLoggerProviderUtil#addLoggerConfiguratorCondition(SdkLoggerProviderBuilder, Predicate,
* LoggerConfig)
*/
@AutoValue
@Immutable
public abstract class LoggerConfig {
private static final LoggerConfig DEFAULT_CONFIG =
new AutoValue_LoggerConfig(/* enabled= */ true);
private static final LoggerConfig DISABLED_CONFIG =
new AutoValue_LoggerConfig(/* enabled= */ false);
/** Returns a disabled {@link LoggerConfig}. */
public static LoggerConfig disabled() {
return DISABLED_CONFIG;
}
/** Returns an enabled {@link LoggerConfig}. */
public static LoggerConfig enabled() {
return DEFAULT_CONFIG;
}
/**
* Returns the default {@link LoggerConfig}, which is used when no configurator is set or when the
* logger configurator returns {@code null} for a {@link InstrumentationScopeInfo}.
*/
public static LoggerConfig defaultConfig() {
return DEFAULT_CONFIG;
}
/**
* Create a {@link ScopeConfiguratorBuilder} for configuring {@link
* SdkLoggerProviderUtil#setLoggerConfigurator(SdkLoggerProviderBuilder, ScopeConfigurator)}.
*/
public static ScopeConfiguratorBuilder<LoggerConfig> configuratorBuilder() {
return ScopeConfigurator.builder();
}
LoggerConfig() {}
/** Returns {@code true} if this logger is enabled. Defaults to {@code true}. */
public abstract boolean isEnabled();
}

View File

@ -0,0 +1,58 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.logs.internal;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Predicate;
/**
* A collection of methods that allow use of experimental features prior to availability in public
* APIs.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public final class SdkLoggerProviderUtil {
private SdkLoggerProviderUtil() {}
/** Reflectively set the {@link ScopeConfigurator} to the {@link SdkLoggerProviderBuilder}. */
public static void setLoggerConfigurator(
SdkLoggerProviderBuilder sdkLoggerProviderBuilder,
ScopeConfigurator<LoggerConfig> loggerConfigurator) {
try {
Method method =
SdkLoggerProviderBuilder.class.getDeclaredMethod(
"setLoggerConfigurator", ScopeConfigurator.class);
method.setAccessible(true);
method.invoke(sdkLoggerProviderBuilder, loggerConfigurator);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(
"Error calling setLoggerConfigurator on SdkLoggerProviderBuilder", e);
}
}
/** Reflectively add a logger configurator condition to the {@link SdkLoggerProviderBuilder}. */
public static void addLoggerConfiguratorCondition(
SdkLoggerProviderBuilder sdkLoggerProviderBuilder,
Predicate<InstrumentationScopeInfo> scopeMatcher,
LoggerConfig loggerConfig) {
try {
Method method =
SdkLoggerProviderBuilder.class.getDeclaredMethod(
"addLoggerConfiguratorCondition", Predicate.class, LoggerConfig.class);
method.setAccessible(true);
method.invoke(sdkLoggerProviderBuilder, scopeMatcher, loggerConfig);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(
"Error calling addLoggerConfiguratorCondition on SdkLoggerProviderBuilder", e);
}
}
}

View File

@ -0,0 +1,127 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.logs;
import static io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder.nameEquals;
import static io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder.nameMatchesGlob;
import static io.opentelemetry.sdk.logs.internal.LoggerConfig.defaultConfig;
import static io.opentelemetry.sdk.logs.internal.LoggerConfig.enabled;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import io.opentelemetry.api.logs.Logger;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.logs.data.LogRecordData;
import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor;
import io.opentelemetry.sdk.logs.internal.LoggerConfig;
import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
class LoggerConfigTest {
@Test
void disableScopes() {
InMemoryLogRecordExporter exporter = InMemoryLogRecordExporter.create();
SdkLoggerProvider loggerProvider =
SdkLoggerProvider.builder()
// Disable loggerB. Since loggers are enabled by default, loggerA and loggerC are
// enabled.
.addLoggerConfiguratorCondition(nameEquals("loggerB"), LoggerConfig.disabled())
.addLogRecordProcessor(SimpleLogRecordProcessor.create(exporter))
.build();
Logger loggerA = loggerProvider.get("loggerA");
Logger loggerB = loggerProvider.get("loggerB");
Logger loggerC = loggerProvider.get("loggerC");
loggerA.logRecordBuilder().setBody("messageA").emit();
loggerB.logRecordBuilder().setBody("messageB").emit();
loggerC.logRecordBuilder().setBody("messageC").emit();
// Only logs from loggerA and loggerC should be seen
assertThat(exporter.getFinishedLogRecordItems())
.satisfies(
metrics -> {
Map<InstrumentationScopeInfo, List<LogRecordData>> logsByScope =
metrics.stream()
.collect(Collectors.groupingBy(LogRecordData::getInstrumentationScopeInfo));
assertThat(logsByScope.get(InstrumentationScopeInfo.create("loggerA"))).hasSize(1);
assertThat(logsByScope.get(InstrumentationScopeInfo.create("loggerB"))).isNull();
assertThat(logsByScope.get(InstrumentationScopeInfo.create("loggerC"))).hasSize(1);
});
}
@ParameterizedTest
@MethodSource("loggerConfiguratorArgs")
void loggerConfigurator(
ScopeConfigurator<LoggerConfig> loggerConfigurator,
InstrumentationScopeInfo scope,
LoggerConfig expectedLoggerConfig) {
LoggerConfig loggerConfig = loggerConfigurator.apply(scope);
loggerConfig = loggerConfig == null ? defaultConfig() : loggerConfig;
assertThat(loggerConfig).isEqualTo(expectedLoggerConfig);
}
private static final InstrumentationScopeInfo scopeCat = InstrumentationScopeInfo.create("cat");
private static final InstrumentationScopeInfo scopeDog = InstrumentationScopeInfo.create("dog");
private static final InstrumentationScopeInfo scopeDuck = InstrumentationScopeInfo.create("duck");
private static Stream<Arguments> loggerConfiguratorArgs() {
ScopeConfigurator<LoggerConfig> defaultConfigurator =
LoggerConfig.configuratorBuilder().build();
ScopeConfigurator<LoggerConfig> disableCat =
LoggerConfig.configuratorBuilder()
.addCondition(nameEquals("cat"), LoggerConfig.disabled())
// Second matching rule for cat should be ignored
.addCondition(nameEquals("cat"), enabled())
.build();
ScopeConfigurator<LoggerConfig> disableStartsWithD =
LoggerConfig.configuratorBuilder()
.addCondition(nameMatchesGlob("d*"), LoggerConfig.disabled())
.build();
ScopeConfigurator<LoggerConfig> enableCat =
LoggerConfig.configuratorBuilder()
.setDefault(LoggerConfig.disabled())
.addCondition(nameEquals("cat"), enabled())
// Second matching rule for cat should be ignored
.addCondition(nameEquals("cat"), LoggerConfig.disabled())
.build();
ScopeConfigurator<LoggerConfig> enableStartsWithD =
LoggerConfig.configuratorBuilder()
.setDefault(LoggerConfig.disabled())
.addCondition(nameMatchesGlob("d*"), LoggerConfig.enabled())
.build();
return Stream.of(
// default
Arguments.of(defaultConfigurator, scopeCat, defaultConfig()),
Arguments.of(defaultConfigurator, scopeDog, defaultConfig()),
Arguments.of(defaultConfigurator, scopeDuck, defaultConfig()),
// default enabled, disable cat
Arguments.of(disableCat, scopeCat, LoggerConfig.disabled()),
Arguments.of(disableCat, scopeDog, enabled()),
Arguments.of(disableCat, scopeDuck, enabled()),
// default enabled, disable pattern
Arguments.of(disableStartsWithD, scopeCat, enabled()),
Arguments.of(disableStartsWithD, scopeDog, LoggerConfig.disabled()),
Arguments.of(disableStartsWithD, scopeDuck, LoggerConfig.disabled()),
// default disabled, enable cat
Arguments.of(enableCat, scopeCat, enabled()),
Arguments.of(enableCat, scopeDog, LoggerConfig.disabled()),
Arguments.of(enableCat, scopeDuck, LoggerConfig.disabled()),
// default disabled, enable pattern
Arguments.of(enableStartsWithD, scopeCat, LoggerConfig.disabled()),
Arguments.of(enableStartsWithD, scopeDog, enabled()),
Arguments.of(enableStartsWithD, scopeDuck, enabled()));
}
}

View File

@ -24,6 +24,7 @@ import io.opentelemetry.api.logs.LogRecordBuilder;
import io.opentelemetry.sdk.common.Clock;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.logs.internal.LoggerConfig;
import io.opentelemetry.sdk.resources.Resource;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@ -44,7 +45,7 @@ class SdkLoggerTest {
when(state.getLogRecordProcessor()).thenReturn(logRecordProcessor);
when(state.getClock()).thenReturn(clock);
SdkLogger logger = new SdkLogger(state, info);
SdkLogger logger = new SdkLogger(state, info, LoggerConfig.defaultConfig());
LogRecordBuilder logRecordBuilder = logger.logRecordBuilder();
logRecordBuilder.setBody("foo");

View File

@ -15,6 +15,7 @@ import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.api.metrics.ObservableMeasurement;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.internal.MeterConfig;
import io.opentelemetry.sdk.metrics.internal.export.RegisteredReader;
import io.opentelemetry.sdk.metrics.internal.state.CallbackRegistration;
import io.opentelemetry.sdk.metrics.internal.state.MeterProviderSharedState;
@ -55,14 +56,17 @@ final class SdkMeter implements Meter {
private final InstrumentationScopeInfo instrumentationScopeInfo;
private final MeterProviderSharedState meterProviderSharedState;
private final MeterSharedState meterSharedState;
private final MeterConfig meterConfig;
SdkMeter(
MeterProviderSharedState meterProviderSharedState,
InstrumentationScopeInfo instrumentationScopeInfo,
List<RegisteredReader> registeredReaders) {
List<RegisteredReader> registeredReaders,
MeterConfig meterConfig) {
this.instrumentationScopeInfo = instrumentationScopeInfo;
this.meterProviderSharedState = meterProviderSharedState;
this.meterSharedState = MeterSharedState.create(instrumentationScopeInfo, registeredReaders);
this.meterConfig = meterConfig;
}
// Visible for testing
@ -82,34 +86,32 @@ final class SdkMeter implements Meter {
@Override
public LongCounterBuilder counterBuilder(String name) {
return !checkValidInstrumentName(name)
? NOOP_METER.counterBuilder(NOOP_INSTRUMENT_NAME)
: new SdkLongCounter.SdkLongCounterBuilder(
meterProviderSharedState, meterSharedState, name);
return meterConfig.isEnabled() && checkValidInstrumentName(name)
? new SdkLongCounter.SdkLongCounterBuilder(meterProviderSharedState, meterSharedState, name)
: NOOP_METER.counterBuilder(NOOP_INSTRUMENT_NAME);
}
@Override
public LongUpDownCounterBuilder upDownCounterBuilder(String name) {
return !checkValidInstrumentName(name)
? NOOP_METER.upDownCounterBuilder(NOOP_INSTRUMENT_NAME)
: new SdkLongUpDownCounter.SdkLongUpDownCounterBuilder(
meterProviderSharedState, meterSharedState, name);
return meterConfig.isEnabled() && checkValidInstrumentName(name)
? new SdkLongUpDownCounter.SdkLongUpDownCounterBuilder(
meterProviderSharedState, meterSharedState, name)
: NOOP_METER.upDownCounterBuilder(NOOP_INSTRUMENT_NAME);
}
@Override
public DoubleHistogramBuilder histogramBuilder(String name) {
return !checkValidInstrumentName(name)
? NOOP_METER.histogramBuilder(NOOP_INSTRUMENT_NAME)
: new SdkDoubleHistogram.SdkDoubleHistogramBuilder(
meterProviderSharedState, meterSharedState, name);
return meterConfig.isEnabled() && checkValidInstrumentName(name)
? new SdkDoubleHistogram.SdkDoubleHistogramBuilder(
meterProviderSharedState, meterSharedState, name)
: NOOP_METER.histogramBuilder(NOOP_INSTRUMENT_NAME);
}
@Override
public DoubleGaugeBuilder gaugeBuilder(String name) {
return !checkValidInstrumentName(name)
? NOOP_METER.gaugeBuilder(NOOP_INSTRUMENT_NAME)
: new SdkDoubleGauge.SdkDoubleGaugeBuilder(
meterProviderSharedState, meterSharedState, name);
return meterConfig.isEnabled() && checkValidInstrumentName(name)
? new SdkDoubleGauge.SdkDoubleGaugeBuilder(meterProviderSharedState, meterSharedState, name)
: NOOP_METER.gaugeBuilder(NOOP_INSTRUMENT_NAME);
}
@Override
@ -117,6 +119,9 @@ final class SdkMeter implements Meter {
Runnable callback,
ObservableMeasurement observableMeasurement,
ObservableMeasurement... additionalMeasurements) {
if (!meterConfig.isEnabled()) {
return NOOP_METER.batchCallback(callback, observableMeasurement, additionalMeasurements);
}
Set<ObservableMeasurement> measurements = new HashSet<>();
measurements.add(observableMeasurement);
Collections.addAll(measurements, additionalMeasurements);

View File

@ -11,11 +11,14 @@ import io.opentelemetry.api.metrics.MeterBuilder;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.sdk.common.Clock;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ComponentRegistry;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.export.CollectionRegistration;
import io.opentelemetry.sdk.metrics.export.MetricProducer;
import io.opentelemetry.sdk.metrics.export.MetricReader;
import io.opentelemetry.sdk.metrics.internal.MeterConfig;
import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter;
import io.opentelemetry.sdk.metrics.internal.export.CardinalityLimitSelector;
@ -49,6 +52,7 @@ public final class SdkMeterProvider implements MeterProvider, Closeable {
private final List<MetricProducer> metricProducers;
private final MeterProviderSharedState sharedState;
private final ComponentRegistry<SdkMeter> registry;
private final ScopeConfigurator<MeterConfig> meterConfigurator;
private final AtomicBoolean isClosed = new AtomicBoolean(false);
/** Returns a new {@link SdkMeterProviderBuilder} for {@link SdkMeterProvider}. */
@ -62,7 +66,8 @@ public final class SdkMeterProvider implements MeterProvider, Closeable {
List<MetricProducer> metricProducers,
Clock clock,
Resource resource,
ExemplarFilter exemplarFilter) {
ExemplarFilter exemplarFilter,
ScopeConfigurator<MeterConfig> meterConfigurator) {
long startEpochNanos = clock.now();
this.registeredViews = registeredViews;
this.registeredReaders =
@ -79,7 +84,12 @@ public final class SdkMeterProvider implements MeterProvider, Closeable {
this.registry =
new ComponentRegistry<>(
instrumentationLibraryInfo ->
new SdkMeter(sharedState, instrumentationLibraryInfo, registeredReaders));
new SdkMeter(
sharedState,
instrumentationLibraryInfo,
registeredReaders,
getMeterConfig(instrumentationLibraryInfo)));
this.meterConfigurator = meterConfigurator;
for (RegisteredReader registeredReader : registeredReaders) {
List<MetricProducer> readerMetricProducers = new ArrayList<>(metricProducers);
readerMetricProducers.add(new LeasedMetricProducer(registry, sharedState, registeredReader));
@ -90,6 +100,11 @@ public final class SdkMeterProvider implements MeterProvider, Closeable {
}
}
private MeterConfig getMeterConfig(InstrumentationScopeInfo instrumentationScopeInfo) {
MeterConfig meterConfig = meterConfigurator.apply(instrumentationScopeInfo);
return meterConfig == null ? MeterConfig.defaultConfig() : meterConfig;
}
@Override
public MeterBuilder meterBuilder(String instrumentationScopeName) {
if (registeredReaders.isEmpty()) {

View File

@ -6,8 +6,12 @@
package io.opentelemetry.sdk.metrics;
import io.opentelemetry.sdk.common.Clock;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder;
import io.opentelemetry.sdk.metrics.export.MetricProducer;
import io.opentelemetry.sdk.metrics.export.MetricReader;
import io.opentelemetry.sdk.metrics.internal.MeterConfig;
import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
import io.opentelemetry.sdk.metrics.internal.debug.SourceInfo;
import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter;
@ -18,6 +22,7 @@ import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
/**
* Builder class for the {@link SdkMeterProvider}.
@ -40,6 +45,8 @@ public final class SdkMeterProviderBuilder {
private final List<MetricProducer> metricProducers = new ArrayList<>();
private final List<RegisteredView> registeredViews = new ArrayList<>();
private ExemplarFilter exemplarFilter = DEFAULT_EXEMPLAR_FILTER;
private ScopeConfiguratorBuilder<MeterConfig> meterConfiguratorBuilder =
MeterConfig.configuratorBuilder();
SdkMeterProviderBuilder() {}
@ -150,9 +157,48 @@ public final class SdkMeterProviderBuilder {
return this;
}
/**
* Set the meter configurator, which computes {@link MeterConfig} for each {@link
* InstrumentationScopeInfo}.
*
* <p>Overrides any matchers added via {@link #addMeterConfiguratorCondition(Predicate,
* MeterConfig)}.
*
* @see MeterConfig#configuratorBuilder()
*/
SdkMeterProviderBuilder setMeterConfigurator(ScopeConfigurator<MeterConfig> meterConfigurator) {
this.meterConfiguratorBuilder = meterConfigurator.toBuilder();
return this;
}
/**
* Adds a condition to the meter configurator, which computes {@link MeterConfig} for each {@link
* InstrumentationScopeInfo}.
*
* <p>Applies after any previously added conditions.
*
* <p>If {@link #setMeterConfigurator(ScopeConfigurator)} was previously called, this condition
* will only be applied if the {@link ScopeConfigurator#apply(Object)} returns null for the
* matched {@link InstrumentationScopeInfo}(s).
*
* @see ScopeConfiguratorBuilder#nameEquals(String)
* @see ScopeConfiguratorBuilder#nameMatchesGlob(String)
*/
SdkMeterProviderBuilder addMeterConfiguratorCondition(
Predicate<InstrumentationScopeInfo> scopeMatcher, MeterConfig meterConfig) {
this.meterConfiguratorBuilder.addCondition(scopeMatcher, meterConfig);
return this;
}
/** Returns an {@link SdkMeterProvider} built with the configuration of this builder. */
public SdkMeterProvider build() {
return new SdkMeterProvider(
registeredViews, metricReaders, metricProducers, clock, resource, exemplarFilter);
registeredViews,
metricReaders,
metricProducers,
clock,
resource,
exemplarFilter,
meterConfiguratorBuilder.build());
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.metrics.internal;
import com.google.auto.value.AutoValue;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder;
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
import java.util.function.Predicate;
import javax.annotation.concurrent.Immutable;
/**
* A collection of configuration options which define the behavior of a {@link Meter}.
*
* @see SdkMeterProviderUtil#setMeterConfigurator(SdkMeterProviderBuilder, ScopeConfigurator)
* @see SdkMeterProviderUtil#addMeterConfiguratorCondition(SdkMeterProviderBuilder, Predicate,
* MeterConfig)
*/
@AutoValue
@Immutable
public abstract class MeterConfig {
private static final MeterConfig DEFAULT_CONFIG = new AutoValue_MeterConfig(/* enabled= */ true);
private static final MeterConfig DISABLED_CONFIG =
new AutoValue_MeterConfig(/* enabled= */ false);
/** Returns a disabled {@link MeterConfig}. */
public static MeterConfig disabled() {
return DISABLED_CONFIG;
}
/** Returns an enabled {@link MeterConfig}. */
public static MeterConfig enabled() {
return DEFAULT_CONFIG;
}
/**
* Returns the default {@link MeterConfig}, which is used when no configurator is set or when the
* meter configurator returns {@code null} for a {@link InstrumentationScopeInfo}.
*/
public static MeterConfig defaultConfig() {
return DEFAULT_CONFIG;
}
/**
* Create a {@link ScopeConfiguratorBuilder} for configuring {@link
* SdkMeterProviderUtil#setMeterConfigurator(SdkMeterProviderBuilder, ScopeConfigurator)}.
*/
public static ScopeConfiguratorBuilder<MeterConfig> configuratorBuilder() {
return ScopeConfigurator.builder();
}
MeterConfig() {}
/** Returns {@code true} if this meter is enabled. Defaults to {@code true}. */
public abstract boolean isEnabled();
}

View File

@ -5,6 +5,8 @@
package io.opentelemetry.sdk.metrics.internal;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
import io.opentelemetry.sdk.metrics.ViewBuilder;
@ -18,8 +20,11 @@ import java.lang.reflect.Method;
import java.util.function.Predicate;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
* A collection of methods that allow use of experimental features prior to availability in public
* APIs.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public final class SdkMeterProviderUtil {
@ -66,6 +71,39 @@ public final class SdkMeterProviderUtil {
}
}
/** Reflectively set the {@link ScopeConfigurator} to the {@link SdkMeterProviderBuilder}. */
public static void setMeterConfigurator(
SdkMeterProviderBuilder sdkMeterProviderBuilder,
ScopeConfigurator<MeterConfig> meterConfigurator) {
try {
Method method =
SdkMeterProviderBuilder.class.getDeclaredMethod(
"setMeterConfigurator", ScopeConfigurator.class);
method.setAccessible(true);
method.invoke(sdkMeterProviderBuilder, meterConfigurator);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(
"Error calling setMeterConfigurator on SdkMeterProviderBuilder", e);
}
}
/** Reflectively add a tracer configurator condition to the {@link SdkMeterProviderBuilder}. */
public static void addMeterConfiguratorCondition(
SdkMeterProviderBuilder sdkMeterProviderBuilder,
Predicate<InstrumentationScopeInfo> scopeMatcher,
MeterConfig meterConfig) {
try {
Method method =
SdkMeterProviderBuilder.class.getDeclaredMethod(
"addMeterConfiguratorCondition", Predicate.class, MeterConfig.class);
method.setAccessible(true);
method.invoke(sdkMeterProviderBuilder, scopeMatcher, meterConfig);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(
"Error calling addMeterConfiguratorCondition on SdkMeterProviderBuilder", e);
}
}
/**
* Reflectively add an {@link AttributesProcessor} to the {@link ViewBuilder} which appends
* key-values from baggage to all measurements.

View File

@ -21,9 +21,12 @@ import javax.annotation.concurrent.Immutable;
@AutoValue
@Immutable
public abstract class MeterProviderSharedState {
public static MeterProviderSharedState create(
Clock clock, Resource resource, ExemplarFilter exemplarFilter, long startEpochNanos) {
return new AutoValue_MeterProviderSharedState(clock, resource, startEpochNanos, exemplarFilter);
MeterProviderSharedState sharedState =
new AutoValue_MeterProviderSharedState(clock, resource, startEpochNanos, exemplarFilter);
return sharedState;
}
MeterProviderSharedState() {}

View File

@ -9,6 +9,7 @@ import static io.opentelemetry.sdk.metrics.internal.view.NoopAttributesProcessor
import static java.util.Objects.requireNonNull;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.GlobUtil;
import io.opentelemetry.sdk.metrics.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentSelector;
import io.opentelemetry.sdk.metrics.InstrumentType;
@ -27,10 +28,8 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.annotation.concurrent.Immutable;
/**
@ -170,7 +169,8 @@ public final class ViewRegistry {
return false;
}
if (selector.getInstrumentName() != null
&& !toGlobPatternPredicate(selector.getInstrumentName()).test(descriptor.getName())) {
&& !GlobUtil.toGlobPatternPredicate(selector.getInstrumentName())
.test(descriptor.getName())) {
return false;
}
return matchesMeter(selector, meterScope);
@ -190,69 +190,6 @@ public final class ViewRegistry {
|| selector.getMeterSchemaUrl().equals(meterScope.getSchemaUrl());
}
/**
* Return a predicate that returns {@code true} if a string matches the {@code globPattern}.
*
* <p>{@code globPattern} may contain the wildcard characters {@code *} and {@code ?} with the
* following matching criteria:
*
* <ul>
* <li>{@code *} matches 0 or more instances of any character
* <li>{@code ?} matches exactly one instance of any character
* </ul>
*/
// Visible for testing
static Predicate<String> toGlobPatternPredicate(String globPattern) {
// Match all
if (globPattern.equals("*")) {
return unused -> true;
}
// If globPattern contains '*' or '?', convert it to a regex and return corresponding predicate
for (int i = 0; i < globPattern.length(); i++) {
char c = globPattern.charAt(i);
if (c == '*' || c == '?') {
Pattern pattern = toRegexPattern(globPattern);
return string -> pattern.matcher(string).matches();
}
}
// Exact match, ignoring case
return globPattern::equalsIgnoreCase;
}
/**
* Transform the {@code globPattern} to a regex by converting {@code *} to {@code .*}, {@code ?}
* to {@code .}, and escaping other regex special characters.
*/
private static Pattern toRegexPattern(String globPattern) {
int tokenStart = -1;
StringBuilder patternBuilder = new StringBuilder();
for (int i = 0; i < globPattern.length(); i++) {
char c = globPattern.charAt(i);
if (c == '*' || c == '?') {
if (tokenStart != -1) {
patternBuilder.append(Pattern.quote(globPattern.substring(tokenStart, i)));
tokenStart = -1;
}
if (c == '*') {
patternBuilder.append(".*");
} else {
// c == '?'
patternBuilder.append(".");
}
} else {
if (tokenStart == -1) {
tokenStart = i;
}
}
}
if (tokenStart != -1) {
patternBuilder.append(Pattern.quote(globPattern.substring(tokenStart)));
}
return Pattern.compile(patternBuilder.toString());
}
private static RegisteredView applyAdviceToDefaultView(
RegisteredView instrumentDefaultView, Advice advice) {
return RegisteredView.create(

View File

@ -0,0 +1,148 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.metrics;
import static io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder.nameEquals;
import static io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder.nameMatchesGlob;
import static io.opentelemetry.sdk.metrics.internal.MeterConfig.defaultConfig;
import static io.opentelemetry.sdk.metrics.internal.MeterConfig.disabled;
import static io.opentelemetry.sdk.metrics.internal.MeterConfig.enabled;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.internal.MeterConfig;
import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
class MeterConfigTest {
@Test
void disableScopes() {
InMemoryMetricReader reader = InMemoryMetricReader.create();
SdkMeterProvider meterProvider =
SdkMeterProvider.builder()
// Disable meterB. Since meters are enabled by default, meterA and meterC are enabled.
.addMeterConfiguratorCondition(nameEquals("meterB"), disabled())
.registerMetricReader(reader)
.build();
Meter meterA = meterProvider.get("meterA");
Meter meterB = meterProvider.get("meterB");
Meter meterC = meterProvider.get("meterC");
meterA.counterBuilder("counterA").build().add(1);
meterA.counterBuilder("asyncCounterA").buildWithCallback(observable -> observable.record(1));
meterA.upDownCounterBuilder("upDownCounterA").build().add(1);
meterA
.upDownCounterBuilder("asyncUpDownCounterA")
.buildWithCallback(observable -> observable.record(1));
meterA.histogramBuilder("histogramA").build().record(1.0);
meterA.gaugeBuilder("gaugeA").buildWithCallback(observable -> observable.record(1.0));
meterB.counterBuilder("counterB").build().add(1);
meterB.counterBuilder("asyncCounterB").buildWithCallback(observable -> observable.record(1));
meterB.upDownCounterBuilder("upDownCounterB").build().add(1);
meterB
.upDownCounterBuilder("asyncUpDownCounterB")
.buildWithCallback(observable -> observable.record(1));
meterB.histogramBuilder("histogramB").build().record(1.0);
meterB.gaugeBuilder("gaugeB").buildWithCallback(observable -> observable.record(1.0));
meterC.counterBuilder("counterC").build().add(1);
meterC.counterBuilder("asyncCounterC").buildWithCallback(observable -> observable.record(1));
meterC.upDownCounterBuilder("upDownCounterC").build().add(1);
meterC
.upDownCounterBuilder("asyncUpDownCounterC")
.buildWithCallback(observable -> observable.record(1));
meterC.histogramBuilder("histogramC").build().record(1.0);
meterC.gaugeBuilder("gaugeC").buildWithCallback(observable -> observable.record(1.0));
// Only metrics from meterA and meterC should be seen
assertThat(reader.collectAllMetrics())
.satisfies(
metrics -> {
Map<InstrumentationScopeInfo, List<MetricData>> metricsByScope =
metrics.stream()
.collect(Collectors.groupingBy(MetricData::getInstrumentationScopeInfo));
assertThat(metricsByScope.get(InstrumentationScopeInfo.create("meterA"))).hasSize(6);
assertThat(metricsByScope.get(InstrumentationScopeInfo.create("meterB"))).isNull();
assertThat(metricsByScope.get(InstrumentationScopeInfo.create("meterC"))).hasSize(6);
});
}
@ParameterizedTest
@MethodSource("meterConfiguratorArgs")
void meterConfigurator(
ScopeConfigurator<MeterConfig> meterConfigurator,
InstrumentationScopeInfo scope,
MeterConfig expectedMeterConfig) {
MeterConfig meterConfig = meterConfigurator.apply(scope);
meterConfig = meterConfig == null ? defaultConfig() : meterConfig;
assertThat(meterConfig).isEqualTo(expectedMeterConfig);
}
private static final InstrumentationScopeInfo scopeCat = InstrumentationScopeInfo.create("cat");
private static final InstrumentationScopeInfo scopeDog = InstrumentationScopeInfo.create("dog");
private static final InstrumentationScopeInfo scopeDuck = InstrumentationScopeInfo.create("duck");
private static Stream<Arguments> meterConfiguratorArgs() {
ScopeConfigurator<MeterConfig> defaultConfigurator = MeterConfig.configuratorBuilder().build();
ScopeConfigurator<MeterConfig> disableCat =
MeterConfig.configuratorBuilder()
.addCondition(nameEquals("cat"), MeterConfig.disabled())
// Second matching rule for cat should be ignored
.addCondition(nameEquals("cat"), enabled())
.build();
ScopeConfigurator<MeterConfig> disableStartsWithD =
MeterConfig.configuratorBuilder()
.addCondition(nameMatchesGlob("d*"), MeterConfig.disabled())
.build();
ScopeConfigurator<MeterConfig> enableCat =
MeterConfig.configuratorBuilder()
.setDefault(MeterConfig.disabled())
.addCondition(nameEquals("cat"), enabled())
// Second matching rule for cat should be ignored
.addCondition(nameEquals("cat"), MeterConfig.disabled())
.build();
ScopeConfigurator<MeterConfig> enableStartsWithD =
MeterConfig.configuratorBuilder()
.setDefault(MeterConfig.disabled())
.addCondition(nameMatchesGlob("d*"), MeterConfig.enabled())
.build();
return Stream.of(
// default
Arguments.of(defaultConfigurator, scopeCat, defaultConfig()),
Arguments.of(defaultConfigurator, scopeDog, defaultConfig()),
Arguments.of(defaultConfigurator, scopeDuck, defaultConfig()),
// default enabled, disable cat
Arguments.of(disableCat, scopeCat, MeterConfig.disabled()),
Arguments.of(disableCat, scopeDog, enabled()),
Arguments.of(disableCat, scopeDuck, enabled()),
// default enabled, disable pattern
Arguments.of(disableStartsWithD, scopeCat, enabled()),
Arguments.of(disableStartsWithD, scopeDog, MeterConfig.disabled()),
Arguments.of(disableStartsWithD, scopeDuck, MeterConfig.disabled()),
// default disabled, enable cat
Arguments.of(enableCat, scopeCat, enabled()),
Arguments.of(enableCat, scopeDog, MeterConfig.disabled()),
Arguments.of(enableCat, scopeDuck, MeterConfig.disabled()),
// default disabled, enable pattern
Arguments.of(enableStartsWithD, scopeCat, MeterConfig.disabled()),
Arguments.of(enableStartsWithD, scopeDog, enabled()),
Arguments.of(enableStartsWithD, scopeDuck, enabled()));
}
}

View File

@ -7,7 +7,6 @@ package io.opentelemetry.sdk.metrics.internal.view;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.sdk.metrics.internal.view.ViewRegistry.DEFAULT_REGISTERED_VIEW;
import static io.opentelemetry.sdk.metrics.internal.view.ViewRegistry.toGlobPatternPredicate;
import static org.assertj.core.api.Assertions.assertThat;
import io.github.netmikey.logunit.api.LogCapturer;
@ -544,26 +543,4 @@ class ViewRegistryTest {
INSTRUMENTATION_SCOPE_INFO))
.isEqualTo(Collections.singletonList(DEFAULT_REGISTERED_VIEW));
}
@Test
void matchesName() {
assertThat(toGlobPatternPredicate("foo").test("foo")).isTrue();
assertThat(toGlobPatternPredicate("foo").test("Foo")).isTrue();
assertThat(toGlobPatternPredicate("foo").test("bar")).isFalse();
assertThat(toGlobPatternPredicate("fo?").test("foo")).isTrue();
assertThat(toGlobPatternPredicate("fo??").test("fooo")).isTrue();
assertThat(toGlobPatternPredicate("fo?").test("fob")).isTrue();
assertThat(toGlobPatternPredicate("fo?").test("fooo")).isFalse();
assertThat(toGlobPatternPredicate("*").test("foo")).isTrue();
assertThat(toGlobPatternPredicate("*").test("bar")).isTrue();
assertThat(toGlobPatternPredicate("*").test("baz")).isTrue();
assertThat(toGlobPatternPredicate("*").test("foo.bar.baz")).isTrue();
assertThat(toGlobPatternPredicate("fo*").test("fo")).isTrue();
assertThat(toGlobPatternPredicate("fo*").test("foo")).isTrue();
assertThat(toGlobPatternPredicate("fo*").test("fooo")).isTrue();
assertThat(toGlobPatternPredicate("fo*").test("foo.bar.baz")).isTrue();
assertThat(toGlobPatternPredicate("f()[]$^.{}|").test("f()[]$^.{}|")).isTrue();
assertThat(toGlobPatternPredicate("f()[]$^.{}|?").test("f()[]$^.{}|o")).isTrue();
assertThat(toGlobPatternPredicate("f()[]$^.{}|*").test("f()[]$^.{}|ooo")).isTrue();
}
}

View File

@ -9,21 +9,31 @@ import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.TracerProvider;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.trace.internal.TracerConfig;
/** {@link SdkTracer} is SDK implementation of {@link Tracer}. */
final class SdkTracer implements Tracer {
static final String FALLBACK_SPAN_NAME = "<unspecified span name>";
private static final Tracer NOOP_TRACER = TracerProvider.noop().get("noop");
private final TracerSharedState sharedState;
private final InstrumentationScopeInfo instrumentationScopeInfo;
private final TracerConfig tracerConfig;
SdkTracer(TracerSharedState sharedState, InstrumentationScopeInfo instrumentationScopeInfo) {
SdkTracer(
TracerSharedState sharedState,
InstrumentationScopeInfo instrumentationScopeInfo,
TracerConfig tracerConfig) {
this.sharedState = sharedState;
this.instrumentationScopeInfo = instrumentationScopeInfo;
this.tracerConfig = tracerConfig;
}
@Override
public SpanBuilder spanBuilder(String spanName) {
if (!tracerConfig.isEnabled()) {
return NOOP_TRACER.spanBuilder(spanName);
}
if (spanName == null || spanName.trim().isEmpty()) {
spanName = FALLBACK_SPAN_NAME;
}

View File

@ -10,8 +10,11 @@ import io.opentelemetry.api.trace.TracerBuilder;
import io.opentelemetry.api.trace.TracerProvider;
import io.opentelemetry.sdk.common.Clock;
import io.opentelemetry.sdk.common.CompletableResultCode;
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.TracerConfig;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import java.io.Closeable;
import java.util.List;
@ -27,6 +30,7 @@ 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;
/**
* Returns a new {@link SdkTracerProviderBuilder} for {@link SdkTracerProvider}.
@ -37,19 +41,31 @@ public final class SdkTracerProvider implements TracerProvider, Closeable {
return new SdkTracerProviderBuilder();
}
@SuppressWarnings("NonApiType")
SdkTracerProvider(
Clock clock,
IdGenerator idsGenerator,
Resource resource,
Supplier<SpanLimits> spanLimitsSupplier,
Sampler sampler,
List<SpanProcessor> spanProcessors) {
List<SpanProcessor> spanProcessors,
ScopeConfigurator<TracerConfig> tracerConfigurator) {
this.sharedState =
new TracerSharedState(
clock, idsGenerator, resource, spanLimitsSupplier, sampler, spanProcessors);
this.tracerSdkComponentRegistry =
new ComponentRegistry<>(
instrumentationScopeInfo -> new SdkTracer(sharedState, instrumentationScopeInfo));
instrumentationScopeInfo ->
new SdkTracer(
sharedState,
instrumentationScopeInfo,
getTracerConfig(instrumentationScopeInfo)));
this.tracerConfigurator = tracerConfigurator;
}
private TracerConfig getTracerConfig(InstrumentationScopeInfo instrumentationScopeInfo) {
TracerConfig tracerConfig = tracerConfigurator.apply(instrumentationScopeInfo);
return tracerConfig == null ? TracerConfig.defaultConfig() : tracerConfig;
}
@Override

View File

@ -8,11 +8,16 @@ package io.opentelemetry.sdk.trace;
import static java.util.Objects.requireNonNull;
import io.opentelemetry.sdk.common.Clock;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.internal.TracerConfig;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.function.Supplier;
/** Builder of {@link SdkTracerProvider}. */
@ -26,6 +31,8 @@ public final class SdkTracerProviderBuilder {
private Resource resource = Resource.getDefault();
private Supplier<SpanLimits> spanLimitsSupplier = SpanLimits::getDefault;
private Sampler sampler = DEFAULT_SAMPLER;
private ScopeConfiguratorBuilder<TracerConfig> tracerConfiguratorBuilder =
TracerConfig.configuratorBuilder();
/**
* Assign a {@link Clock}. {@link Clock} will be used each time a {@link
@ -147,6 +154,40 @@ public final class SdkTracerProviderBuilder {
return this;
}
/**
* Set the tracer configurator, which computes {@link TracerConfig} for each {@link
* InstrumentationScopeInfo}.
*
* <p>Overrides any matchers added via {@link #addTracerConfiguratorCondition(Predicate,
* TracerConfig)}.
*
* @see TracerConfig#configuratorBuilder()
*/
SdkTracerProviderBuilder setTracerConfigurator(
ScopeConfigurator<TracerConfig> tracerConfigurator) {
this.tracerConfiguratorBuilder = tracerConfigurator.toBuilder();
return this;
}
/**
* Adds a condition to the tracer configurator, which computes {@link TracerConfig} for each
* {@link InstrumentationScopeInfo}.
*
* <p>Applies after any previously added conditions.
*
* <p>If {@link #setTracerConfigurator(ScopeConfigurator)} was previously called, this condition
* will only be applied if the {@link ScopeConfigurator#apply(Object)} returns null for the
* matched {@link InstrumentationScopeInfo}(s).
*
* @see ScopeConfiguratorBuilder#nameEquals(String)
* @see ScopeConfiguratorBuilder#nameMatchesGlob(String)
*/
SdkTracerProviderBuilder addTracerConfiguratorCondition(
Predicate<InstrumentationScopeInfo> scopeMatcher, TracerConfig tracerConfig) {
this.tracerConfiguratorBuilder.addCondition(scopeMatcher, tracerConfig);
return this;
}
/**
* Create a new {@link SdkTracerProvider} instance with the configuration.
*
@ -154,7 +195,13 @@ public final class SdkTracerProviderBuilder {
*/
public SdkTracerProvider build() {
return new SdkTracerProvider(
clock, idsGenerator, resource, spanLimitsSupplier, sampler, spanProcessors);
clock,
idsGenerator,
resource,
spanLimitsSupplier,
sampler,
spanProcessors,
tracerConfiguratorBuilder.build());
}
SdkTracerProviderBuilder() {}

View File

@ -15,6 +15,7 @@ import javax.annotation.Nullable;
// Represents the shared state/config between all Tracers created by the same TracerProvider.
final class TracerSharedState {
private final Object lock = new Object();
private final Clock clock;
private final IdGenerator idGenerator;

View File

@ -0,0 +1,58 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.trace.internal;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Predicate;
/**
* A collection of methods that allow use of experimental features prior to availability in public
* APIs.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public final class SdkTracerProviderUtil {
private SdkTracerProviderUtil() {}
/** Reflectively set the {@link ScopeConfigurator} to the {@link SdkTracerProviderBuilder}. */
public static void setTracerConfigurator(
SdkTracerProviderBuilder sdkTracerProviderBuilder,
ScopeConfigurator<TracerConfig> tracerConfigurator) {
try {
Method method =
SdkTracerProviderBuilder.class.getDeclaredMethod(
"setTracerConfigurator", ScopeConfigurator.class);
method.setAccessible(true);
method.invoke(sdkTracerProviderBuilder, tracerConfigurator);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(
"Error calling setTracerConfigurator on SdkTracerProviderBuilder", e);
}
}
/** Reflectively add a tracer configurator condition to the {@link SdkTracerProviderBuilder}. */
public static void addTracerConfiguratorCondition(
SdkTracerProviderBuilder sdkTracerProviderBuilder,
Predicate<InstrumentationScopeInfo> scopeMatcher,
TracerConfig tracerConfig) {
try {
Method method =
SdkTracerProviderBuilder.class.getDeclaredMethod(
"addTracerConfiguratorCondition", Predicate.class, TracerConfig.class);
method.setAccessible(true);
method.invoke(sdkTracerProviderBuilder, scopeMatcher, tracerConfig);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(
"Error calling addTracerConfiguratorCondition on SdkTracerProviderBuilder", e);
}
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.trace.internal;
import com.google.auto.value.AutoValue;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ScopeConfigurator;
import io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder;
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
import java.util.function.Predicate;
import javax.annotation.concurrent.Immutable;
/**
* A collection of configuration options which define the behavior of a {@link Tracer}.
*
* @see SdkTracerProviderUtil#setTracerConfigurator(SdkTracerProviderBuilder, ScopeConfigurator)
* @see SdkTracerProviderUtil#addTracerConfiguratorCondition(SdkTracerProviderBuilder, Predicate,
* TracerConfig)
*/
@AutoValue
@Immutable
public abstract class TracerConfig {
private static final TracerConfig DEFAULT_CONFIG =
new AutoValue_TracerConfig(/* enabled= */ true);
private static final TracerConfig DISABLED_CONFIG =
new AutoValue_TracerConfig(/* enabled= */ false);
/** Returns a disabled {@link TracerConfig}. */
public static TracerConfig disabled() {
return DISABLED_CONFIG;
}
/** Returns an enabled {@link TracerConfig}. */
public static TracerConfig enabled() {
return DEFAULT_CONFIG;
}
/**
* Returns the default {@link TracerConfig}, which is used when no configurator is set or when the
* tracer configurator returns {@code null} for a {@link InstrumentationScopeInfo}.
*/
public static TracerConfig defaultConfig() {
return DEFAULT_CONFIG;
}
/**
* Create a {@link ScopeConfiguratorBuilder} for configuring {@link
* SdkTracerProviderUtil#setTracerConfigurator(SdkTracerProviderBuilder, ScopeConfigurator)}.
*/
public static ScopeConfiguratorBuilder<TracerConfig> configuratorBuilder() {
return ScopeConfigurator.builder();
}
TracerConfig() {}
/** Returns {@code true} if this tracer is enabled. Defaults to {@code true}. */
public abstract boolean isEnabled();
}

View File

@ -0,0 +1,156 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.trace;
import static io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder.nameEquals;
import static io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder.nameMatchesGlob;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static io.opentelemetry.sdk.trace.internal.TracerConfig.defaultConfig;
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.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;
import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import io.opentelemetry.sdk.trace.internal.TracerConfig;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
class TracerConfigTest {
@Test
void disableScopes() throws InterruptedException {
InMemorySpanExporter exporter = InMemorySpanExporter.create();
SdkTracerProvider tracerProvider =
SdkTracerProvider.builder()
// Disable tracerB. Since tracers are enabled by default, tracerA and tracerC are
// enabled.
.addTracerConfiguratorCondition(nameEquals("tracerB"), disabled())
.addSpanProcessor(SimpleSpanProcessor.create(exporter))
.build();
Tracer tracerA = tracerProvider.get("tracerA");
Tracer tracerB = tracerProvider.get("tracerB");
Tracer tracerC = tracerProvider.get("tracerC");
Span parent;
Span child;
Span grandchild;
parent = tracerA.spanBuilder("parent").startSpan();
try (Scope parentScope = parent.makeCurrent()) {
parent.setAttribute("a", "1");
child = tracerB.spanBuilder("child").startSpan();
// tracerB is disabled and should behave the same as noop tracer
assertThat(child.getSpanContext()).isEqualTo(parent.getSpanContext());
assertThat(child.isRecording()).isFalse();
try (Scope childScope = child.makeCurrent()) {
child.setAttribute("b", "1");
grandchild = tracerC.spanBuilder("grandchild").startSpan();
try (Scope grandchildScope = grandchild.makeCurrent()) {
grandchild.setAttribute("c", "1");
Thread.sleep(100);
} finally {
grandchild.end();
}
} finally {
child.end();
}
} finally {
parent.end();
}
// Only contain tracerA:parent and tracerC:child should be seen
// tracerC:grandchild should list tracerA:parent as its parent
assertThat(exporter.getFinishedSpanItems())
.satisfiesExactlyInAnyOrder(
spanData ->
assertThat(spanData)
.hasInstrumentationScopeInfo(InstrumentationScopeInfo.create("tracerA"))
.hasName("parent")
.hasSpanId(parent.getSpanContext().getSpanId())
.hasParentSpanId(SpanId.getInvalid())
.hasAttributes(Attributes.builder().put("a", "1").build()),
spanData ->
assertThat(spanData)
.hasInstrumentationScopeInfo(InstrumentationScopeInfo.create("tracerC"))
.hasName("grandchild")
.hasSpanId(grandchild.getSpanContext().getSpanId())
.hasParentSpanId(parent.getSpanContext().getSpanId())
.hasAttributes(Attributes.builder().put("c", "1").build()));
}
@ParameterizedTest
@MethodSource("tracerConfiguratorArgs")
void tracerConfigurator(
ScopeConfigurator<TracerConfig> tracerConfigurator,
InstrumentationScopeInfo scope,
TracerConfig expectedTracerConfig) {
TracerConfig tracerConfig = tracerConfigurator.apply(scope);
tracerConfig = tracerConfig == null ? defaultConfig() : tracerConfig;
assertThat(tracerConfig).isEqualTo(expectedTracerConfig);
}
private static final InstrumentationScopeInfo scopeCat = InstrumentationScopeInfo.create("cat");
private static final InstrumentationScopeInfo scopeDog = InstrumentationScopeInfo.create("dog");
private static final InstrumentationScopeInfo scopeDuck = InstrumentationScopeInfo.create("duck");
private static Stream<Arguments> tracerConfiguratorArgs() {
ScopeConfigurator<TracerConfig> defaultConfigurator =
TracerConfig.configuratorBuilder().build();
ScopeConfigurator<TracerConfig> disableCat =
TracerConfig.configuratorBuilder()
.addCondition(nameEquals("cat"), disabled())
// Second matching rule for cat should be ignored
.addCondition(nameEquals("cat"), enabled())
.build();
ScopeConfigurator<TracerConfig> disableStartsWithD =
TracerConfig.configuratorBuilder().addCondition(nameMatchesGlob("d*"), disabled()).build();
ScopeConfigurator<TracerConfig> enableCat =
TracerConfig.configuratorBuilder()
.setDefault(disabled())
.addCondition(nameEquals("cat"), enabled())
// Second matching rule for cat should be ignored
.addCondition(nameEquals("cat"), disabled())
.build();
ScopeConfigurator<TracerConfig> enableStartsWithD =
TracerConfig.configuratorBuilder()
.setDefault(disabled())
.addCondition(nameMatchesGlob("d*"), TracerConfig.enabled())
.build();
return Stream.of(
// default
Arguments.of(defaultConfigurator, scopeCat, defaultConfig()),
Arguments.of(defaultConfigurator, scopeDog, defaultConfig()),
Arguments.of(defaultConfigurator, scopeDuck, defaultConfig()),
// default enabled, disable cat
Arguments.of(disableCat, scopeCat, disabled()),
Arguments.of(disableCat, scopeDog, enabled()),
Arguments.of(disableCat, scopeDuck, enabled()),
// default enabled, disable pattern
Arguments.of(disableStartsWithD, scopeCat, enabled()),
Arguments.of(disableStartsWithD, scopeDog, disabled()),
Arguments.of(disableStartsWithD, scopeDuck, disabled()),
// default disabled, enable cat
Arguments.of(enableCat, scopeCat, enabled()),
Arguments.of(enableCat, scopeDog, disabled()),
Arguments.of(enableCat, scopeDuck, disabled()),
// default disabled, enable pattern
Arguments.of(enableStartsWithD, scopeCat, disabled()),
Arguments.of(enableStartsWithD, scopeDog, enabled()),
Arguments.of(enableStartsWithD, scopeDuck, enabled()));
}
}