diff --git a/micrometer-core/src/main/java/io/micronaut/configuration/metrics/common/tags/CommonTagsCondition.java b/micrometer-core/src/main/java/io/micronaut/configuration/metrics/common/tags/CommonTagsCondition.java new file mode 100644 index 000000000..2384ce496 --- /dev/null +++ b/micrometer-core/src/main/java/io/micronaut/configuration/metrics/common/tags/CommonTagsCondition.java @@ -0,0 +1,38 @@ +/* + * Copyright 2017-2026 original authors + * + * 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.micronaut.configuration.metrics.common.tags; + +import io.micronaut.context.condition.Condition; +import io.micronaut.context.condition.ConditionContext; + +import static io.micronaut.configuration.metrics.micrometer.MeterRegistryFactory.MICRONAUT_METRICS_COMMON_TAGS; +import static io.micronaut.configuration.metrics.micrometer.MeterRegistryFactory.MICRONAUT_METRICS_TAGS; + +/** + * Activates common tag configuration when either supported property prefix is configured. + */ +public final class CommonTagsCondition implements Condition { + + @Override + public boolean matches(ConditionContext context) { + return containsConfiguredTags(context, MICRONAUT_METRICS_TAGS) + || containsConfiguredTags(context, MICRONAUT_METRICS_COMMON_TAGS); + } + + private static boolean containsConfiguredTags(ConditionContext context, String property) { + return context.containsProperty(property) || context.containsProperties(property); + } +} diff --git a/micrometer-core/src/main/java/io/micronaut/configuration/metrics/common/tags/CommonTagsConfigurer.java b/micrometer-core/src/main/java/io/micronaut/configuration/metrics/common/tags/CommonTagsConfigurer.java index 5f50ab0d7..b414cc0f7 100644 --- a/micrometer-core/src/main/java/io/micronaut/configuration/metrics/common/tags/CommonTagsConfigurer.java +++ b/micrometer-core/src/main/java/io/micronaut/configuration/metrics/common/tags/CommonTagsConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 original authors + * Copyright 2017-2026 original authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,30 +19,41 @@ import io.micrometer.core.instrument.Tag; import io.micronaut.configuration.metrics.aggregator.MeterRegistryConfigurer; import io.micronaut.configuration.metrics.annotation.RequiresMetrics; -import io.micronaut.configuration.metrics.micrometer.ExportConfigurationProperties; import io.micronaut.context.annotation.Factory; import io.micronaut.context.annotation.Requires; +import io.micronaut.context.env.Environment; +import io.micronaut.core.naming.conventions.StringConvention; +import io.micronaut.core.type.Argument; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; -import java.util.Properties; +import java.util.Map; +import java.util.TreeMap; import static io.micronaut.configuration.metrics.micrometer.MeterRegistryFactory.MICRONAUT_METRICS_COMMON_TAGS; +import static io.micronaut.configuration.metrics.micrometer.MeterRegistryFactory.MICRONAUT_METRICS_TAGS; /** * Configures every MeterRegistry with common tags. */ @Factory @RequiresMetrics -@Requires(property = MICRONAUT_METRICS_COMMON_TAGS) +@Requires(condition = CommonTagsCondition.class) public class CommonTagsConfigurer implements MeterRegistryConfigurer { + private static final Argument> STRING_OBJECT_MAP = Argument.mapOf(String.class, Object.class); + private final List commonTags = new ArrayList<>(); - public CommonTagsConfigurer(ExportConfigurationProperties configuration) { - Properties tags = configuration.getTags(); - for (String key : tags.stringPropertyNames()) { - commonTags.add(Tag.of(key, tags.getProperty(key))); + public CommonTagsConfigurer(Environment environment) { + Map tags = new TreeMap<>(readConfiguredTags(environment, MICRONAUT_METRICS_TAGS)); + tags.putAll(readConfiguredTags(environment, MICRONAUT_METRICS_COMMON_TAGS)); + for (Map.Entry entry : tags.entrySet()) { + Object value = entry.getValue(); + if (value != null) { + commonTags.add(Tag.of(entry.getKey(), String.valueOf(value))); + } } } @@ -60,4 +71,14 @@ public Class getType() { public int getOrder() { return HIGHEST_PRECEDENCE; } + + private static Map readConfiguredTags(Environment environment, String property) { + if (environment.containsProperties(property)) { + return new LinkedHashMap<>(environment.getProperties(property, StringConvention.RAW)); + } + if (environment.containsProperty(property)) { + return new LinkedHashMap<>(environment.getProperty(property, STRING_OBJECT_MAP).orElse(Map.of())); + } + return new LinkedHashMap<>(); + } } diff --git a/micrometer-core/src/main/java/io/micronaut/configuration/metrics/micrometer/MeterRegistryFactory.java b/micrometer-core/src/main/java/io/micronaut/configuration/metrics/micrometer/MeterRegistryFactory.java index ffc92eb71..3c8eb73a0 100644 --- a/micrometer-core/src/main/java/io/micronaut/configuration/metrics/micrometer/MeterRegistryFactory.java +++ b/micrometer-core/src/main/java/io/micronaut/configuration/metrics/micrometer/MeterRegistryFactory.java @@ -45,7 +45,8 @@ public class MeterRegistryFactory { public static final String MICRONAUT_METRICS = "micronaut.metrics."; public static final String MICRONAUT_METRICS_BINDERS = MICRONAUT_METRICS + "binders"; - public static final String MICRONAUT_METRICS_COMMON_TAGS = MICRONAUT_METRICS + "tags"; + public static final String MICRONAUT_METRICS_TAGS = MICRONAUT_METRICS + "tags"; + public static final String MICRONAUT_METRICS_COMMON_TAGS = MICRONAUT_METRICS + "common-tags"; public static final String MICRONAUT_METRICS_ENABLED = MICRONAUT_METRICS + "enabled"; public static final String MICRONAUT_METRICS_EXPORT = MICRONAUT_METRICS + "export"; diff --git a/micrometer-core/src/test/groovy/io/micronaut/configuration/metrics/common/tags/CommonTagsConfigurerSpec.groovy b/micrometer-core/src/test/groovy/io/micronaut/configuration/metrics/common/tags/CommonTagsConfigurerSpec.groovy new file mode 100644 index 000000000..e4a5e5a66 --- /dev/null +++ b/micrometer-core/src/test/groovy/io/micronaut/configuration/metrics/common/tags/CommonTagsConfigurerSpec.groovy @@ -0,0 +1,59 @@ +package io.micronaut.configuration.metrics.common.tags + +import io.micrometer.core.instrument.simple.SimpleMeterRegistry +import io.micronaut.context.ApplicationContext +import io.micronaut.context.env.Environment +import io.micronaut.core.naming.conventions.StringConvention +import spock.lang.Specification + +import static io.micronaut.configuration.metrics.micrometer.MeterRegistryFactory.MICRONAUT_METRICS_COMMON_TAGS +import static io.micronaut.configuration.metrics.micrometer.MeterRegistryFactory.MICRONAUT_METRICS_TAGS + +class CommonTagsConfigurerSpec extends Specification { + + void "common-tags override legacy tags for duplicate keys"() { + given: + ApplicationContext context = ApplicationContext.run([ + "micronaut.metrics.tags.application" : "legacy", + "micronaut.metrics.tags.region" : "us-east-1", + "micronaut.metrics.common-tags.application": "common-tags", + "micronaut.metrics.common-tags.cluster" : "primary" + ]) + SimpleMeterRegistry registry = new SimpleMeterRegistry() + + when: + context.getBean(CommonTagsConfigurer).configure(registry) + def counter = registry.counter("test.counter") + + then: + counter.id.getTag("application") == "common-tags" + counter.id.getTag("region") == "us-east-1" + counter.id.getTag("cluster") == "primary" + + cleanup: + registry.close() + context.close() + } + + void "null common tag values are ignored"() { + given: + Environment environment = Stub() { + containsProperties(MICRONAUT_METRICS_TAGS) >> false + containsProperty(MICRONAUT_METRICS_TAGS) >> false + containsProperties(MICRONAUT_METRICS_COMMON_TAGS) >> true + getProperties(MICRONAUT_METRICS_COMMON_TAGS, StringConvention.RAW) >> [application: null, region: "us-east-1"] + } + SimpleMeterRegistry registry = new SimpleMeterRegistry() + + when: + new CommonTagsConfigurer(environment).configure(registry) + def counter = registry.counter("test.counter") + + then: + counter.id.getTag("application") == null + counter.id.getTag("region") == "us-east-1" + + cleanup: + registry.close() + } +} diff --git a/micrometer-core/src/test/groovy/io/micronaut/configuration/metrics/management/endpoint/MetricsEndpointSpec.groovy b/micrometer-core/src/test/groovy/io/micronaut/configuration/metrics/management/endpoint/MetricsEndpointSpec.groovy index 6c0366743..acc95f160 100644 --- a/micrometer-core/src/test/groovy/io/micronaut/configuration/metrics/management/endpoint/MetricsEndpointSpec.groovy +++ b/micrometer-core/src/test/groovy/io/micronaut/configuration/metrics/management/endpoint/MetricsEndpointSpec.groovy @@ -378,12 +378,15 @@ class MetricsEndpointSpec extends Specification { } @Unroll - void "test metrics endpoint with common tags"() { + void "test metrics endpoint #name with common tags from #configurationStyle #propertyName"() { given: - run('endpoints.metrics.sensitive' : false, - (MICRONAUT_METRICS_ENABLED) : true, - "micronaut.metrics.binders.web.enabled": true, - "micronaut.metrics.tags" : ["test1": "test1-val", "test2": "test2-val"]) + Map config = [ + 'endpoints.metrics.sensitive' : false, + (MICRONAUT_METRICS_ENABLED) : true, + "micronaut.metrics.binders.web.enabled": true + ] + config.putAll(tagsConfig) + run(config) expect: 100.times { @@ -400,7 +403,15 @@ class MetricsEndpointSpec extends Specification { } where: - name << ["process.files.open", "process.files.max"] + configurationStyle | propertyName | name | tagsConfig + "dotted" | "micronaut.metrics.tags" | "process.files.open" | ["micronaut.metrics.tags.test1" : "test1-val", "micronaut.metrics.tags.test2" : "test2-val"] + "dotted" | "micronaut.metrics.tags" | "process.files.max" | ["micronaut.metrics.tags.test1" : "test1-val", "micronaut.metrics.tags.test2" : "test2-val"] + "dotted" | "micronaut.metrics.common-tags" | "process.files.open" | ["micronaut.metrics.common-tags.test1": "test1-val", "micronaut.metrics.common-tags.test2": "test2-val"] + "dotted" | "micronaut.metrics.common-tags" | "process.files.max" | ["micronaut.metrics.common-tags.test1": "test1-val", "micronaut.metrics.common-tags.test2": "test2-val"] + "map" | "micronaut.metrics.tags" | "process.files.open" | ["micronaut.metrics.tags" : [test1: "test1-val", test2: "test2-val"]] + "map" | "micronaut.metrics.tags" | "process.files.max" | ["micronaut.metrics.tags" : [test1: "test1-val", test2: "test2-val"]] + "map" | "micronaut.metrics.common-tags" | "process.files.open" | ["micronaut.metrics.common-tags" : [test1: "test1-val", test2: "test2-val"]] + "map" | "micronaut.metrics.common-tags" | "process.files.max" | ["micronaut.metrics.common-tags" : [test1: "test1-val", test2: "test2-val"]] } @Unroll diff --git a/micrometer-core/src/test/groovy/io/micronaut/configuration/metrics/micrometer/MeterRegistryConfigurerOrderSpec.groovy b/micrometer-core/src/test/groovy/io/micronaut/configuration/metrics/micrometer/MeterRegistryConfigurerOrderSpec.groovy index 9bd14910e..c7556f194 100644 --- a/micrometer-core/src/test/groovy/io/micronaut/configuration/metrics/micrometer/MeterRegistryConfigurerOrderSpec.groovy +++ b/micrometer-core/src/test/groovy/io/micronaut/configuration/metrics/micrometer/MeterRegistryConfigurerOrderSpec.groovy @@ -11,14 +11,13 @@ class MeterRegistryConfigurerOrderSpec extends Specification { void "verify beans created by in correct order"() { when: ApplicationContext ctx = ApplicationContext.run([ - "test.properties.enabled": true, - "micronaut.metrics.tags.test": "test" + "test.properties.enabled": true ]) List configurerList = ctx.getBeansOfType(MeterRegistryConfigurer) then: - configurerList.size() == 3 + configurerList.any { it.getClass() == CompositeMeterRegistryConfigurer.class } configurerList.last().getClass() == CompositeMeterRegistryConfigurer.class cleanup: diff --git a/src/main/docs/guide/metricsConcepts.adoc b/src/main/docs/guide/metricsConcepts.adoc index 6d63a86c8..71476950a 100644 --- a/src/main/docs/guide/metricsConcepts.adoc +++ b/src/main/docs/guide/metricsConcepts.adoc @@ -26,6 +26,18 @@ include::{metricscore}/aggregator/MeterRegistryConfigurer.java[] include::{testsmetricscore}/SimpleMeterRegistryConfigurer.java[] ---- +Micronaut can also add common tags to every meter via configuration. The legacy `micronaut.metrics.tags.*` prefix remains supported, and `micronaut.metrics.common-tags.*` is available as a clearer alias. +When both prefixes configure the same tag key, `micronaut.metrics.common-tags.*` takes precedence. + +[configuration] +---- +micronaut: + metrics: + common-tags: + application: ${micronaut.application.name} + region: us-east-1 +---- + === Meter Filter * A https://micrometer.io/docs/concepts#_meter_filters[meter filter] can be used to determine if a Meter is to be added to the registry.