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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package io.opentelemetry.exporter.prometheus.internal;

import io.opentelemetry.api.incubator.config.DeclarativeConfigException;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.exporter.prometheus.PrometheusHttpServer;
import io.opentelemetry.exporter.prometheus.PrometheusHttpServerBuilder;
Expand Down Expand Up @@ -59,10 +60,14 @@ public MetricReader create(DeclarativeConfigProperties config) {
if (withResourceConstantLabels != null) {
List<String> included = withResourceConstantLabels.getScalarList("included", String.class);
List<String> excluded = withResourceConstantLabels.getScalarList("excluded", String.class);
if (included != null || excluded != null) {
prometheusBuilder.setAllowedResourceAttributesFilter(
IncludeExcludePredicate.createPatternMatching(included, excluded));
if (included != null && included.isEmpty()) {
throw new DeclarativeConfigException("included must not be empty");
}
if (excluded != null && excluded.isEmpty()) {
throw new DeclarativeConfigException("excluded must not be empty");
}
prometheusBuilder.setAllowedResourceAttributesFilter(
IncludeExcludePredicate.createPatternMatching(included, excluded));
}

return prometheusBuilder.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import static java.util.stream.Collectors.toSet;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.incubator.config.DeclarativeConfigException;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.SpanKind;
Expand Down Expand Up @@ -86,9 +87,13 @@ private static AttributeMatcher attributeValuesMatcher(
if (attributeValuesModel == null) {
return null;
}
List<String> values = attributeValuesModel.getValues();
if (values == null || values.isEmpty()) {
throw new DeclarativeConfigException(".values is required and must be non-empty");
}
return new AttributeMatcher(
requireNonNull(attributeValuesModel.getKey(), "attribute_values key"),
IncludeExcludePredicate.createExactMatching(attributeValuesModel.getValues(), null));
IncludeExcludePredicate.createExactMatching(values, null));
}

@Nullable
Expand All @@ -98,10 +103,17 @@ private static AttributeMatcher attributePatternsMatcher(
if (attributePatternsModel == null) {
return null;
}
List<String> included = attributePatternsModel.getIncluded();
if (included != null && included.isEmpty()) {
throw new DeclarativeConfigException("included must not be empty");
}
List<String> excluded = attributePatternsModel.getExcluded();
if (excluded != null && excluded.isEmpty()) {
throw new DeclarativeConfigException("excluded must not be empty");
}
return new AttributeMatcher(
requireNonNull(attributePatternsModel.getKey(), "attribute_patterns key"),
IncludeExcludePredicate.createPatternMatching(
attributePatternsModel.getIncluded(), attributePatternsModel.getExcluded()));
IncludeExcludePredicate.createPatternMatching(included, excluded));
}

// Visible for testing
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.extension.incubator.fileconfig;

import io.opentelemetry.api.incubator.config.DeclarativeConfigException;
import io.opentelemetry.sdk.common.internal.IncludeExcludePredicate;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.IncludeExcludeModel;
import java.util.List;
import java.util.function.Predicate;

final class IncludeExcludeFactory implements Factory<IncludeExcludeModel, Predicate<String>> {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since IncludeExcludeFactory is a recurring concept in the config schema, create a dedicated factory which consistently enforces the constraints

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(minor/style) instead of having a dedicated factory class maybe we could have used a static factory method on IncludeExcludePredicate to keep things simpler as the factory is purely static here and has no state.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This factory pattern is employed widely in declarative config with the general pattern being one factory per model, with each factory responsible for translating an instance of the model to an instance of a corresponding SDK component. They're all stateless, but having singleton instances is still useful because it forces consistency in the pattern.

Can't move to IncludeExcludePredicate because the model class isn't there. Could have a static method in IncludeExcludePredicate which accepts @Nullable List<String> included and @Nullable List<String> excluded parameters, but DeclarativeConfigException isn't available so would need to wrap the call in a try/catch and map the IllegalArgumentException to DeclarativeConfigException.


private static final IncludeExcludeFactory INSTANCE = new IncludeExcludeFactory();

private IncludeExcludeFactory() {}

static IncludeExcludeFactory getInstance() {
return INSTANCE;
}

@Override
public Predicate<String> create(IncludeExcludeModel model, DeclarativeConfigContext context) {
List<String> included = model.getIncluded();
if (included != null && included.isEmpty()) {
throw new DeclarativeConfigException("included must not be empty");
}
List<String> excluded = model.getExcluded();
if (excluded != null && excluded.isEmpty()) {
throw new DeclarativeConfigException("excluded must not be empty");
}

return IncludeExcludePredicate.createPatternMatching(included, excluded);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import io.opentelemetry.sdk.common.internal.IncludeExcludePredicate;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AttributeNameValueModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalResourceDetectionModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalResourceDetectorModel;
Expand All @@ -21,7 +20,6 @@
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import javax.annotation.Nullable;

final class ResourceFactory implements Factory<ResourceModel, Resource> {

Expand Down Expand Up @@ -49,8 +47,11 @@ public Resource create(ResourceModel model, DeclarativeConfigContext context) {
}
}

IncludeExcludeModel attributesIncludeExcludeModel = detectionModel.getAttributes();
Predicate<String> detectorAttributeFilter =
detectorAttributeFilter(detectionModel.getAttributes());
attributesIncludeExcludeModel == null
? ResourceFactory::matchAll
: IncludeExcludeFactory.getInstance().create(attributesIncludeExcludeModel, context);
Attributes filteredDetectedAttributes =
detectedResourceBuilder.build().getAttributes().toBuilder()
.removeIf(attributeKey -> !detectorAttributeFilter.test(attributeKey.getKey()))
Expand Down Expand Up @@ -84,13 +85,4 @@ public Resource create(ResourceModel model, DeclarativeConfigContext context) {
private static boolean matchAll(String attributeKey) {
return true;
}

private static Predicate<String> detectorAttributeFilter(
@Nullable IncludeExcludeModel includedExcludeModel) {
if (includedExcludeModel == null) {
return ResourceFactory::matchAll;
}
return IncludeExcludePredicate.createPatternMatching(
includedExcludeModel.getIncluded(), includedExcludeModel.getExcluded());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@

package io.opentelemetry.sdk.extension.incubator.fileconfig;

import io.opentelemetry.sdk.common.internal.IncludeExcludePredicate;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.IncludeExcludeModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ViewStreamModel;
import io.opentelemetry.sdk.metrics.View;
import io.opentelemetry.sdk.metrics.ViewBuilder;
import java.util.List;

final class ViewFactory implements Factory<ViewStreamModel, View> {

Expand All @@ -33,9 +31,8 @@ public View create(ViewStreamModel model, DeclarativeConfigContext context) {
}
IncludeExcludeModel attributeKeys = model.getAttributeKeys();
if (attributeKeys != null) {
List<String> included = attributeKeys.getIncluded();
List<String> excluded = attributeKeys.getExcluded();
builder.setAttributeFilter(IncludeExcludePredicate.createExactMatching(included, excluded));
builder.setAttributeFilter(
IncludeExcludeFactory.getInstance().create(attributeKeys, context));
}
if (model.getAggregation() != null) {
builder.setAggregation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
import static io.opentelemetry.sdk.extension.incubator.fileconfig.ComposableRuleBasedSamplerFactory.DeclarativeConfigSamplingPredicate.toSpanParent;
import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.incubator.config.DeclarativeConfigException;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.TraceFlags;
Expand Down Expand Up @@ -58,6 +60,76 @@ void create(ExperimentalComposableRuleBasedSamplerModel model, ComposableSampler
assertThat(composableSampler.toString()).isEqualTo(expectedResult.toString());
}

@ParameterizedTest
@MethodSource("createInvalidTestCases")
void createInvalid(ExperimentalComposableRuleBasedSamplerModel model, String expectedMessage) {
assertThatThrownBy(() -> ComposableRuleBasedSamplerFactory.getInstance().create(model, context))
.isInstanceOf(DeclarativeConfigException.class)
.hasMessage(expectedMessage);
}

private static Stream<Arguments> createInvalidTestCases() {
return Stream.of(
Arguments.of(
new ExperimentalComposableRuleBasedSamplerModel()
.withRules(
Collections.singletonList(
new ExperimentalComposableRuleBasedSamplerRuleModel()
.withAttributePatterns(
new ExperimentalComposableRuleBasedSamplerRuleAttributePatternsModel()
.withKey("http.path")
.withIncluded(Collections.emptyList())
.withExcluded(null))
.withSampler(
new ExperimentalComposableSamplerModel()
.withAlwaysOn(
new ExperimentalComposableAlwaysOnSamplerModel())))),
"included must not be empty"),
Arguments.of(
new ExperimentalComposableRuleBasedSamplerModel()
.withRules(
Collections.singletonList(
new ExperimentalComposableRuleBasedSamplerRuleModel()
.withAttributePatterns(
new ExperimentalComposableRuleBasedSamplerRuleAttributePatternsModel()
.withKey("http.path")
.withIncluded(null)
.withExcluded(Collections.emptyList()))
.withSampler(
new ExperimentalComposableSamplerModel()
.withAlwaysOn(
new ExperimentalComposableAlwaysOnSamplerModel())))),
"excluded must not be empty"),
Arguments.of(
new ExperimentalComposableRuleBasedSamplerModel()
.withRules(
Collections.singletonList(
new ExperimentalComposableRuleBasedSamplerRuleModel()
.withAttributeValues(
new ExperimentalComposableRuleBasedSamplerRuleAttributeValuesModel()
.withKey("http.route")
.withValues(null))
.withSampler(
new ExperimentalComposableSamplerModel()
.withAlwaysOn(
new ExperimentalComposableAlwaysOnSamplerModel())))),
".values is required and must be non-empty"),
Arguments.of(
new ExperimentalComposableRuleBasedSamplerModel()
.withRules(
Collections.singletonList(
new ExperimentalComposableRuleBasedSamplerRuleModel()
.withAttributeValues(
new ExperimentalComposableRuleBasedSamplerRuleAttributeValuesModel()
.withKey("http.route")
.withValues(Collections.emptyList()))
.withSampler(
new ExperimentalComposableSamplerModel()
.withAlwaysOn(
new ExperimentalComposableAlwaysOnSamplerModel())))),
".values is required and must be non-empty"));
}

private static Stream<Arguments> createTestCases() {
return Stream.of(
Arguments.of(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.extension.incubator.fileconfig;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import io.opentelemetry.api.incubator.config.DeclarativeConfigException;
import io.opentelemetry.common.ComponentLoader;
import io.opentelemetry.sdk.common.internal.IncludeExcludePredicate;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.IncludeExcludeModel;
import java.util.Arrays;
import java.util.Collections;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

class IncludeExcludeFactoryTest {

private final DeclarativeConfigContext context =
new DeclarativeConfigContext(ComponentLoader.forClassLoader(getClass().getClassLoader()));

@ParameterizedTest
@MethodSource("createArguments")
void create(IncludeExcludeModel model, Predicate<String> expectedPredicate) {
Predicate<String> predicate = IncludeExcludeFactory.getInstance().create(model, context);
assertThat(predicate.toString()).isEqualTo(expectedPredicate.toString());
}

private static Stream<Arguments> createArguments() {
return Stream.of(
// included null, excluded null
Arguments.of(
new IncludeExcludeModel().withIncluded(null).withExcluded(null),
IncludeExcludePredicate.createPatternMatching(null, null)),
// included present, excluded null
Arguments.of(
new IncludeExcludeModel().withIncluded(Arrays.asList("foo", "bar")).withExcluded(null),
IncludeExcludePredicate.createPatternMatching(Arrays.asList("foo", "bar"), null)),
// included null, excluded present
Arguments.of(
new IncludeExcludeModel()
.withIncluded(null)
.withExcluded(Collections.singletonList("baz")),
IncludeExcludePredicate.createPatternMatching(null, Collections.singletonList("baz"))),
// both included and excluded present
Arguments.of(
new IncludeExcludeModel()
.withIncluded(Arrays.asList("foo", "bar"))
.withExcluded(Collections.singletonList("baz")),
IncludeExcludePredicate.createPatternMatching(
Arrays.asList("foo", "bar"), Collections.singletonList("baz"))));
}

@ParameterizedTest
@MethodSource("createInvalidArguments")
void createInvalid(IncludeExcludeModel model, String expectedMessage) {
assertThatThrownBy(() -> IncludeExcludeFactory.getInstance().create(model, context))
.isInstanceOf(DeclarativeConfigException.class)
.hasMessage(expectedMessage);
}

private static Stream<Arguments> createInvalidArguments() {
return Stream.of(
Arguments.of(
new IncludeExcludeModel().withIncluded(Collections.emptyList()).withExcluded(null),
"included must not be empty"),
Arguments.of(
new IncludeExcludeModel().withIncluded(null).withExcluded(Collections.emptyList()),
"excluded must not be empty"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,6 @@ private static Stream<Arguments> createWithDetectorsArgs() {
Collections.singletonList("*o*"),
Collections.singletonList("order"),
Resource.getDefault().toBuilder().put("color", "red").build()),
// empty or missing include should be treated as include all
Arguments.of(
Collections.emptyList(),
Collections.singletonList("order"),
Resource.getDefault().toBuilder().put("color", "red").put("shape", "square").build()),
Arguments.of(
null,
Collections.singletonList("order"),
Expand Down Expand Up @@ -191,6 +186,24 @@ private static Stream<Arguments> createInvalidDetectorsArgs() {
new ExperimentalResourceDetectionModel()
.withDetectors(
Collections.singletonList(new ExperimentalResourceDetectorModel()))),
"resource detector must have exactly one entry but has 0"));
"resource detector must have exactly one entry but has 0"),
Arguments.of(
new ResourceModel()
.withDetectionDevelopment(
new ExperimentalResourceDetectionModel()
.withAttributes(
new IncludeExcludeModel()
.withIncluded(Collections.emptyList())
.withExcluded(null))),
"included must not be empty"),
Arguments.of(
new ResourceModel()
.withDetectionDevelopment(
new ExperimentalResourceDetectionModel()
.withAttributes(
new IncludeExcludeModel()
.withIncluded(null)
.withExcluded(Collections.emptyList()))),
"excluded must not be empty"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ void create() {
.setName("name")
.setDescription("description")
.setAttributeFilter(
IncludeExcludePredicate.createExactMatching(
IncludeExcludePredicate.createPatternMatching(
Arrays.asList("foo", "bar"), Collections.singletonList("baz")))
.setAggregation(
Aggregation.explicitBucketHistogram(
Expand Down
Loading