diff --git a/build.gradle b/build.gradle index 983d70e0af..096e15bb70 100644 --- a/build.gradle +++ b/build.gradle @@ -337,7 +337,7 @@ subprojects { check.dependsOn("testModules") - if (!(project.name in ['micrometer-registry-prometheus', 'micrometer-registry-prometheus-simpleclient', 'micrometer-jakarta9', 'micrometer-java11', 'micrometer-jetty12', 'micrometer-test-ctw'])) { // add projects here that do not exist in the previous minor so should be excluded from japicmp + if (!(project.name in ['micrometer-registry-prometheus', 'micrometer-registry-prometheus-simpleclient', 'micrometer-jakarta9', 'micrometer-java11', 'micrometer-jetty12', 'micrometer-test-aspectj-ltw', 'micrometer-test-aspectj-ctw'])) { // add projects here that do not exist in the previous minor so should be excluded from japicmp apply plugin: 'me.champeau.gradle.japicmp' apply plugin: 'de.undercouch.download' diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f166ee5548..be619d2883 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -89,6 +89,7 @@ activemqArtemisJunit5 = { module = "org.apache.activemq:artemis-junit-5", versio applicationInsights = { module = "com.microsoft.azure:applicationinsights-core", version.ref = "application-insights" } archunitJunit5 = { module = "com.tngtech.archunit:archunit-junit5", version.ref = "archunit" } asmForPlugins = { module = "org.ow2.asm:asm", version.ref = "asmForPlugins" } +aspectjrt = { module = "org.aspectj:aspectjrt", version.ref = "aspectjweaver" } aspectjweaver = { module = "org.aspectj:aspectjweaver", version.ref = "aspectjweaver" } assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } awaitility = { module = "org.awaitility:awaitility", version.ref = "awaitility" } @@ -232,3 +233,4 @@ plugin-bnd = "biz.aQute.bnd:biz.aQute.bnd.gradle:6.4.0" kotlin19 = { id = "org.jetbrains.kotlin.jvm", version = "1.9.23" } kotlin17 = { id = "org.jetbrains.kotlin.jvm", version = "1.7.22" } jcstress = { id = "io.github.reyerizo.gradle.jcstress", version = "0.8.15" } +aspectj = { id = 'io.freefair.aspectj.post-compile-weaving', version = '8.6' } diff --git a/micrometer-core/build.gradle b/micrometer-core/build.gradle index 49a6580e52..b4ccfec351 100644 --- a/micrometer-core/build.gradle +++ b/micrometer-core/build.gradle @@ -1,7 +1,7 @@ plugins { alias(libs.plugins.kotlin19) + alias(libs.plugins.aspectj) id 'me.champeau.mrjar' version "0.1.1" - id 'io.freefair.aspectj.post-compile-weaving' version '8.6' } description 'Core module of Micrometer containing instrumentation API and implementation' @@ -82,8 +82,8 @@ dependencies { } // Aspects - implementation "org.aspectj:aspectjrt:${libs.versions.aspectjweaver.get()}" - java11Implementation "org.aspectj:aspectjrt:${libs.versions.aspectjweaver.get()}" + implementation libs.aspectjrt + java11Implementation libs.aspectjrt optionalApi 'org.aspectj:aspectjweaver' // instrumentation options diff --git a/micrometer-core/src/main/java/io/micrometer/core/aop/CountedAspect.java b/micrometer-core/src/main/java/io/micrometer/core/aop/CountedAspect.java index d0682e98eb..39d3f47c19 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/aop/CountedAspect.java +++ b/micrometer-core/src/main/java/io/micrometer/core/aop/CountedAspect.java @@ -167,7 +167,7 @@ public CountedAspect(MeterRegistry registry, Function delegate = new AtomicReference<>(ObservationRegistry.NOOP); + + DelegatingObservationRegistry(ObservationRegistry delegate) { + setDelegate(delegate); + } + + void setDelegate(ObservationRegistry delegate) { + this.delegate.set(Objects.requireNonNull(delegate, "Delegate must not be null")); + } + + @Nullable + @Override + public Observation getCurrentObservation() { + return delegate.get().getCurrentObservation(); + } + + @Nullable + @Override + public Observation.Scope getCurrentObservationScope() { + return delegate.get().getCurrentObservationScope(); + } + + @Override + public void setCurrentObservationScope(@Nullable Observation.Scope current) { + delegate.get().setCurrentObservationScope(current); + } + + @Override + public ObservationConfig observationConfig() { + return delegate.get().observationConfig(); + } + + @Override + public boolean isNoop() { + return delegate.get().isNoop(); + } + } } diff --git a/micrometer-observation/src/main/java/io/micrometer/observation/aop/ObservedAspect.java b/micrometer-observation/src/main/java/io/micrometer/observation/aop/ObservedAspect.java index bb6f7d72a3..18654aa0c7 100644 --- a/micrometer-observation/src/main/java/io/micrometer/observation/aop/ObservedAspect.java +++ b/micrometer-observation/src/main/java/io/micrometer/observation/aop/ObservedAspect.java @@ -111,7 +111,7 @@ public ObservedAspect(ObservationRegistry registry, this.shouldSkip = shouldSkip; } - @Around("@within(io.micrometer.observation.annotation.Observed) and not @annotation(io.micrometer.observation.annotation.Observed)") + @Around("@within(io.micrometer.observation.annotation.Observed) && !@annotation(io.micrometer.observation.annotation.Observed) && execution(* *.*(..))") @Nullable public Object observeClass(ProceedingJoinPoint pjp) throws Throwable { if (shouldSkip.test(pjp)) { diff --git a/micrometer-test-ctw/build.gradle b/micrometer-test-aspectj-ctw/build.gradle similarity index 62% rename from micrometer-test-ctw/build.gradle rename to micrometer-test-aspectj-ctw/build.gradle index 21c30280bb..62facd4f57 100644 --- a/micrometer-test-ctw/build.gradle +++ b/micrometer-test-aspectj-ctw/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'io.freefair.aspectj.post-compile-weaving' version '8.6' + alias(libs.plugins.aspectj) } description 'AspectJ compile-time weaving test for Micrometer aspects' @@ -10,8 +10,8 @@ dependencies { aspect project(':micrometer-core') aspect project(':micrometer-observation') - testImplementation 'org.junit.jupiter:junit-jupiter' - testImplementation 'org.assertj:assertj-core' + testImplementation libs.junitJupiter + testImplementation libs.assertj } test { diff --git a/micrometer-test-ctw/src/main/java/io/micrometer/test/ctw/MeasuredClass.java b/micrometer-test-aspectj-ctw/src/main/java/io/micrometer/test/ctw/MeasuredClass.java similarity index 84% rename from micrometer-test-ctw/src/main/java/io/micrometer/test/ctw/MeasuredClass.java rename to micrometer-test-aspectj-ctw/src/main/java/io/micrometer/test/ctw/MeasuredClass.java index 83ae3b80c0..aaf5fb0b29 100644 --- a/micrometer-test-ctw/src/main/java/io/micrometer/test/ctw/MeasuredClass.java +++ b/micrometer-test-aspectj-ctw/src/main/java/io/micrometer/test/ctw/MeasuredClass.java @@ -19,6 +19,9 @@ import io.micrometer.core.annotation.Timed; import io.micrometer.observation.annotation.Observed; +@Observed +@Counted +@Timed public class MeasuredClass { @Timed @@ -33,4 +36,13 @@ public void countedMethod() { public void observedMethod() { } + public void classLevelTimedMethod() { + } + + public void classLevelCountedMethod() { + } + + public void classLevelObservedMethod() { + } + } diff --git a/micrometer-test-ctw/src/test/java/io/micrometer/test/ctw/MeasuredClassTest.java b/micrometer-test-aspectj-ctw/src/test/java/io/micrometer/test/ctw/MeasuredClassTest.java similarity index 66% rename from micrometer-test-ctw/src/test/java/io/micrometer/test/ctw/MeasuredClassTest.java rename to micrometer-test-aspectj-ctw/src/test/java/io/micrometer/test/ctw/MeasuredClassTest.java index 1da773670e..4d069b0036 100644 --- a/micrometer-test-ctw/src/test/java/io/micrometer/test/ctw/MeasuredClassTest.java +++ b/micrometer-test-aspectj-ctw/src/test/java/io/micrometer/test/ctw/MeasuredClassTest.java @@ -111,4 +111,60 @@ void shouldWrapMethodWithObservedAspectThroughCTW() { then(timer.count()).isEqualTo(2); } + @Test + void shouldWrapMethodWithClassLevelTimedAspectThroughCTW() { + // when + measured.classLevelTimedMethod(); + // then + Collection timers = registry.find(TimedAspect.DEFAULT_METRIC_NAME) + .tag("class", MeasuredClass.class.getName()) + .tag("method", "classLevelTimedMethod") + .timers(); + then(timers).hasSize(1); + Timer timer = timers.iterator().next(); + then(timer.count()).isEqualTo(1); + + // when + measured.classLevelTimedMethod(); + // then + then(timer.count()).isEqualTo(2); + } + + @Test + void shouldWrapMethodWithClassLevelCountedAspectThroughCTW() { + // when + measured.classLevelCountedMethod(); + // then + Collection counters = registry.find("method.counted") + .tag("class", MeasuredClass.class.getName()) + .tag("method", "classLevelCountedMethod") + .counters(); + then(counters).hasSize(1); + Counter counter = counters.iterator().next(); + then(counter.count()).isEqualTo(1); + + // when + measured.classLevelCountedMethod(); + // then + then(counter.count()).isEqualTo(2); + } + + @Test + void shouldWrapMethodWithClassLevelObservedAspectThroughCTW() { + // when + measured.classLevelObservedMethod(); + // then + Collection timers = registry.find("method.observed") + .tag("class", MeasuredClass.class.getName()) + .tag("method", "classLevelObservedMethod") + .timers(); + then(timers).hasSize(1); + Timer timer = timers.iterator().next(); + then(timer.count()).isEqualTo(1); + + // when + measured.classLevelObservedMethod(); + // then + then(timer.count()).isEqualTo(2); + } } diff --git a/micrometer-test-ctw/src/test/resources/logback.xml b/micrometer-test-aspectj-ctw/src/test/resources/logback.xml similarity index 100% rename from micrometer-test-ctw/src/test/resources/logback.xml rename to micrometer-test-aspectj-ctw/src/test/resources/logback.xml diff --git a/micrometer-test-aspectj-ltw/build.gradle b/micrometer-test-aspectj-ltw/build.gradle new file mode 100644 index 0000000000..57ad0df217 --- /dev/null +++ b/micrometer-test-aspectj-ltw/build.gradle @@ -0,0 +1,23 @@ +plugins { + id 'java' +} + +description 'AspectJ load-time weaving test for Micrometer aspects' + +configurations { + agents +} + +dependencies { + agents libs.aspectjweaver + implementation project(':micrometer-core') + implementation project(':micrometer-observation') + + testImplementation libs.junitJupiter + testImplementation libs.assertj +} + +test { + useJUnitPlatform() + jvmArgs '-javaagent:' + configurations.agents.files.find { it.name.startsWith('aspectjweaver') } +} diff --git a/micrometer-test-aspectj-ltw/src/main/java/io/micrometer/test/ltw/MeasuredClass.java b/micrometer-test-aspectj-ltw/src/main/java/io/micrometer/test/ltw/MeasuredClass.java new file mode 100644 index 0000000000..28543dd215 --- /dev/null +++ b/micrometer-test-aspectj-ltw/src/main/java/io/micrometer/test/ltw/MeasuredClass.java @@ -0,0 +1,48 @@ +/* + * Copyright 2024 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.test.ltw; + +import io.micrometer.core.annotation.Counted; +import io.micrometer.core.annotation.Timed; +import io.micrometer.observation.annotation.Observed; + +@Observed +@Counted +@Timed +public class MeasuredClass { + + @Timed + public void timedMethod() { + } + + @Counted + public void countedMethod() { + } + + @Observed + public void observedMethod() { + } + + public void classLevelTimedMethod() { + } + + public void classLevelCountedMethod() { + } + + public void classLevelObservedMethod() { + } + +} diff --git a/micrometer-test-aspectj-ltw/src/main/resources/META-INF/aop.xml b/micrometer-test-aspectj-ltw/src/main/resources/META-INF/aop.xml new file mode 100644 index 0000000000..4b2666819d --- /dev/null +++ b/micrometer-test-aspectj-ltw/src/main/resources/META-INF/aop.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + diff --git a/micrometer-test-aspectj-ltw/src/test/java/io/micrometer/test/ltw/MeasuredClassTest.java b/micrometer-test-aspectj-ltw/src/test/java/io/micrometer/test/ltw/MeasuredClassTest.java new file mode 100644 index 0000000000..51f8ba2a45 --- /dev/null +++ b/micrometer-test-aspectj-ltw/src/test/java/io/micrometer/test/ltw/MeasuredClassTest.java @@ -0,0 +1,170 @@ +/* + * Copyright 2024 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.test.ltw; + +import io.micrometer.core.aop.TimedAspect; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.Observations; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Collection; + +import static org.assertj.core.api.BDDAssertions.then; + +class MeasuredClassTest { + + MeterRegistry registry = new SimpleMeterRegistry(); + + ObservationRegistry observationRegistry = ObservationRegistry.create(); + + MeasuredClass measured = new MeasuredClass(); + + @BeforeEach + void setUp() { + observationRegistry.observationConfig().observationHandler(new DefaultMeterObservationHandler(registry)); + // Global registry must be used because aspect gets created for us + Metrics.addRegistry(registry); + Observations.setRegistry(observationRegistry); + } + + @AfterEach + void cleanUp() { + Metrics.removeRegistry(registry); + Observations.resetRegistry(); + } + + @Test + void shouldWrapMethodWithTimedAspectThroughLTW() { + // when + measured.timedMethod(); + // then + Collection timers = registry.find(TimedAspect.DEFAULT_METRIC_NAME) + .tag("class", MeasuredClass.class.getName()) + .tag("method", "timedMethod") + .timers(); + then(timers).hasSize(1); + Timer timer = timers.iterator().next(); + then(timer.count()).isEqualTo(1); + + // when + measured.timedMethod(); + // then + then(timer.count()).isEqualTo(2); + } + + @Test + void shouldWrapMethodWithCountedAspectThroughLTW() { + // when + measured.countedMethod(); + // then + Collection counters = registry.find("method.counted") + .tag("class", MeasuredClass.class.getName()) + .tag("method", "countedMethod") + .counters(); + then(counters).hasSize(1); + Counter counter = counters.iterator().next(); + then(counter.count()).isEqualTo(1); + + // when + measured.countedMethod(); + // then + then(counter.count()).isEqualTo(2); + } + + @Test + void shouldWrapMethodWithObservedAspectThroughLTW() { + // when + measured.observedMethod(); + // then + Collection timers = registry.find("method.observed") + .tag("class", MeasuredClass.class.getName()) + .tag("method", "observedMethod") + .timers(); + then(timers).hasSize(1); + Timer timer = timers.iterator().next(); + then(timer.count()).isEqualTo(1); + + // when + measured.observedMethod(); + // then + then(timer.count()).isEqualTo(2); + } + + @Test + void shouldWrapMethodWithClassLevelTimedAspectThroughLTW() { + // when + measured.classLevelTimedMethod(); + // then + Collection timers = registry.find(TimedAspect.DEFAULT_METRIC_NAME) + .tag("class", MeasuredClass.class.getName()) + .tag("method", "classLevelTimedMethod") + .timers(); + then(timers).hasSize(1); + Timer timer = timers.iterator().next(); + then(timer.count()).isEqualTo(1); + + // when + measured.classLevelTimedMethod(); + // then + then(timer.count()).isEqualTo(2); + } + + @Test + void shouldWrapMethodWithClassLevelCountedAspectThroughLTW() { + // when + measured.classLevelCountedMethod(); + // then + Collection counters = registry.find("method.counted") + .tag("class", MeasuredClass.class.getName()) + .tag("method", "classLevelCountedMethod") + .counters(); + then(counters).hasSize(1); + Counter counter = counters.iterator().next(); + then(counter.count()).isEqualTo(1); + + // when + measured.classLevelCountedMethod(); + // then + then(counter.count()).isEqualTo(2); + } + + @Test + void shouldWrapMethodWithClassLevelObservedAspectThroughLTW() { + // when + measured.classLevelObservedMethod(); + // then + Collection timers = registry.find("method.observed") + .tag("class", MeasuredClass.class.getName()) + .tag("method", "classLevelObservedMethod") + .timers(); + then(timers).hasSize(1); + Timer timer = timers.iterator().next(); + then(timer.count()).isEqualTo(1); + + // when + measured.classLevelObservedMethod(); + // then + then(timer.count()).isEqualTo(2); + } +} diff --git a/micrometer-test-aspectj-ltw/src/test/resources/logback.xml b/micrometer-test-aspectj-ltw/src/test/resources/logback.xml new file mode 100644 index 0000000000..9240fdf2fb --- /dev/null +++ b/micrometer-test-aspectj-ltw/src/test/resources/logback.xml @@ -0,0 +1,31 @@ + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/settings.gradle b/settings.gradle index 7461f620ed..b6c01584a8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -31,7 +31,7 @@ include 'micrometer-commons', 'micrometer-core', 'micrometer-observation' project(":micrometer-samples-$sample").projectDir = new File(rootProject.projectDir, "samples/micrometer-samples-$sample") } -include 'micrometer-test', 'micrometer-observation-test', 'micrometer-test-ctw' +include 'micrometer-test', 'micrometer-observation-test', 'micrometer-test-aspectj-ltw', 'micrometer-test-aspectj-ctw' ['atlas', 'prometheus', 'prometheus-simpleclient', 'datadog', 'elastic', 'ganglia', 'graphite', 'health', 'jmx', 'influx', 'otlp', 'statsd', 'new-relic', 'cloudwatch2', 'signalfx', 'wavefront', 'dynatrace', 'azure-monitor', 'humio', 'appoptics', 'kairos', 'stackdriver', 'opentsdb'].each { sys -> include "micrometer-registry-$sys"