diff --git a/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpConfig.java b/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpConfig.java index 56bed994d6..01ff6d9185 100644 --- a/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpConfig.java +++ b/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpConfig.java @@ -18,8 +18,12 @@ import io.micrometer.core.instrument.config.validate.Validated; import io.micrometer.core.instrument.push.PushRegistryConfig; -import static io.micrometer.core.instrument.config.MeterRegistryConfigValidator.checkAll; -import static io.micrometer.core.instrument.config.MeterRegistryConfigValidator.checkRequired; +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +import static io.micrometer.core.instrument.config.MeterRegistryConfigValidator.*; +import static io.micrometer.core.instrument.config.validate.PropertyValidator.getString; import static io.micrometer.core.instrument.config.validate.PropertyValidator.getUrlString; /** @@ -48,9 +52,44 @@ default String url() { return getUrlString(this, "url").orElse("http://localhost:4318/v1/metrics"); } + /** + * Attributes to set on the Resource that will be used for all metrics published. This + * should include a {@code service.name} attribute that identifies your service. + *

+ * By default, resource attributes will load using the {@link #get(String)} method, + * extracting key values from a comma-separated list in the format + * {@code key1=value1,key2=value2}. Resource attributes will be loaded from the + * {@code OTEL_RESOURCE_ATTRIBUTES} environment variable and the service name from the + * {@code OTEL_SERVICE_NAME} environment variable if they are set and + * {@link #get(String)} does not return a value. + * @return map of key value pairs to use as resource attributes + * @see OpenTelemetry + * Resource Semantic Conventions + */ + default Map resourceAttributes() { + Map env = System.getenv(); + String resourceAttributesConfig = getString(this, "resourceAttributes") + .orElse(env.get("OTEL_RESOURCE_ATTRIBUTES")); + String[] splitResourceAttributesString = resourceAttributesConfig == null ? new String[] {} + : resourceAttributesConfig.trim().split(","); + + Map resourceAttributes = Arrays.stream(splitResourceAttributesString).map(String::trim) + .filter(keyvalue -> keyvalue.length() > 2 && keyvalue.indexOf('=') > 0) + .collect(Collectors.toMap(keyvalue -> keyvalue.substring(0, keyvalue.indexOf('=')).trim(), + keyvalue -> keyvalue.substring(keyvalue.indexOf('=') + 1).trim())); + + if (env.containsKey("OTEL_SERVICE_NAME") && !resourceAttributes.containsKey("service.name")) { + resourceAttributes.put("service.name", env.get("OTEL_SERVICE_NAME")); + } + + return resourceAttributes; + } + @Override default Validated validate() { - return checkAll(this, c -> PushRegistryConfig.validate(c), checkRequired("url", OtlpConfig::url)); + return checkAll(this, c -> PushRegistryConfig.validate(c), checkRequired("url", OtlpConfig::url), + check("resourceAttributes", OtlpConfig::resourceAttributes)); } } diff --git a/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpMeterRegistry.java b/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpMeterRegistry.java index 46faae19a9..29d627480b 100644 --- a/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpMeterRegistry.java +++ b/implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpMeterRegistry.java @@ -40,6 +40,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.function.DoubleSupplier; @@ -277,20 +278,30 @@ private Iterable getTagsForId(Meter.Id id) { .collect(Collectors.toList()); } - private KeyValue createKeyValue(String key, String value) { + // VisibleForTesting + static KeyValue createKeyValue(String key, String value) { return KeyValue.newBuilder().setKey(key).setValue(AnyValue.newBuilder().setStringValue(value)).build(); } - private Iterable getResourceAttributes() { + // VisibleForTesting + Iterable getResourceAttributes() { + boolean serviceNameProvided = false; List attributes = new ArrayList<>(); - // TODO How to expose configuration of the service.name - attributes.add(createKeyValue("service.name", "unknown_service")); attributes.add(createKeyValue("telemetry.sdk.name", "io.micrometer")); attributes.add(createKeyValue("telemetry.sdk.language", "java")); String micrometerCoreVersion = MeterRegistry.class.getPackage().getImplementationVersion(); if (micrometerCoreVersion != null) { attributes.add(createKeyValue("telemetry.sdk.version", micrometerCoreVersion)); } + for (Map.Entry keyValue : this.config.resourceAttributes().entrySet()) { + if ("service.name".equals(keyValue.getKey())) { + serviceNameProvided = true; + } + attributes.add(createKeyValue(keyValue.getKey(), keyValue.getValue())); + } + if (!serviceNameProvided) { + attributes.add(createKeyValue("service.name", "unknown_service")); + } return attributes; } diff --git a/implementations/micrometer-registry-otlp/src/test/java/io/micrometer/registry/otlp/OtlpConfigTest.java b/implementations/micrometer-registry-otlp/src/test/java/io/micrometer/registry/otlp/OtlpConfigTest.java new file mode 100644 index 0000000000..d05a9cd59e --- /dev/null +++ b/implementations/micrometer-registry-otlp/src/test/java/io/micrometer/registry/otlp/OtlpConfigTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2022 VMware, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micrometer.registry.otlp; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class OtlpConfigTest { + + @Test + void resourceAttributesInputParsing() { + OtlpConfig config = k -> "key1=value1,"; + assertThat(config.resourceAttributes()).containsEntry("key1", "value1").hasSize(1); + config = k -> "k=v,a"; + assertThat(config.resourceAttributes()).containsEntry("k", "v").hasSize(1); + config = k -> "k=v,a=="; + assertThat(config.resourceAttributes()).containsEntry("k", "v").containsEntry("a", "=").hasSize(2); + config = k -> " k = v, a= b "; + assertThat(config.resourceAttributes()).containsEntry("k", "v").containsEntry("a", "b").hasSize(2); + } + +} diff --git a/implementations/micrometer-registry-otlp/src/test/java/io/micrometer/registry/otlp/OtlpMeterRegistryTest.java b/implementations/micrometer-registry-otlp/src/test/java/io/micrometer/registry/otlp/OtlpMeterRegistryTest.java index d34f455e1d..e4e4aca190 100644 --- a/implementations/micrometer-registry-otlp/src/test/java/io/micrometer/registry/otlp/OtlpMeterRegistryTest.java +++ b/implementations/micrometer-registry-otlp/src/test/java/io/micrometer/registry/otlp/OtlpMeterRegistryTest.java @@ -18,9 +18,13 @@ import io.micrometer.core.instrument.*; import org.junit.jupiter.api.Test; +import java.io.IOException; import java.lang.management.CompilationMXBean; import java.lang.management.ManagementFactory; import java.lang.management.OperatingSystemMXBean; +import java.util.Collections; +import java.util.Map; +import java.util.Properties; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; @@ -466,4 +470,39 @@ void longTaskTimer() { + " }\n" + " aggregation_temporality: AGGREGATION_TEMPORALITY_CUMULATIVE\n" + "}\n"); } + // If the service.name was not specified, SDKs MUST fallback to 'unknown_service' + @Test + void unknownServiceByDefault() { + assertThat(registry.getResourceAttributes()) + .contains(OtlpMeterRegistry.createKeyValue("service.name", "unknown_service")); + } + + @Test + void setServiceNameOverrideMethod() { + registry = new OtlpMeterRegistry(new OtlpConfig() { + @Override + public String get(String key) { + return null; + } + + @Override + public Map resourceAttributes() { + return Collections.singletonMap("service.name", "myService"); + } + }, Clock.SYSTEM); + + assertThat(registry.getResourceAttributes()) + .contains(OtlpMeterRegistry.createKeyValue("service.name", "myService")); + } + + // can't test environment variables easily in an isolated way + @Test + void setResourceAttributesAsString() throws IOException { + Properties propertiesConfig = new Properties(); + propertiesConfig.load(this.getClass().getResourceAsStream("/otlp-config.properties")); + registry = new OtlpMeterRegistry(key -> (String) propertiesConfig.get(key), Clock.SYSTEM); + assertThat(registry.getResourceAttributes()).contains(OtlpMeterRegistry.createKeyValue("key1", "value1"), + OtlpMeterRegistry.createKeyValue("key2", "value2")); + } + } diff --git a/implementations/micrometer-registry-otlp/src/test/resources/otlp-config.properties b/implementations/micrometer-registry-otlp/src/test/resources/otlp-config.properties new file mode 100644 index 0000000000..3760ed4e95 --- /dev/null +++ b/implementations/micrometer-registry-otlp/src/test/resources/otlp-config.properties @@ -0,0 +1,17 @@ +# +# Copyright 2022 VMware, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +otlp.resourceAttributes=key1=value1,key2=value2