From 1cdd19297c6ec1cf8005adfc12436482a1790946 Mon Sep 17 00:00:00 2001 From: Illia Sorokoumov Date: Tue, 12 Mar 2024 22:32:24 +0100 Subject: [PATCH 1/3] Add ObservationApplier abstraction --- micrometer-core/build.gradle | 3 + .../ObservationToSuspendingMethodApplier.kt | 60 +++++++++++++++++++ .../observation/aop/ObservedAspect.java | 41 ++++++------- .../aop/applier/ObservationApplier.java | 18 ++++++ .../applier/ObservationApplierRegistry.java | 42 +++++++++++++ .../ObservationToCompletionStageApplier.java | 43 +++++++++++++ 6 files changed, 183 insertions(+), 24 deletions(-) create mode 100644 micrometer-core/src/main/kotlin/io/micrometer/core/aop/kotlin/ObservationToSuspendingMethodApplier.kt create mode 100644 micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplier.java create mode 100644 micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplierRegistry.java create mode 100644 micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationToCompletionStageApplier.java diff --git a/micrometer-core/build.gradle b/micrometer-core/build.gradle index fa70508afd..ddd2ab787e 100644 --- a/micrometer-core/build.gradle +++ b/micrometer-core/build.gradle @@ -135,6 +135,9 @@ dependencies { optionalApi 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' optionalApi 'org.jetbrains.kotlinx:kotlinx-coroutines-core' + // reactor + optionalApi 'io.projectreactor:reactor-core' + testImplementation 'io.projectreactor:reactor-test' testImplementation project(":micrometer-observation-test") java11TestImplementation project(":micrometer-observation-test") diff --git a/micrometer-core/src/main/kotlin/io/micrometer/core/aop/kotlin/ObservationToSuspendingMethodApplier.kt b/micrometer-core/src/main/kotlin/io/micrometer/core/aop/kotlin/ObservationToSuspendingMethodApplier.kt new file mode 100644 index 0000000000..22fa71055b --- /dev/null +++ b/micrometer-core/src/main/kotlin/io/micrometer/core/aop/kotlin/ObservationToSuspendingMethodApplier.kt @@ -0,0 +1,60 @@ +package io.micrometer.core.aop.kotlin + +import io.micrometer.core.instrument.kotlin.asContextElement +import io.micrometer.observation.Observation +import io.micrometer.observation.aop.applier.ObservationApplier +import org.aspectj.lang.ProceedingJoinPoint +import reactor.core.publisher.Mono +import java.lang.reflect.Method +import java.util.Arrays +import kotlin.coroutines.Continuation +import kotlin.coroutines.CoroutineContext + +object ObservationToSuspendingMethodApplier : ObservationApplier { + /** + * Using such a simplistic implementation of [kotlin.coroutines.Continuation] interface is only enough, + * because of how AopUtils#invokeJoinpointUsingReflection() in Spring Framework is implemented. + * This means that this whole implementation is only usable in conjunction with Spring Framework. + * @see AopUtils.java + * @see CoroutinesUtils.java + */ + private class DelegatingContinuation( + override val context: CoroutineContext, + private val underlying: Continuation + ) : Continuation { + override fun resumeWith(result: Result) { + underlying.resumeWith(result) + } + } + + override fun isApplicable(pjp: ProceedingJoinPoint, method: Method): Boolean { + return method.parameterTypes.isNotEmpty() + && Continuation::class.java.isAssignableFrom(method.parameterTypes.last()) + } + + override fun applyAndProceed(pjp: ProceedingJoinPoint, method: Method, observation: Observation): Any { + observation.start() + return try { + val continuation = pjp.args.last() as Continuation<*> + val coroutineContext = continuation.context + + val newCoroutineContext = coroutineContext + observation.openScope().use { + observation.observationRegistry.asContextElement() + } + + val newArgs = Arrays.copyOf(pjp.args, pjp.args.size) + newArgs[newArgs.size - 1] = DelegatingContinuation(newCoroutineContext, continuation) + + @Suppress("ReactiveStreamsUnusedPublisher") + (pjp.proceed(newArgs) as Mono<*>).doOnError { error -> + observation.error(error) + }.doOnTerminate { + observation.stop() + } + } catch (error: Throwable) { + observation.error(error) + observation.stop() + throw error + } + } +} 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 8976077d67..e5261dcc84 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 @@ -21,12 +21,15 @@ import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.annotation.Observed; import io.micrometer.observation.ObservationConvention; +import io.micrometer.observation.aop.applier.ObservationApplier; +import io.micrometer.observation.aop.applier.ObservationApplierRegistry; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import java.lang.reflect.Method; +import java.util.Optional; import java.util.concurrent.CompletionStage; import java.util.function.Predicate; @@ -84,6 +87,8 @@ public class ObservedAspect { private final Predicate shouldSkip; + private final ObservationApplierRegistry observationApplierRegistry; + public ObservedAspect(ObservationRegistry registry) { this(registry, null, DONT_SKIP_ANYTHING); } @@ -100,9 +105,17 @@ public ObservedAspect(ObservationRegistry registry, Predicate observationConvention, Predicate shouldSkip) { + this(registry, observationConvention, shouldSkip, ObservationApplierRegistry.getInstance()); + } + + public ObservedAspect(ObservationRegistry registry, + @Nullable ObservationConvention observationConvention, + Predicate shouldSkip, + ObservationApplierRegistry observationApplierRegistry) { this.registry = registry; this.observationConvention = observationConvention; this.shouldSkip = shouldSkip; + this.observationApplierRegistry = observationApplierRegistry; } @Around("@within(io.micrometer.observation.annotation.Observed) and not @annotation(io.micrometer.observation.annotation.Observed)") @@ -132,22 +145,10 @@ public Object observeMethod(ProceedingJoinPoint pjp) throws Throwable { private Object observe(ProceedingJoinPoint pjp, Method method, Observed observed) throws Throwable { Observation observation = ObservedAspectObservationDocumentation.of(pjp, observed, this.registry, this.observationConvention); - if (CompletionStage.class.isAssignableFrom(method.getReturnType())) { - observation.start(); - Observation.Scope scope = observation.openScope(); - try { - return ((CompletionStage) pjp.proceed()) - .whenComplete((result, error) -> stopObservation(observation, scope, error)); - } - catch (Throwable error) { - stopObservation(observation, scope, error); - throw error; - } - finally { - scope.close(); - } - } - else { + Optional observationApplier = observationApplierRegistry.findApplicable(pjp, method); + if (observationApplier.isPresent()) { + return observationApplier.get().applyAndProceed(pjp, method, observation); + } else { return observation.observeChecked(() -> pjp.proceed()); } } @@ -171,14 +172,6 @@ private Method getMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException { return method; } - private void stopObservation(Observation observation, Observation.Scope scope, @Nullable Throwable error) { - if (error != null) { - observation.error(error); - } - scope.close(); - observation.stop(); - } - public static class ObservedAspectContext extends Observation.Context { private final ProceedingJoinPoint proceedingJoinPoint; diff --git a/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplier.java b/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplier.java new file mode 100644 index 0000000000..9ec76dd11f --- /dev/null +++ b/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplier.java @@ -0,0 +1,18 @@ +package io.micrometer.observation.aop.applier; + +import io.micrometer.common.lang.NonNull; +import io.micrometer.observation.Observation; +import org.aspectj.lang.ProceedingJoinPoint; + +import java.lang.reflect.Method; + +public interface ObservationApplier { + + boolean isApplicable(@NonNull ProceedingJoinPoint pjp, @NonNull Method method); + + Object applyAndProceed( + @NonNull ProceedingJoinPoint pjp, + @NonNull Method method, + @NonNull Observation observation + ) throws Throwable; +} diff --git a/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplierRegistry.java b/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplierRegistry.java new file mode 100644 index 0000000000..978b8fa654 --- /dev/null +++ b/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplierRegistry.java @@ -0,0 +1,42 @@ +package io.micrometer.observation.aop.applier; + +import org.aspectj.lang.ProceedingJoinPoint; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; + +public class ObservationApplierRegistry { + + private static ObservationApplierRegistry instance; + + private final List observationAppliers; + + public ObservationApplierRegistry(List observationAppliers) { + this.observationAppliers = new CopyOnWriteArrayList<>(observationAppliers); + } + + public Optional findApplicable(ProceedingJoinPoint pjp, Method method) { + for (ObservationApplier observationApplier : observationAppliers) { + if (observationApplier.isApplicable(pjp, method)) { + return Optional.of(observationApplier); + } + } + return Optional.empty(); + } + + public void register(ObservationApplier observationApplier) { + this.observationAppliers.add(observationApplier); + } + + public static ObservationApplierRegistry getInstance() { + if (instance == null) { + instance = new ObservationApplierRegistry( + Collections.singletonList(new ObservationToCompletionStageApplier()) + ); + } + return instance; + } +} diff --git a/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationToCompletionStageApplier.java b/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationToCompletionStageApplier.java new file mode 100644 index 0000000000..04fb334732 --- /dev/null +++ b/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationToCompletionStageApplier.java @@ -0,0 +1,43 @@ +package io.micrometer.observation.aop.applier; + +import io.micrometer.common.lang.NonNull; +import io.micrometer.common.lang.Nullable; +import io.micrometer.observation.Observation; +import org.aspectj.lang.ProceedingJoinPoint; + +import java.lang.reflect.Method; +import java.util.concurrent.CompletionStage; + +public class ObservationToCompletionStageApplier implements ObservationApplier { + @Override + public boolean isApplicable(@NonNull ProceedingJoinPoint pjp, @NonNull Method method) { + return CompletionStage.class.isAssignableFrom(method.getReturnType()); + } + + @Override + public Object applyAndProceed( + @NonNull ProceedingJoinPoint pjp, + @NonNull Method method, + @NonNull Observation observation + ) throws Throwable { + observation.start(); + Observation.Scope scope = observation.openScope(); + try { + return ((CompletionStage) pjp.proceed()) + .whenComplete((result, error) -> stopObservation(observation, scope, error)); + } catch (Throwable error) { + stopObservation(observation, scope, error); + throw error; + } finally { + scope.close(); + } + } + + private void stopObservation(Observation observation, Observation.Scope scope, @Nullable Throwable error) { + if (error != null) { + observation.error(error); + } + scope.close(); + observation.stop(); + } +} From 38f1b8bb95ffcb41385ef49f17a90ff7d8d55c7a Mon Sep 17 00:00:00 2001 From: Illia Sorokoumov Date: Tue, 12 Mar 2024 22:44:25 +0100 Subject: [PATCH 2/3] Add copyrights to files --- .../ObservationToSuspendingMethodApplier.kt | 15 +++++++++++++++ .../aop/applier/ObservationApplier.java | 15 +++++++++++++++ .../aop/applier/ObservationApplierRegistry.java | 15 +++++++++++++++ .../ObservationToCompletionStageApplier.java | 15 +++++++++++++++ 4 files changed, 60 insertions(+) diff --git a/micrometer-core/src/main/kotlin/io/micrometer/core/aop/kotlin/ObservationToSuspendingMethodApplier.kt b/micrometer-core/src/main/kotlin/io/micrometer/core/aop/kotlin/ObservationToSuspendingMethodApplier.kt index 22fa71055b..bdfba8b0e2 100644 --- a/micrometer-core/src/main/kotlin/io/micrometer/core/aop/kotlin/ObservationToSuspendingMethodApplier.kt +++ b/micrometer-core/src/main/kotlin/io/micrometer/core/aop/kotlin/ObservationToSuspendingMethodApplier.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2024 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. + * 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.core.aop.kotlin import io.micrometer.core.instrument.kotlin.asContextElement diff --git a/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplier.java b/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplier.java index 9ec76dd11f..ed74423a78 100644 --- a/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplier.java +++ b/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplier.java @@ -1,3 +1,18 @@ +/* + * Copyright 2024 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. + * 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.observation.aop.applier; import io.micrometer.common.lang.NonNull; diff --git a/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplierRegistry.java b/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplierRegistry.java index 978b8fa654..a0b96b5476 100644 --- a/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplierRegistry.java +++ b/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplierRegistry.java @@ -1,3 +1,18 @@ +/* + * Copyright 2024 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. + * 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.observation.aop.applier; import org.aspectj.lang.ProceedingJoinPoint; diff --git a/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationToCompletionStageApplier.java b/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationToCompletionStageApplier.java index 04fb334732..9a32b8eca5 100644 --- a/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationToCompletionStageApplier.java +++ b/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationToCompletionStageApplier.java @@ -1,3 +1,18 @@ +/* + * Copyright 2024 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. + * 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.observation.aop.applier; import io.micrometer.common.lang.NonNull; From 783b7a4b8a62c7fee10617c927e9131eaa4cc601 Mon Sep 17 00:00:00 2001 From: Illia Sorokoumov Date: Wed, 13 Mar 2024 07:10:51 +0100 Subject: [PATCH 3/3] Fix formatting --- .../observation/aop/ObservedAspect.java | 6 +++--- .../aop/applier/ObservationApplier.java | 8 +++----- .../aop/applier/ObservationApplierRegistry.java | 4 ++-- .../ObservationToCompletionStageApplier.java | 15 ++++++++------- 4 files changed, 16 insertions(+), 17 deletions(-) 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 e5261dcc84..06466c40f1 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 @@ -110,8 +110,7 @@ public ObservedAspect(ObservationRegistry registry, public ObservedAspect(ObservationRegistry registry, @Nullable ObservationConvention observationConvention, - Predicate shouldSkip, - ObservationApplierRegistry observationApplierRegistry) { + Predicate shouldSkip, ObservationApplierRegistry observationApplierRegistry) { this.registry = registry; this.observationConvention = observationConvention; this.shouldSkip = shouldSkip; @@ -148,7 +147,8 @@ private Object observe(ProceedingJoinPoint pjp, Method method, Observed observed Optional observationApplier = observationApplierRegistry.findApplicable(pjp, method); if (observationApplier.isPresent()) { return observationApplier.get().applyAndProceed(pjp, method, observation); - } else { + } + else { return observation.observeChecked(() -> pjp.proceed()); } } diff --git a/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplier.java b/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplier.java index ed74423a78..0daf532929 100644 --- a/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplier.java +++ b/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplier.java @@ -25,9 +25,7 @@ public interface ObservationApplier { boolean isApplicable(@NonNull ProceedingJoinPoint pjp, @NonNull Method method); - Object applyAndProceed( - @NonNull ProceedingJoinPoint pjp, - @NonNull Method method, - @NonNull Observation observation - ) throws Throwable; + Object applyAndProceed(@NonNull ProceedingJoinPoint pjp, @NonNull Method method, @NonNull Observation observation) + throws Throwable; + } diff --git a/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplierRegistry.java b/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplierRegistry.java index a0b96b5476..a512189b64 100644 --- a/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplierRegistry.java +++ b/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationApplierRegistry.java @@ -49,9 +49,9 @@ public void register(ObservationApplier observationApplier) { public static ObservationApplierRegistry getInstance() { if (instance == null) { instance = new ObservationApplierRegistry( - Collections.singletonList(new ObservationToCompletionStageApplier()) - ); + Collections.singletonList(new ObservationToCompletionStageApplier())); } return instance; } + } diff --git a/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationToCompletionStageApplier.java b/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationToCompletionStageApplier.java index 9a32b8eca5..4877de7c02 100644 --- a/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationToCompletionStageApplier.java +++ b/micrometer-observation/src/main/java/io/micrometer/observation/aop/applier/ObservationToCompletionStageApplier.java @@ -24,26 +24,26 @@ import java.util.concurrent.CompletionStage; public class ObservationToCompletionStageApplier implements ObservationApplier { + @Override public boolean isApplicable(@NonNull ProceedingJoinPoint pjp, @NonNull Method method) { return CompletionStage.class.isAssignableFrom(method.getReturnType()); } @Override - public Object applyAndProceed( - @NonNull ProceedingJoinPoint pjp, - @NonNull Method method, - @NonNull Observation observation - ) throws Throwable { + public Object applyAndProceed(@NonNull ProceedingJoinPoint pjp, @NonNull Method method, + @NonNull Observation observation) throws Throwable { observation.start(); Observation.Scope scope = observation.openScope(); try { return ((CompletionStage) pjp.proceed()) .whenComplete((result, error) -> stopObservation(observation, scope, error)); - } catch (Throwable error) { + } + catch (Throwable error) { stopObservation(observation, scope, error); throw error; - } finally { + } + finally { scope.close(); } } @@ -55,4 +55,5 @@ private void stopObservation(Observation observation, Observation.Scope scope, @ scope.close(); observation.stop(); } + }