From 188171cc5ad3c4e49a5b58b228c752be62c9f4f7 Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Wed, 23 Aug 2023 15:26:31 -0500 Subject: [PATCH 1/2] Add Resource configuration factory --- sdk-extensions/incubator/build.gradle.kts | 1 + .../fileconfig/AttributesFactory.java | 157 ++++++++++++++++++ .../OpenTelemetryConfigurationFactory.java | 8 + .../incubator/fileconfig/ResourceFactory.java | 43 +++++ .../fileconfig/AttributesFactoryTest.java | 99 +++++++++++ ...OpenTelemetryConfigurationFactoryTest.java | 16 ++ .../fileconfig/ResourceFactoryTest.java | 47 ++++++ 7 files changed, 371 insertions(+) create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactory.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ResourceFactory.java create mode 100644 sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactoryTest.java create mode 100644 sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ResourceFactoryTest.java diff --git a/sdk-extensions/incubator/build.gradle.kts b/sdk-extensions/incubator/build.gradle.kts index a7977b5e82a..bfd2a84c643 100644 --- a/sdk-extensions/incubator/build.gradle.kts +++ b/sdk-extensions/incubator/build.gradle.kts @@ -28,6 +28,7 @@ dependencies { implementation("com.fasterxml.jackson.core:jackson-databind") implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml") implementation(project(":sdk-extensions:autoconfigure")) + implementation(project(":semconv")) testImplementation(project(":sdk:testing")) testImplementation(project(":sdk-extensions:autoconfigure")) diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactory.java new file mode 100644 index 00000000000..2ae94d45846 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactory.java @@ -0,0 +1,157 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Attributes; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +import java.io.Closeable; +import java.util.List; +import javax.annotation.Nullable; + +final class AttributesFactory + implements Factory { + + private static final AttributesFactory INSTANCE = new AttributesFactory(); + + private AttributesFactory() {} + + static AttributesFactory getInstance() { + return INSTANCE; + } + + @Override + public io.opentelemetry.api.common.Attributes create( + @Nullable Attributes model, SpiHelper spiHelper, List closeables) { + if (model == null) { + return io.opentelemetry.api.common.Attributes.empty(); + } + + AttributesBuilder builder = io.opentelemetry.api.common.Attributes.builder(); + + String serviceName = model.getServiceName(); + if (serviceName != null) { + builder.put(ResourceAttributes.SERVICE_NAME, serviceName); + } + + model + .getAdditionalProperties() + .forEach( + (key, value) -> { + if (value == null) { + throw new ConfigurationException( + "Error processing attribute with key \"" + key + "\": unexpected null value"); + } + Class valueClass = value.getClass(); + if (value instanceof String) { + builder.put(key, (String) value); + return; + } + if (value instanceof Integer) { + builder.put(key, (int) value); + return; + } + if (value instanceof Long) { + builder.put(key, (long) value); + return; + } + if (value instanceof Double) { + builder.put(key, (double) value); + return; + } + if (value instanceof Float) { + builder.put(key, (float) value); + return; + } + if (value instanceof Boolean) { + builder.put(key, (boolean) value); + return; + } + if (List.class.isAssignableFrom(valueClass)) { + List values = (List) value; + if (values.isEmpty()) { + return; + } + Object first = values.get(0); + if (first instanceof String) { + checkAllEntriesOfType(key, values, String.class); + builder.put( + AttributeKey.stringArrayKey(key), + values.stream().map(obj -> (String) obj).toArray(String[]::new)); + return; + } + if (first instanceof Long) { + checkAllEntriesOfType(key, values, Long.class); + builder.put( + AttributeKey.longArrayKey(key), + values.stream().map(obj -> (long) obj).toArray(Long[]::new)); + return; + } + if (first instanceof Integer) { + checkAllEntriesOfType(key, values, Integer.class); + builder.put( + AttributeKey.longArrayKey(key), + values.stream().map(obj -> Long.valueOf((int) obj)).toArray(Long[]::new)); + return; + } + if (first instanceof Double) { + checkAllEntriesOfType(key, values, Double.class); + builder.put( + AttributeKey.doubleArrayKey(key), + values.stream().map(obj -> (double) obj).toArray(Double[]::new)); + return; + } + if (first instanceof Float) { + checkAllEntriesOfType(key, values, Float.class); + builder.put( + AttributeKey.doubleArrayKey(key), + values.stream() + .map(obj -> Double.valueOf((float) obj)) + .toArray(Double[]::new)); + return; + } + if (first instanceof Boolean) { + checkAllEntriesOfType(key, values, Boolean.class); + builder.put( + AttributeKey.booleanArrayKey(key), + values.stream().map(obj -> (Boolean) obj).toArray(Boolean[]::new)); + return; + } + } + throw new ConfigurationException( + "Error processing attribute with key \"" + + key + + "\": unrecognized value type " + + value.getClass().getName()); + }); + + return builder.build(); + } + + private static void checkAllEntriesOfType(String key, List values, Class expectedType) { + values.forEach( + value -> { + if (value == null) { + throw new ConfigurationException( + "Error processing attribute with key \"" + + key + + "\": unexpected null element in value"); + } + if (!expectedType.isAssignableFrom(value.getClass())) { + throw new ConfigurationException( + "Error processing attribute with key \"" + + key + + "\": expected value entries to be of type " + + expectedType + + " but found entry with type " + + value.getClass()); + } + }); + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java index 41204fd213d..2e15cac48e6 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java @@ -10,6 +10,7 @@ import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfiguration; +import io.opentelemetry.sdk.resources.Resource; import java.io.Closeable; import java.util.List; import javax.annotation.Nullable; @@ -39,12 +40,18 @@ public OpenTelemetrySdk create( OpenTelemetrySdkBuilder builder = OpenTelemetrySdk.builder(); + Resource resource = Resource.getDefault(); + if (model.getResource() != null) { + resource = ResourceFactory.getInstance().create(model.getResource(), spiHelper, closeables); + } + if (model.getLoggerProvider() != null) { builder.setLoggerProvider( FileConfigUtil.addAndReturn( closeables, LoggerProviderFactory.getInstance() .create(model.getLoggerProvider(), spiHelper, closeables) + .setResource(resource) .build())); } @@ -54,6 +61,7 @@ public OpenTelemetrySdk create( closeables, TracerProviderFactory.getInstance() .create(model.getTracerProvider(), spiHelper, closeables) + .setResource(resource) .build())); } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ResourceFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ResourceFactory.java new file mode 100644 index 00000000000..47ec3b6606a --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ResourceFactory.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Attributes; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Resource; +import io.opentelemetry.sdk.resources.ResourceBuilder; +import java.io.Closeable; +import java.util.List; +import javax.annotation.Nullable; + +final class ResourceFactory implements Factory { + + private static final ResourceFactory INSTANCE = new ResourceFactory(); + + private ResourceFactory() {} + + static ResourceFactory getInstance() { + return INSTANCE; + } + + @Override + public io.opentelemetry.sdk.resources.Resource create( + @Nullable Resource model, SpiHelper spiHelper, List closeables) { + if (model == null) { + return io.opentelemetry.sdk.resources.Resource.getDefault(); + } + + ResourceBuilder builder = io.opentelemetry.sdk.resources.Resource.getDefault().toBuilder(); + + Attributes attributesModel = model.getAttributes(); + if (attributesModel != null) { + builder.putAll( + AttributesFactory.getInstance().create(attributesModel, spiHelper, closeables)); + } + + return builder.build(); + } +} diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactoryTest.java new file mode 100644 index 00000000000..dca78ba641c --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactoryTest.java @@ -0,0 +1,99 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Attributes; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +import java.util.Arrays; +import java.util.Collections; +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 AttributesFactoryTest { + + @Test + void create_Null() { + assertThat( + AttributesFactory.getInstance() + .create(null, mock(SpiHelper.class), Collections.emptyList())) + .isEqualTo(io.opentelemetry.api.common.Attributes.empty()); + } + + @ParameterizedTest + @MethodSource("invalidAttributes") + void create_InvalidAttributes(Attributes model, String expectedMessage) { + assertThatThrownBy( + () -> + AttributesFactory.getInstance() + .create(model, mock(SpiHelper.class), Collections.emptyList())) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining(expectedMessage); + } + + private static Stream invalidAttributes() { + return Stream.of( + Arguments.of( + new Attributes().withAdditionalProperty("key", null), + "Error processing attribute with key \"key\": unexpected null value"), + Arguments.of( + new Attributes().withAdditionalProperty("key", new Object()), + "Error processing attribute with key \"key\": unrecognized value type java.lang.Object"), + Arguments.of( + new Attributes().withAdditionalProperty("key", Arrays.asList(1L, 1)), + "Error processing attribute with key \"key\": expected value entries to be of type class java.lang.Long but found entry with type class java.lang.Integer"), + Arguments.of( + new Attributes().withAdditionalProperty("key", Arrays.asList(1L, null)), + "Error processing attribute with key \"key\": unexpected null element in value")); + } + + @Test + void create() { + assertThat( + AttributesFactory.getInstance() + .create( + new Attributes() + .withServiceName("my-service") + .withAdditionalProperty("strKey", "val") + .withAdditionalProperty("longKey", 1L) + .withAdditionalProperty("intKey", 2) + .withAdditionalProperty("doubleKey", 1.0d) + .withAdditionalProperty("floatKey", 2.0f) + .withAdditionalProperty("boolKey", true) + .withAdditionalProperty("strArrKey", Arrays.asList("val1", "val2")) + .withAdditionalProperty("longArrKey", Arrays.asList(1L, 2L)) + .withAdditionalProperty("intArrKey", Arrays.asList(1, 2)) + .withAdditionalProperty("doubleArrKey", Arrays.asList(1.0d, 2.0d)) + .withAdditionalProperty("floatArrKey", Arrays.asList(1.0f, 2.0f)) + .withAdditionalProperty("boolArrKey", Arrays.asList(true, false)), + mock(SpiHelper.class), + Collections.emptyList())) + .isEqualTo( + io.opentelemetry.api.common.Attributes.builder() + .put(ResourceAttributes.SERVICE_NAME, "my-service") + .put("strKey", "val") + .put("longKey", 1L) + .put("intKey", 2) + .put("doubleKey", 1.0d) + .put("floatKey", 2.0f) + .put("boolKey", true) + .put("strArrKey", "val1", "val2") + .put("longArrKey", 1L, 2L) + .put("intArrKey", 1, 2) + .put("doubleArrKey", 1.0d, 2.0d) + .put("floatArrKey", 1.0f, 2.0f) + .put("boolArrKey", true, false) + .build()); + } +} diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java index 62fdb55ad98..4a0ecc2ff97 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java @@ -14,6 +14,7 @@ import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Attributes; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchLogRecordProcessor; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchSpanProcessor; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporter; @@ -22,6 +23,7 @@ import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LoggerProvider; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfiguration; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Otlp; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Resource; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanExporter; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanProcessor; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.TracerProvider; @@ -29,6 +31,7 @@ import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SpanLimits; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; import java.io.Closeable; import java.util.ArrayList; import java.util.Arrays; @@ -94,10 +97,16 @@ void create_Defaults() { @Test void create_Configured() { List closeables = new ArrayList<>(); + io.opentelemetry.sdk.resources.Resource expectedResource = + io.opentelemetry.sdk.resources.Resource.getDefault().toBuilder() + .put(ResourceAttributes.SERVICE_NAME, "my-service") + .put("key", "val") + .build(); OpenTelemetrySdk expectedSdk = OpenTelemetrySdk.builder() .setLoggerProvider( SdkLoggerProvider.builder() + .setResource(expectedResource) .setLogLimits( () -> LogLimits.builder() @@ -111,6 +120,7 @@ void create_Configured() { .build()) .setTracerProvider( SdkTracerProvider.builder() + .setResource(expectedResource) .setSpanLimits( SpanLimits.builder() .setMaxNumberOfAttributes(1) @@ -133,6 +143,12 @@ void create_Configured() { .create( new OpenTelemetryConfiguration() .withFileFormat("0.1") + .withResource( + new Resource() + .withAttributes( + new Attributes() + .withServiceName("my-service") + .withAdditionalProperty("key", "val"))) .withLoggerProvider( new LoggerProvider() .withLimits( diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ResourceFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ResourceFactoryTest.java new file mode 100644 index 00000000000..d465dbf091f --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ResourceFactoryTest.java @@ -0,0 +1,47 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.mockito.Mockito.mock; + +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Attributes; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +import java.util.Collections; +import org.junit.jupiter.api.Test; + +class ResourceFactoryTest { + + @Test + void create_Null() { + assertThat( + ResourceFactory.getInstance() + .create(null, mock(SpiHelper.class), Collections.emptyList())) + .isEqualTo(Resource.getDefault()); + } + + @Test + void create() { + assertThat( + ResourceFactory.getInstance() + .create( + new io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model + .Resource() + .withAttributes( + new Attributes() + .withServiceName("my-service") + .withAdditionalProperty("key", "val")), + mock(SpiHelper.class), + Collections.emptyList())) + .isEqualTo( + Resource.getDefault().toBuilder() + .put(ResourceAttributes.SERVICE_NAME, "my-service") + .put("key", "val") + .build()); + } +} From 0ab8496a0659b82b59689650e85ecf34deb47a00 Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Thu, 24 Aug 2023 10:22:30 -0500 Subject: [PATCH 2/2] PR feedback, test coverage --- .../sdk/extension/incubator/fileconfig/AttributesFactory.java | 3 +-- .../extension/incubator/fileconfig/AttributesFactoryTest.java | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactory.java index 2ae94d45846..92e385992f1 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactory.java @@ -48,7 +48,6 @@ public io.opentelemetry.api.common.Attributes create( throw new ConfigurationException( "Error processing attribute with key \"" + key + "\": unexpected null value"); } - Class valueClass = value.getClass(); if (value instanceof String) { builder.put(key, (String) value); return; @@ -73,7 +72,7 @@ public io.opentelemetry.api.common.Attributes create( builder.put(key, (boolean) value); return; } - if (List.class.isAssignableFrom(valueClass)) { + if (value instanceof List) { List values = (List) value; if (values.isEmpty()) { return; diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactoryTest.java index dca78ba641c..bdba5e80bb7 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/AttributesFactoryTest.java @@ -76,7 +76,8 @@ void create() { .withAdditionalProperty("intArrKey", Arrays.asList(1, 2)) .withAdditionalProperty("doubleArrKey", Arrays.asList(1.0d, 2.0d)) .withAdditionalProperty("floatArrKey", Arrays.asList(1.0f, 2.0f)) - .withAdditionalProperty("boolArrKey", Arrays.asList(true, false)), + .withAdditionalProperty("boolArrKey", Arrays.asList(true, false)) + .withAdditionalProperty("emptyArrKey", Collections.emptyList()), mock(SpiHelper.class), Collections.emptyList())) .isEqualTo(