diff --git a/agent/instrumentation/micrometer-1.0/src/main/java/io/opentelemetry/javaagent/instrumentation/micrometer/ai/ActuatorInstrumentation.java b/agent/instrumentation/micrometer-1.0/src/main/java/io/opentelemetry/javaagent/instrumentation/micrometer/ai/ActuatorInstrumentation.java index 5b3bc08c284..f5440e97ba5 100644 --- a/agent/instrumentation/micrometer-1.0/src/main/java/io/opentelemetry/javaagent/instrumentation/micrometer/ai/ActuatorInstrumentation.java +++ b/agent/instrumentation/micrometer-1.0/src/main/java/io/opentelemetry/javaagent/instrumentation/micrometer/ai/ActuatorInstrumentation.java @@ -16,6 +16,11 @@ public final class ActuatorInstrumentation implements TypeInstrumentation { + private static final String SPRING_BOOT_3_METRICS_AUTO_CONFIGURATION = + "org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration"; + private static final String SPRING_BOOT_4_METRICS_AUTO_CONFIGURATION = + "org.springframework.boot.micrometer.metrics.autoconfigure.MetricsAutoConfiguration"; + @Override public ElementMatcher typeMatcher() { return named("org.springframework.boot.autoconfigure.AutoConfigurationImportSelector"); @@ -36,8 +41,9 @@ public static class GetCandidateConfigurationsAdvice { @Advice.OnMethodExit(suppress = Throwable.class) public static void onExit(@Advice.Return(readOnly = false) List configurations) { - if (configurations.contains( - "org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration")) { + if ((configurations.contains(SPRING_BOOT_3_METRICS_AUTO_CONFIGURATION) + || configurations.contains(SPRING_BOOT_4_METRICS_AUTO_CONFIGURATION)) + && !configurations.contains(AzureMonitorAutoConfiguration.class.getName())) { List configs = new ArrayList<>(configurations.size() + 1); configs.addAll(configurations); // using class reference here so that muzzle will consider it a dependency of this advice diff --git a/agent/instrumentation/micrometer-1.0/src/main/java/io/opentelemetry/javaagent/instrumentation/micrometer/ai/AzureMonitorAutoConfiguration.java b/agent/instrumentation/micrometer-1.0/src/main/java/io/opentelemetry/javaagent/instrumentation/micrometer/ai/AzureMonitorAutoConfiguration.java index 221d5c2d88b..a0b9b23cb24 100644 --- a/agent/instrumentation/micrometer-1.0/src/main/java/io/opentelemetry/javaagent/instrumentation/micrometer/ai/AzureMonitorAutoConfiguration.java +++ b/agent/instrumentation/micrometer-1.0/src/main/java/io/opentelemetry/javaagent/instrumentation/micrometer/ai/AzureMonitorAutoConfiguration.java @@ -4,9 +4,6 @@ package io.opentelemetry.javaagent.instrumentation.micrometer.ai; import io.micrometer.core.instrument.Clock; -import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -15,12 +12,22 @@ import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) -@AutoConfigureBefore(CompositeMeterRegistryAutoConfiguration.class) +@AutoConfigureBefore( + name = { + "org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration", + "org.springframework.boot.micrometer.metrics.autoconfigure.CompositeMeterRegistryAutoConfiguration" + }) // configure after SimpleMeterRegistry is registered, otherwise SimpleMeterRegistry will be // suppressed by the existence of the MeterRegistry created here, which can alter the spring boot // actuator scraping endpoint behavior (since AzureMonitorMeterRegistry is a delta // StepMeterRegistry, while SimpleMeterRegistry is cumulative) -@AutoConfigureAfter({MetricsAutoConfiguration.class, SimpleMetricsExportAutoConfiguration.class}) +@AutoConfigureAfter( + name = { + "org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration", + "org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration", + "org.springframework.boot.micrometer.metrics.autoconfigure.MetricsAutoConfiguration", + "org.springframework.boot.micrometer.metrics.autoconfigure.export.simple.SimpleMetricsExportAutoConfiguration" + }) @ConditionalOnBean(Clock.class) @ConditionalOnClass(AzureMonitorMeterRegistry.class) public class AzureMonitorAutoConfiguration { diff --git a/settings.gradle.kts b/settings.gradle.kts index 09922f86795..b12ed3d9dce 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -59,6 +59,7 @@ include(":smoke-tests:framework") // TODO (trask) consider un-hiding these and running smoke tests against the latest versions hideFromDependabot(":smoke-tests:apps:ActuatorMetrics") +hideFromDependabot(":smoke-tests:apps:ActuatorMetricsSpringBoot4") hideFromDependabot(":smoke-tests:apps:AutoPerfCounters") hideFromDependabot(":smoke-tests:apps:AzureSdk") hideFromDependabot(":smoke-tests:apps:AzureFunctions") diff --git a/smoke-tests/apps/ActuatorMetricsSpringBoot4/build.gradle.kts b/smoke-tests/apps/ActuatorMetricsSpringBoot4/build.gradle.kts new file mode 100644 index 00000000000..c7bced36838 --- /dev/null +++ b/smoke-tests/apps/ActuatorMetricsSpringBoot4/build.gradle.kts @@ -0,0 +1,24 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +plugins { + id("ai.smoke-test-jar") +} + +configurations.configureEach { + resolutionStrategy.force( + "ch.qos.logback:logback-classic:1.5.21", + "ch.qos.logback:logback-core:1.5.21", + "org.slf4j:slf4j-api:2.0.17" + ) +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-web:4.0.0") + implementation("org.springframework.boot:spring-boot-starter-actuator:4.0.0") + implementation("org.springframework.boot:spring-boot-starter-micrometer-metrics:4.0.0") +} + +tasks.named("shadowJar") { + append("META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports") + append("META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports") +} diff --git a/smoke-tests/apps/ActuatorMetricsSpringBoot4/src/main/java/com/microsoft/applicationinsights/smoketestapp/SpringBootApp.java b/smoke-tests/apps/ActuatorMetricsSpringBoot4/src/main/java/com/microsoft/applicationinsights/smoketestapp/SpringBootApp.java new file mode 100644 index 00000000000..a4ff0769f9c --- /dev/null +++ b/smoke-tests/apps/ActuatorMetricsSpringBoot4/src/main/java/com/microsoft/applicationinsights/smoketestapp/SpringBootApp.java @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.applicationinsights.smoketestapp; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Metrics; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory; +import org.springframework.boot.web.server.servlet.ServletWebServerFactory; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +public class SpringBootApp { + + @Bean + public ServletWebServerFactory servletWebServerFactory() { + return new TomcatServletWebServerFactory(); + } + + @Bean + public MeterRegistry meterRegistry() { + return Metrics.globalRegistry; + } + + public static void main(String[] args) { + SpringApplication.run(SpringBootApp.class, args); + } +} diff --git a/smoke-tests/apps/ActuatorMetricsSpringBoot4/src/main/java/com/microsoft/applicationinsights/smoketestapp/TestController.java b/smoke-tests/apps/ActuatorMetricsSpringBoot4/src/main/java/com/microsoft/applicationinsights/smoketestapp/TestController.java new file mode 100644 index 00000000000..ad4a88c9d3b --- /dev/null +++ b/smoke-tests/apps/ActuatorMetricsSpringBoot4/src/main/java/com/microsoft/applicationinsights/smoketestapp/TestController.java @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.applicationinsights.smoketestapp; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestController { + + private final Counter counter; + + public TestController(MeterRegistry meterRegistry) { + this.counter = + Counter.builder("demo.requests.total").tag("endpoint", "test").register(meterRegistry); + } + + @GetMapping("/") + public String root() { + return "OK"; + } + + @GetMapping("/test") + public String test() { + counter.increment(); + return "OK!"; + } +} diff --git a/smoke-tests/apps/ActuatorMetricsSpringBoot4/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/ActuatorMetricsSpringBoot4Test.java b/smoke-tests/apps/ActuatorMetricsSpringBoot4/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/ActuatorMetricsSpringBoot4Test.java new file mode 100644 index 00000000000..08ba8b9d492 --- /dev/null +++ b/smoke-tests/apps/ActuatorMetricsSpringBoot4/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/ActuatorMetricsSpringBoot4Test.java @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.applicationinsights.smoketest; + +import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.JAVA_21; +import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.JAVA_21_OPENJ9; +import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.JAVA_25; +import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.JAVA_25_OPENJ9; +import static org.assertj.core.api.Assertions.assertThat; + +import com.microsoft.applicationinsights.smoketest.schemav2.Data; +import com.microsoft.applicationinsights.smoketest.schemav2.DataPoint; +import com.microsoft.applicationinsights.smoketest.schemav2.Envelope; +import com.microsoft.applicationinsights.smoketest.schemav2.MetricData; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +@UseAgent +abstract class ActuatorMetricsSpringBoot4Test { + + @RegisterExtension static final SmokeTestExtension testing = SmokeTestExtension.create(); + + @Test + @TargetUri("/test") + void shouldCaptureCustomMetricRegisteredOnSpringMeterRegistry() throws Exception { + testing.getTelemetry(0); + + List metricItems = + testing.mockedIngestion.waitForItems( + ActuatorMetricsSpringBoot4Test::isTargetMetric, 1, 20, TimeUnit.SECONDS); + + MetricData data = (MetricData) ((Data) metricItems.get(0).getData()).getBaseData(); + List points = data.getMetrics(); + + assertThat(points) + .anySatisfy(point -> assertThat(point.getName()).isEqualTo("demo_requests_total")); + assertThat(data.getProperties()).containsEntry("endpoint", "test"); + } + + static boolean isTargetMetric(Envelope input) { + if (!input.getData().getBaseType().equals("MetricData")) { + return false; + } + MetricData data = (MetricData) ((Data) input.getData()).getBaseData(); + if (!"test".equals(data.getProperties().get("endpoint"))) { + return false; + } + for (DataPoint point : data.getMetrics()) { + if ("demo_requests_total".equals(point.getName()) && point.getValue() >= 1) { + return true; + } + } + return false; + } + + @Environment(JAVA_21) + static class Java21Test extends ActuatorMetricsSpringBoot4Test {} + + @Environment(JAVA_21_OPENJ9) + static class Java21OpenJ9Test extends ActuatorMetricsSpringBoot4Test {} + + @Environment(JAVA_25) + static class Java25Test extends ActuatorMetricsSpringBoot4Test {} + + @Environment(JAVA_25_OPENJ9) + static class Java25OpenJ9Test extends ActuatorMetricsSpringBoot4Test {} +} diff --git a/smoke-tests/apps/ActuatorMetricsSpringBoot4/src/smokeTest/resources/applicationinsights.json b/smoke-tests/apps/ActuatorMetricsSpringBoot4/src/smokeTest/resources/applicationinsights.json new file mode 100644 index 00000000000..e08473e0175 --- /dev/null +++ b/smoke-tests/apps/ActuatorMetricsSpringBoot4/src/smokeTest/resources/applicationinsights.json @@ -0,0 +1,10 @@ +{ + "role": { + "name": "testrolename", + "instance": "testroleinstance" + }, + "sampling": { + "percentage": 100 + }, + "metricIntervalSeconds": 5 +} diff --git a/smoke-tests/apps/ActuatorMetricsSpringBoot4/src/smokeTest/resources/logback-test.xml b/smoke-tests/apps/ActuatorMetricsSpringBoot4/src/smokeTest/resources/logback-test.xml new file mode 100644 index 00000000000..0cbbecd57ce --- /dev/null +++ b/smoke-tests/apps/ActuatorMetricsSpringBoot4/src/smokeTest/resources/logback-test.xml @@ -0,0 +1,11 @@ + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + diff --git a/smoke-tests/apps/Micrometer/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/MicrometerTest.java b/smoke-tests/apps/Micrometer/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/MicrometerTest.java index 64bd632edfe..a196f1cdb12 100644 --- a/smoke-tests/apps/Micrometer/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/MicrometerTest.java +++ b/smoke-tests/apps/Micrometer/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/MicrometerTest.java @@ -9,6 +9,8 @@ import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.JAVA_17_OPENJ9; import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.JAVA_21; import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.JAVA_21_OPENJ9; +import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.JAVA_25; +import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.JAVA_25_OPENJ9; import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.JAVA_8; import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.JAVA_8_OPENJ9; import static org.assertj.core.api.Assertions.assertThat; @@ -99,4 +101,10 @@ static class Java21Test extends MicrometerTest {} @Environment(JAVA_21_OPENJ9) static class Java21OpenJ9Test extends MicrometerTest {} + + @Environment(JAVA_25) + static class Java25Test extends MicrometerTest {} + + @Environment(JAVA_25_OPENJ9) + static class Java25OpenJ9Test extends MicrometerTest {} }