diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/SpiHelper.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/SpiHelper.java index bd3bfc13f0..c8c40e63e0 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/SpiHelper.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/SpiHelper.java @@ -81,9 +81,9 @@ public final class SpiHelper { } /** - * Find a registered {@link ComponentProvider} which {@link ComponentProvider#getType()} matching + * Find a registered {@link ComponentProvider} with {@link ComponentProvider#getType()} matching * {@code type}, {@link ComponentProvider#getName()} matching {@code name}, and call {@link - * ComponentProvider#create(StructuredConfigProperties)} with the given {@code model}. + * ComponentProvider#create(StructuredConfigProperties)} with the given {@code config}. * * @throws ConfigurationException if no matching providers are found, or if multiple are found * (i.e. conflict), or if {@link ComponentProvider#create(StructuredConfigProperties)} throws 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 index 04a0de2bca..eaaf1ae98b 100644 --- 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 @@ -6,14 +6,25 @@ package io.opentelemetry.sdk.extension.incubator.fileconfig; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.autoconfigure.spi.Ordered; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; 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 io.opentelemetry.sdk.resources.Resource; import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; -final class ResourceFactory implements Factory { +final class ResourceFactory + implements Factory< + io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Resource, Resource> { + private static final StructuredConfigProperties EMPTY_CONFIG = + FileConfiguration.toConfigProperties(Collections.emptyMap()); private static final ResourceFactory INSTANCE = new ResourceFactory(); private ResourceFactory() {} @@ -23,16 +34,82 @@ final class ResourceFactory implements Factory closeables) { - ResourceBuilder builder = io.opentelemetry.sdk.resources.Resource.getDefault().toBuilder(); + public Resource create( + io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Resource model, + SpiHelper spiHelper, + List closeables) { + Resource result = Resource.getDefault(); + + List resourceDetectorResources = loadFromResourceDetectors(spiHelper); + for (Resource resourceProviderResource : resourceDetectorResources) { + result = result.merge(resourceProviderResource); + } Attributes attributesModel = model.getAttributes(); if (attributesModel != null) { - builder.putAll( - AttributesFactory.getInstance().create(attributesModel, spiHelper, closeables)); + result = + result.toBuilder() + .putAll( + AttributesFactory.getInstance().create(attributesModel, spiHelper, closeables)) + .build(); } - return builder.build(); + return result; + } + + /** + * Load resources from resource detectors, in order of lowest priority to highest priority. + * + *

In file configuration, a resource detector is a {@link ComponentProvider} with {@link + * ComponentProvider#getType()} set to {@link Resource}. Unlike other {@link ComponentProvider}s, + * the resource detector version does not use {@link ComponentProvider#getName()} (except for + * debug messages), and {@link ComponentProvider#create(StructuredConfigProperties)} is called + * with an empty instance. Additionally, the {@link Ordered#order()} value is respected for + * resource detectors which implement {@link Ordered}. + */ + @SuppressWarnings("rawtypes") + private static List loadFromResourceDetectors(SpiHelper spiHelper) { + List componentProviders = spiHelper.load(ComponentProvider.class); + List resourceAndOrders = new ArrayList<>(); + for (ComponentProvider componentProvider : componentProviders) { + if (componentProvider.getType() != Resource.class) { + continue; + } + Resource resource; + try { + resource = (Resource) componentProvider.create(EMPTY_CONFIG); + } catch (Throwable throwable) { + throw new ConfigurationException( + "Error configuring " + + Resource.class.getName() + + " with name \"" + + componentProvider.getName() + + "\"", + throwable); + } + int order = + (componentProvider instanceof Ordered) ? ((Ordered) componentProvider).order() : 0; + resourceAndOrders.add(new ResourceAndOrder(resource, order)); + } + resourceAndOrders.sort(Comparator.comparing(ResourceAndOrder::order)); + return resourceAndOrders.stream().map(ResourceAndOrder::resource).collect(Collectors.toList()); + } + + private static final class ResourceAndOrder { + private final Resource resource; + private final int order; + + private ResourceAndOrder(Resource resource, int order) { + this.resource = resource; + this.order = order; + } + + private Resource resource() { + return resource; + } + + private int order() { + return order; + } } } 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 da3cc7cd01..7de50047ec 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 @@ -140,6 +140,10 @@ class OpenTelemetryConfigurationFactoryTest { io.opentelemetry.sdk.resources.Resource.getDefault().toBuilder() .put("service.name", "my-service") .put("key", "val") + // resource attributes from resource ComponentProviders + .put("color", "red") + .put("shape", "square") + .put("order", "second") .build(); OpenTelemetrySdk expectedSdk = OpenTelemetrySdk.builder() 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 index 37e494ee28..584f908431 100644 --- 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 @@ -6,7 +6,7 @@ package io.opentelemetry.sdk.extension.incubator.fileconfig; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Attributes; @@ -16,8 +16,11 @@ import org.junit.jupiter.api.Test; class ResourceFactoryTest { + private SpiHelper spiHelper = SpiHelper.create(MetricExporterFactoryTest.class.getClassLoader()); + @Test void create() { + spiHelper = spy(spiHelper); assertThat( ResourceFactory.getInstance() .create( @@ -26,13 +29,21 @@ class ResourceFactoryTest { .withAttributes( new Attributes() .withServiceName("my-service") - .withAdditionalProperty("key", "val")), - mock(SpiHelper.class), + .withAdditionalProperty("key", "val") + // Should override shape attribute from ResourceComponentProvider + .withAdditionalProperty("shape", "circle")), + spiHelper, Collections.emptyList())) .isEqualTo( Resource.getDefault().toBuilder() .put("service.name", "my-service") .put("key", "val") + .put("shape", "circle") + // From ResourceComponentProvider + .put("color", "red") + // From ResourceOrderedSecondComponentProvider, which takes priority over + // ResourceOrderedFirstComponentProvider + .put("order", "second") .build()); } } diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/ResourceComponentProvider.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/ResourceComponentProvider.java new file mode 100644 index 0000000000..13b0e86c05 --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/ResourceComponentProvider.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig.component; + +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; +import io.opentelemetry.sdk.resources.Resource; + +public class ResourceComponentProvider implements ComponentProvider { + @Override + public Class getType() { + return Resource.class; + } + + @Override + public String getName() { + return "unused"; + } + + @Override + public Resource create(StructuredConfigProperties config) { + return Resource.builder().put("shape", "square").put("color", "red").build(); + } +} diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/ResourceOrderedFirstComponentProvider.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/ResourceOrderedFirstComponentProvider.java new file mode 100644 index 0000000000..f2f41e5b95 --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/ResourceOrderedFirstComponentProvider.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig.component; + +import io.opentelemetry.sdk.autoconfigure.spi.Ordered; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; +import io.opentelemetry.sdk.resources.Resource; + +public class ResourceOrderedFirstComponentProvider implements ComponentProvider, Ordered { + @Override + public Class getType() { + return Resource.class; + } + + @Override + public String getName() { + return "unused"; + } + + @Override + public Resource create(StructuredConfigProperties config) { + return Resource.builder().put("order", "first").build(); + } + + @Override + public int order() { + return 1; + } +} diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/ResourceOrderedSecondComponentProvider.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/ResourceOrderedSecondComponentProvider.java new file mode 100644 index 0000000000..00017b2b7d --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/component/ResourceOrderedSecondComponentProvider.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig.component; + +import io.opentelemetry.sdk.autoconfigure.spi.Ordered; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties; +import io.opentelemetry.sdk.resources.Resource; + +public class ResourceOrderedSecondComponentProvider + implements ComponentProvider, Ordered { + @Override + public Class getType() { + return Resource.class; + } + + @Override + public String getName() { + return "unused"; + } + + @Override + public Resource create(StructuredConfigProperties config) { + return Resource.builder().put("order", "second").build(); + } + + @Override + public int order() { + return 2; + } +} diff --git a/sdk-extensions/incubator/src/test/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider b/sdk-extensions/incubator/src/test/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider index 0dc2d209e1..6ab8ffdecc 100644 --- a/sdk-extensions/incubator/src/test/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider +++ b/sdk-extensions/incubator/src/test/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider @@ -1,3 +1,6 @@ io.opentelemetry.sdk.extension.incubator.fileconfig.component.MetricExporterComponentProvider io.opentelemetry.sdk.extension.incubator.fileconfig.component.SpanExporterComponentProvider io.opentelemetry.sdk.extension.incubator.fileconfig.component.LogRecordExporterComponentProvider +io.opentelemetry.sdk.extension.incubator.fileconfig.component.ResourceComponentProvider +io.opentelemetry.sdk.extension.incubator.fileconfig.component.ResourceOrderedFirstComponentProvider +io.opentelemetry.sdk.extension.incubator.fileconfig.component.ResourceOrderedSecondComponentProvider