Scope config (#6375)
This commit is contained in:
parent
1623a80d4c
commit
c33febbea6
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue