diff --git a/sdk-extensions/declarative-config/build.gradle.kts b/sdk-extensions/declarative-config/build.gradle.kts index 2193f4f2176..2765bdd3b2c 100644 --- a/sdk-extensions/declarative-config/build.gradle.kts +++ b/sdk-extensions/declarative-config/build.gradle.kts @@ -107,6 +107,12 @@ jsonSchema2Pojo { // Append Model as suffix to the generated classes. classNameSuffix = "Model" + + // Initialize collection fields to null rather than empty collections so that absent YAML + // properties deserialize as null (not present) rather than [] (explicitly empty). This lets + // factories distinguish between "user omitted the field" and "user provided an empty list", + // which is important for validations like IncludeExcludeFactory. + initializeCollections = false } val generateJsonSchema2Pojo = tasks.getByName("generateJsonSchema2Pojo") diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/ComposableRuleBasedSamplerFactory.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/ComposableRuleBasedSamplerFactory.java index 963e0d99e64..1cedf988f5f 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/ComposableRuleBasedSamplerFactory.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/ComposableRuleBasedSamplerFactory.java @@ -54,12 +54,10 @@ public ComposableSampler create( rule -> { AttributeMatcher valueMatcher = attributeValuesMatcher(rule.getAttributeValues()); AttributeMatcher patternMatcher = attributePatternsMatcher(rule.getAttributePatterns()); - // TODO: should be null when omitted but is empty Set matchingParents = rule.getParent() != null && !rule.getParent().isEmpty() ? new HashSet<>(rule.getParent()) : null; - // TODO: should be null when omitted but is empty Set matchingSpanKinds = rule.getSpanKinds() != null && !rule.getSpanKinds().isEmpty() ? rule.getSpanKinds().stream() diff --git a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/ComposableRuleBasedSamplerFactoryTest.java b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/ComposableRuleBasedSamplerFactoryTest.java index c5c0f28cd39..aae18522053 100644 --- a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/ComposableRuleBasedSamplerFactoryTest.java +++ b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/ComposableRuleBasedSamplerFactoryTest.java @@ -135,6 +135,33 @@ private static Stream createTestCases() { Arguments.of( new ExperimentalComposableRuleBasedSamplerModel(), ComposableSampler.ruleBasedBuilder().build()), + // attributePatterns with only included (no excluded) - analogous to + // https://github.com/open-telemetry/opentelemetry-java/issues/8337 + Arguments.of( + new ExperimentalComposableRuleBasedSamplerModel() + .withRules( + Collections.singletonList( + new ExperimentalComposableRuleBasedSamplerRuleModel() + .withAttributePatterns( + new ExperimentalComposableRuleBasedSamplerRuleAttributePatternsModel() + .withKey("http.path") + .withIncluded(Collections.singletonList("/internal/*"))) + .withSampler( + new ExperimentalComposableSamplerModel() + .withAlwaysOn( + new ExperimentalComposableAlwaysOnSamplerModel())))), + ComposableSampler.ruleBasedBuilder() + .add( + new DeclarativeConfigSamplingPredicate( + null, + new AttributeMatcher( + "http.path", + IncludeExcludePredicate.createPatternMatching( + Collections.singletonList("/internal/*"), null)), + null, + null), + ComposableSampler.alwaysOn()) + .build()), // Recreate example Arguments.of( new ExperimentalComposableRuleBasedSamplerModel() diff --git a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/ViewFactoryTest.java b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/ViewFactoryTest.java index a9897d5282c..1fb466ea619 100644 --- a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/ViewFactoryTest.java +++ b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/ViewFactoryTest.java @@ -18,56 +18,65 @@ import io.opentelemetry.sdk.metrics.View; import java.util.Arrays; import java.util.Collections; -import org.junit.jupiter.api.Test; +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 ViewFactoryTest { - @Test - void create_Defaults() { - View expectedView = View.builder().build(); - - View view = - ViewFactory.getInstance() - .create( - new ViewStreamModel().withAttributeKeys(null), - mock(DeclarativeConfigContext.class)); - + @ParameterizedTest + @MethodSource("createArguments") + void create(ViewStreamModel model, View expectedView) { + View view = ViewFactory.getInstance().create(model, mock(DeclarativeConfigContext.class)); assertThat(view.toString()).isEqualTo(expectedView.toString()); } - @Test - void create() { - View expectedView = - View.builder() - .setName("name") - .setDescription("description") - .setAttributeFilter( - IncludeExcludePredicate.createPatternMatching( - Arrays.asList("foo", "bar"), Collections.singletonList("baz"))) - .setAggregation( - Aggregation.explicitBucketHistogram( - ExplicitBucketHistogramOptions.builder() - .setBucketBoundaries(Arrays.asList(1.0, 2.0)) - .build())) - .build(); - - View view = - ViewFactory.getInstance() - .create( - new ViewStreamModel() - .withName("name") - .withDescription("description") - .withAttributeKeys( - new IncludeExcludeModel() - .withIncluded(Arrays.asList("foo", "bar")) - .withExcluded(Collections.singletonList("baz"))) - .withAggregation( - new AggregationModel() - .withExplicitBucketHistogram( - new ExplicitBucketHistogramAggregationModel() - .withBoundaries(Arrays.asList(1.0, 2.0)))), - mock(DeclarativeConfigContext.class)); - - assertThat(view.toString()).isEqualTo(expectedView.toString()); + private static Stream createArguments() { + return Stream.of( + // defaults + Arguments.of(new ViewStreamModel().withAttributeKeys(null), View.builder().build()), + // attribute_keys with only included (no excluded) - reproduces + // https://github.com/open-telemetry/opentelemetry-java/issues/8337 + Arguments.of( + new ViewStreamModel() + .withAttributeKeys( + new IncludeExcludeModel() + .withIncluded( + Arrays.asList( + "url.full", "http.request.method", "http.response.status_code"))), + View.builder() + .setAttributeFilter( + IncludeExcludePredicate.createPatternMatching( + Arrays.asList( + "url.full", "http.request.method", "http.response.status_code"), + null)) + .build()), + // full configuration + Arguments.of( + new ViewStreamModel() + .withName("name") + .withDescription("description") + .withAttributeKeys( + new IncludeExcludeModel() + .withIncluded(Arrays.asList("foo", "bar")) + .withExcluded(Collections.singletonList("baz"))) + .withAggregation( + new AggregationModel() + .withExplicitBucketHistogram( + new ExplicitBucketHistogramAggregationModel() + .withBoundaries(Arrays.asList(1.0, 2.0)))), + View.builder() + .setName("name") + .setDescription("description") + .setAttributeFilter( + IncludeExcludePredicate.createPatternMatching( + Arrays.asList("foo", "bar"), Collections.singletonList("baz"))) + .setAggregation( + Aggregation.explicitBucketHistogram( + ExplicitBucketHistogramOptions.builder() + .setBucketBoundaries(Arrays.asList(1.0, 2.0)) + .build())) + .build())); } }