From 776d233c1ca2d44bd7e4460b3b50b0ab9057f991 Mon Sep 17 00:00:00 2001 From: Tommy Ludwig <8924140+shakuzen@users.noreply.github.com> Date: Tue, 15 Apr 2025 18:28:46 +0900 Subject: [PATCH 1/2] Auto-configure OtlpMetricsSender if available Update `OtlpMetricsExportAutoConfiguration` to use the new `OtlpMetricsSender`interface that allows users to customize the sending of OTLP metrics using the `OtlpMeterRegistry`. See gh-45204 --- .../OtlpMetricsExportAutoConfiguration.java | 19 +++++++-- ...lpMetricsExportAutoConfigurationTests.java | 40 +++++++++++++++++++ .../reference/pages/actuator/metrics.adoc | 2 + 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java index 82d7ea2717..12ac2355b7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java @@ -19,7 +19,10 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp; import io.micrometer.core.instrument.Clock; import io.micrometer.registry.otlp.OtlpConfig; import io.micrometer.registry.otlp.OtlpMeterRegistry; +import io.micrometer.registry.otlp.OtlpMeterRegistry.Builder; +import io.micrometer.registry.otlp.OtlpMetricsSender; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.export.ConditionalOnEnabledMetricsExport; @@ -76,16 +79,24 @@ public class OtlpMetricsExportAutoConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnThreading(Threading.PLATFORM) - public OtlpMeterRegistry otlpMeterRegistry(OtlpConfig otlpConfig, Clock clock) { - return new OtlpMeterRegistry(otlpConfig, clock); + public OtlpMeterRegistry otlpMeterRegistry(OtlpConfig otlpConfig, Clock clock, + ObjectProvider metricsSender) { + Builder builder = OtlpMeterRegistry.builder(otlpConfig).clock(clock); + metricsSender.ifAvailable(builder::metricsSender); + return builder.build(); } @Bean @ConditionalOnMissingBean @ConditionalOnThreading(Threading.VIRTUAL) - public OtlpMeterRegistry otlpMeterRegistryVirtualThreads(OtlpConfig otlpConfig, Clock clock) { + public OtlpMeterRegistry otlpMeterRegistryVirtualThreads(OtlpConfig otlpConfig, Clock clock, + ObjectProvider metricsSender) { VirtualThreadTaskExecutor taskExecutor = new VirtualThreadTaskExecutor("otlp-meter-registry-"); - return new OtlpMeterRegistry(otlpConfig, clock, taskExecutor.getVirtualThreadFactory()); + Builder builder = OtlpMeterRegistry.builder(otlpConfig) + .clock(clock) + .threadFactory(taskExecutor.getVirtualThreadFactory()); + metricsSender.ifAvailable(builder::metricsSender); + return builder.build(); } /** diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java index 64a9d65aa7..986333fbdc 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java @@ -21,6 +21,7 @@ import java.util.concurrent.ScheduledExecutorService; import io.micrometer.core.instrument.Clock; import io.micrometer.registry.otlp.OtlpConfig; import io.micrometer.registry.otlp.OtlpMeterRegistry; +import io.micrometer.registry.otlp.OtlpMetricsSender; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.JRE; @@ -132,6 +133,32 @@ class OtlpMetricsExportAutoConfigurationTests { }); } + @Test + void allowsCustomMetricsSenderToBeUsed() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class, CustomMetricsSenderConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(OtlpMeterRegistry.class); + OtlpMeterRegistry registry = context.getBean(OtlpMeterRegistry.class); + assertThat(registry).extracting("metricsSender") + .satisfies((sender) -> assertThat(sender) + .isSameAs(CustomMetricsSenderConfiguration.customMetricsSender)); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void allowsCustomMetricsSenderToBeUsedWithVirtualThreads() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class, CustomMetricsSenderConfiguration.class) + .withPropertyValues("spring.threads.virtual.enabled=true") + .run((context) -> { + assertThat(context).hasSingleBean(OtlpMeterRegistry.class); + OtlpMeterRegistry registry = context.getBean(OtlpMeterRegistry.class); + assertThat(registry).extracting("metricsSender") + .satisfies((sender) -> assertThat(sender) + .isSameAs(CustomMetricsSenderConfiguration.customMetricsSender)); + }); + } + @Configuration(proxyBeanMethods = false) static class BaseConfiguration { @@ -174,4 +201,17 @@ class OtlpMetricsExportAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class CustomMetricsSenderConfiguration { + + static OtlpMetricsSender customMetricsSender = (request) -> { + }; + + @Bean + OtlpMetricsSender customMetricsSender() { + return customMetricsSender; + } + + } + } diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc index fb32b1ad1f..d8af87d4a8 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc @@ -522,6 +522,8 @@ management: Custom headers, for example for authentication, can also be provided using configprop:management.otlp.metrics.export.headers.*[] properties. +If an `OtlpMetricsSender` bean is available, it will be configured on the `OtlpMeterRegistry` that Spring Boot auto-configures. + [[actuator.metrics.export.prometheus]] From 15c54b4065f47a3722d71d19dced95384afac04c Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 15 Apr 2025 09:55:59 -0700 Subject: [PATCH 2/2] Polish 'Auto-configure OtlpMetricsSender if available' Update `OtlpMetricsExportAutoConfiguration` to use the new `OtlpMetricsSender`interface that allows users to customize the sending of OTLP metrics using the `OtlpMeterRegistry`. See gh-45204 --- .../OtlpMetricsExportAutoConfiguration.java | 20 +++++++------- ...lpMetricsExportAutoConfigurationTests.java | 26 ++++++++----------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java index 12ac2355b7..65a6cf19c1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp; import io.micrometer.core.instrument.Clock; import io.micrometer.registry.otlp.OtlpConfig; import io.micrometer.registry.otlp.OtlpMeterRegistry; -import io.micrometer.registry.otlp.OtlpMeterRegistry.Builder; import io.micrometer.registry.otlp.OtlpMetricsSender; import org.springframework.beans.factory.ObjectProvider; @@ -81,9 +80,7 @@ public class OtlpMetricsExportAutoConfiguration { @ConditionalOnThreading(Threading.PLATFORM) public OtlpMeterRegistry otlpMeterRegistry(OtlpConfig otlpConfig, Clock clock, ObjectProvider metricsSender) { - Builder builder = OtlpMeterRegistry.builder(otlpConfig).clock(clock); - metricsSender.ifAvailable(builder::metricsSender); - return builder.build(); + return builder(otlpConfig, clock, metricsSender).build(); } @Bean @@ -91,12 +88,15 @@ public class OtlpMetricsExportAutoConfiguration { @ConditionalOnThreading(Threading.VIRTUAL) public OtlpMeterRegistry otlpMeterRegistryVirtualThreads(OtlpConfig otlpConfig, Clock clock, ObjectProvider metricsSender) { - VirtualThreadTaskExecutor taskExecutor = new VirtualThreadTaskExecutor("otlp-meter-registry-"); - Builder builder = OtlpMeterRegistry.builder(otlpConfig) - .clock(clock) - .threadFactory(taskExecutor.getVirtualThreadFactory()); + VirtualThreadTaskExecutor executor = new VirtualThreadTaskExecutor("otlp-meter-registry-"); + return builder(otlpConfig, clock, metricsSender).threadFactory(executor.getVirtualThreadFactory()).build(); + } + + private OtlpMeterRegistry.Builder builder(OtlpConfig otlpConfig, Clock clock, + ObjectProvider metricsSender) { + OtlpMeterRegistry.Builder builder = OtlpMeterRegistry.builder(otlpConfig).clock(clock); metricsSender.ifAvailable(builder::metricsSender); - return builder.build(); + return builder; } /** diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java index 986333fbdc..0d29938ec4 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import org.junit.jupiter.api.condition.JRE; import org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsExportAutoConfiguration.PropertiesOtlpMetricsConnectionDetails; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.testsupport.assertj.ScheduledExecutorServiceAssert; import org.springframework.context.annotation.Bean; @@ -136,13 +137,7 @@ class OtlpMetricsExportAutoConfigurationTests { @Test void allowsCustomMetricsSenderToBeUsed() { this.contextRunner.withUserConfiguration(BaseConfiguration.class, CustomMetricsSenderConfiguration.class) - .run((context) -> { - assertThat(context).hasSingleBean(OtlpMeterRegistry.class); - OtlpMeterRegistry registry = context.getBean(OtlpMeterRegistry.class); - assertThat(registry).extracting("metricsSender") - .satisfies((sender) -> assertThat(sender) - .isSameAs(CustomMetricsSenderConfiguration.customMetricsSender)); - }); + .run(this::assertHasCustomMetricsSender); } @Test @@ -150,13 +145,14 @@ class OtlpMetricsExportAutoConfigurationTests { void allowsCustomMetricsSenderToBeUsedWithVirtualThreads() { this.contextRunner.withUserConfiguration(BaseConfiguration.class, CustomMetricsSenderConfiguration.class) .withPropertyValues("spring.threads.virtual.enabled=true") - .run((context) -> { - assertThat(context).hasSingleBean(OtlpMeterRegistry.class); - OtlpMeterRegistry registry = context.getBean(OtlpMeterRegistry.class); - assertThat(registry).extracting("metricsSender") - .satisfies((sender) -> assertThat(sender) - .isSameAs(CustomMetricsSenderConfiguration.customMetricsSender)); - }); + .run(this::assertHasCustomMetricsSender); + } + + private void assertHasCustomMetricsSender(AssertableApplicationContext context) { + assertThat(context).hasSingleBean(OtlpMeterRegistry.class); + OtlpMeterRegistry registry = context.getBean(OtlpMeterRegistry.class); + assertThat(registry).extracting("metricsSender") + .satisfies((sender) -> assertThat(sender).isSameAs(CustomMetricsSenderConfiguration.customMetricsSender)); } @Configuration(proxyBeanMethods = false)