diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/ResourceDetector.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/ResourceDetector.java new file mode 100644 index 00000000000..c613165bc58 --- /dev/null +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/ResourceDetector.java @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure.spi.internal; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.Ordered; +import java.util.Optional; +import java.util.function.Function; + +public interface ResourceDetector extends Ordered { + /** Read the data for the resource attributes. */ + Optional readData(ConfigProperties config); + + /** Registers the attributes that this resource detector can provide. */ + void registerAttributes(Builder builder); + + /** Greater order means lower priority. The default order is 0. */ + @Override + int order(); + + /** Returns the name of this resource detector. */ + String name(); + + /** + * Returns whether this resource detector is enabled by default. If not, it will only be used if + * explicitly enabled in the configuration. + */ + default boolean defaultEnabled() { + return true; + } + + /** A builder for registering attributes that a resource detector can provide. */ + interface Builder { + /** + * Adds an attribute to the resource. + * + * @param key the attribute key + * @param getter a function that returns the value of the attribute from the data that is read + * by {@link ResourceDetector#readData(ConfigProperties)} + * @return this builder + * @param the type of the attribute + */ + Builder add(AttributeKey key, Function> getter); + } +} diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/ResourceConfiguration.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/ResourceConfiguration.java index 010d219acba..7d1a8824be2 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/ResourceConfiguration.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/ResourceConfiguration.java @@ -8,19 +8,24 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.sdk.autoconfigure.internal.ResourceDetectorReader; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.autoconfigure.spi.Ordered; import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; import io.opentelemetry.sdk.autoconfigure.spi.internal.ConditionalResourceProvider; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ResourceDetector; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.resources.ResourceBuilder; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.BiFunction; @@ -81,6 +86,7 @@ public static Resource createEnvironmentResource(ConfigProperties config) { return Resource.create(resourceAttributes.build()); } + @SuppressWarnings({"rawtypes", "unchecked"}) static Resource configureResource( ConfigProperties config, SpiHelper spiHelper, @@ -91,19 +97,39 @@ static Resource configureResource( new HashSet<>(config.getList("otel.java.enabled.resource.providers")); Set disabledProviders = new HashSet<>(config.getList("otel.java.disabled.resource.providers")); - for (ResourceProvider resourceProvider : spiHelper.loadOrdered(ResourceProvider.class)) { - if (!enabledProviders.isEmpty() - && !enabledProviders.contains(resourceProvider.getClass().getName())) { - continue; + List providers = (List) spiHelper.load(ResourceProvider.class); + providers.addAll(spiHelper.load(ResourceDetector.class)); + providers.sort(Comparator.comparingInt(Ordered::order)); + for (Ordered ordered : providers) { + if (ordered instanceof ResourceProvider) { + ResourceProvider provider = (ResourceProvider) ordered; + + if (!enabledProviders.isEmpty() + && !enabledProviders.contains(provider.getClass().getName())) { + continue; + } + if (disabledProviders.contains(provider.getClass().getName())) { + continue; + } + if (provider instanceof ConditionalResourceProvider + && !((ConditionalResourceProvider) provider).shouldApply(config, result)) { + continue; + } + result = result.merge(provider.createResource(config)); + } else { + ResourceDetector detector = (ResourceDetector) ordered; + + String key = String.format("otel.resource.provider.%s.enabled", detector.name()); + if (!config.getBoolean(key, detector.defaultEnabled())) { + continue; + } + + ResourceDetectorReader reader = new ResourceDetectorReader<>(detector); + + if (reader.shouldApply(config, result)) { + result = result.merge(reader.createResource(config, result)); + } } - if (disabledProviders.contains(resourceProvider.getClass().getName())) { - continue; - } - if (resourceProvider instanceof ConditionalResourceProvider - && !((ConditionalResourceProvider) resourceProvider).shouldApply(config, result)) { - continue; - } - result = result.merge(resourceProvider.createResource(config)); } result = filterAttributes(result, config); diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/ResourceDetectorReader.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/ResourceDetectorReader.java new file mode 100644 index 00000000000..35cb72eacdb --- /dev/null +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/ResourceDetectorReader.java @@ -0,0 +1,94 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure.internal; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ResourceDetector; +import io.opentelemetry.sdk.resources.Resource; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; + +@SuppressWarnings({"unchecked", "rawtypes"}) +public final class ResourceDetectorReader { + + private final ResourceDetector resourceDetector; + + public class AttributeBuilder implements ResourceDetector.Builder { + + private AttributeBuilder() {} + + @Override + public AttributeBuilder add(AttributeKey key, Function> getter) { + attributeGetters.put((AttributeKey) key, Objects.requireNonNull((Function) getter)); + return this; + } + } + + private final Map, Function>> attributeGetters = + new HashMap<>(); + + public ResourceDetectorReader(ResourceDetector resourceDetector) { + this.resourceDetector = resourceDetector; + resourceDetector.registerAttributes(new AttributeBuilder()); + } + + public boolean shouldApply(ConfigProperties config, Resource existing) { + Map resourceAttributes = getResourceAttributes(config); + return attributeGetters.keySet().stream() + .allMatch(key -> shouldUpdate(config, existing, key, resourceAttributes)); + } + + public Resource createResource(ConfigProperties config, Resource existing) { + return resourceDetector + .readData(config) + .map( + data -> { + Map resourceAttributes = getResourceAttributes(config); + AttributesBuilder builder = Attributes.builder(); + attributeGetters.entrySet().stream() + .filter(e -> shouldUpdate(config, existing, e.getKey(), resourceAttributes)) + .forEach( + e -> + e.getValue() + .apply(data) + .ifPresent(value -> putAttribute(builder, e.getKey(), value))); + return Resource.create(builder.build()); + }) + .orElse(Resource.empty()); + } + + private static void putAttribute(AttributesBuilder builder, AttributeKey key, T value) { + builder.put(key, value); + } + + private static Map getResourceAttributes(ConfigProperties config) { + return config.getMap("otel.resource.attributes"); + } + + private static boolean shouldUpdate( + ConfigProperties config, + Resource existing, + AttributeKey key, + Map resourceAttributes) { + if (resourceAttributes.containsKey(key.getKey())) { + return false; + } + + Object value = existing.getAttribute(key); + + if (key.getKey().equals("service.name")) { + return config.getString("otel.service.name") == null && "unknown_service:java".equals(value); + } + + return value == null; + } +}