diff --git a/micrometer-commons/src/main/java/io/micrometer/common/annotation/AnnotatedParameter.java b/micrometer-commons/src/main/java/io/micrometer/common/annotation/AnnotatedObject.java similarity index 86% rename from micrometer-commons/src/main/java/io/micrometer/common/annotation/AnnotatedParameter.java rename to micrometer-commons/src/main/java/io/micrometer/common/annotation/AnnotatedObject.java index fa21bfd53a..2e1d202ecd 100644 --- a/micrometer-commons/src/main/java/io/micrometer/common/annotation/AnnotatedParameter.java +++ b/micrometer-commons/src/main/java/io/micrometer/common/annotation/AnnotatedObject.java @@ -25,15 +25,15 @@ * * @author Christian Schwerdtfeger */ -class AnnotatedParameter { +class AnnotatedObject { final Annotation annotation; - final Object argument; + final Object object; - AnnotatedParameter(Annotation annotation, Object argument) { + AnnotatedObject(Annotation annotation, Object object) { this.annotation = annotation; - this.argument = argument; + this.object = object; } } diff --git a/micrometer-commons/src/main/java/io/micrometer/common/annotation/AnnotationHandler.java b/micrometer-commons/src/main/java/io/micrometer/common/annotation/AnnotationHandler.java index 2a49b4c172..18b507ff08 100644 --- a/micrometer-commons/src/main/java/io/micrometer/common/annotation/AnnotationHandler.java +++ b/micrometer-commons/src/main/java/io/micrometer/common/annotation/AnnotationHandler.java @@ -26,6 +26,7 @@ import java.util.*; import java.util.function.BiConsumer; import java.util.function.BiFunction; +import java.util.function.Consumer; import java.util.function.Function; /** @@ -89,9 +90,9 @@ public void addAnnotatedParameters(T objectToModify, ProceedingJoinPoint pjp) { try { Method method = ((MethodSignature) pjp.getSignature()).getMethod(); method = tryToTakeMethodFromTargetClass(pjp, method); - List annotatedParameters = AnnotationUtils.findAnnotatedParameters(annotationClass, - method, pjp.getArgs()); - getAnnotationsFromInterfaces(pjp, method, annotatedParameters); + List annotatedParameters = AnnotationUtils.findAnnotatedParameters(annotationClass, method, + pjp.getArgs()); + getParametersAnnotationsFromInterfaces(pjp, method, annotatedParameters); addAnnotatedArguments(objectToModify, annotatedParameters); } catch (Exception ex) { @@ -99,6 +100,26 @@ public void addAnnotatedParameters(T objectToModify, ProceedingJoinPoint pjp) { } } + public void addAnnotatedMethodResult(T objectToModify, ProceedingJoinPoint pjp, Object result) { + try { + Method method = ((MethodSignature) pjp.getSignature()).getMethod(); + method = tryToTakeMethodFromTargetClass(pjp, method); + + List annotatedResult = new ArrayList<>(); + Arrays.stream(method.getAnnotationsByType(annotationClass)) + .map(annotation -> new AnnotatedObject(annotation, result)) + .forEach(annotatedResult::add); + getMethodAnnotationsFromInterfaces(pjp, method).stream() + .map(annotation -> new AnnotatedObject(annotation, result)) + .forEach(annotatedResult::add); + + addAnnotatedArguments(objectToModify, annotatedResult); + } + catch (Exception ex) { + log.error("Exception occurred while trying to add annotated method result", ex); + } + } + private static Method tryToTakeMethodFromTargetClass(ProceedingJoinPoint pjp, Method method) { try { return pjp.getTarget().getClass().getDeclaredMethod(method.getName(), method.getParameterTypes()); @@ -109,34 +130,48 @@ private static Method tryToTakeMethodFromTargetClass(ProceedingJoinPoint pjp, Me return method; } - private void getAnnotationsFromInterfaces(ProceedingJoinPoint pjp, Method mostSpecificMethod, - List annotatedParameters) { + private void getParametersAnnotationsFromInterfaces(ProceedingJoinPoint pjp, Method mostSpecificMethod, + List annotatedParameters) { + traverseInterfacesHierarchy(pjp, mostSpecificMethod, method -> { + List annotatedParametersForActualMethod = AnnotationUtils + .findAnnotatedParameters(annotationClass, method, pjp.getArgs()); + // annotations for a single parameter can be `duplicated` by the ones + // from parent interface, + // however later on during key-based deduplication the ones from + // specific method(target class) + // will take precedence + annotatedParameters.addAll(annotatedParametersForActualMethod); + }); + } + + private void traverseInterfacesHierarchy(ProceedingJoinPoint pjp, Method mostSpecificMethod, + Consumer consumer) { Class[] implementedInterfaces = pjp.getThis().getClass().getInterfaces(); for (Class implementedInterface : implementedInterfaces) { for (Method methodFromInterface : implementedInterface.getMethods()) { if (methodsAreTheSame(mostSpecificMethod, methodFromInterface)) { - List annotatedParametersForActualMethod = AnnotationUtils - .findAnnotatedParameters(annotationClass, methodFromInterface, pjp.getArgs()); - // annotations for a single parameter can be `duplicated` by the ones - // from parent interface, - // however later on during key-based deduplication the ones from - // specific method(target class) - // will take precedence - annotatedParameters.addAll(annotatedParametersForActualMethod); + consumer.accept(methodFromInterface); } } } } + private List getMethodAnnotationsFromInterfaces(ProceedingJoinPoint pjp, Method mostSpecificMethod) { + List allAnnotations = new ArrayList<>(); + traverseInterfacesHierarchy(pjp, mostSpecificMethod, + method -> allAnnotations.addAll(Arrays.asList(method.getAnnotationsByType(annotationClass)))); + return allAnnotations; + } + private boolean methodsAreTheSame(Method mostSpecificMethod, Method method) { return method.getName().equals(mostSpecificMethod.getName()) && Arrays.equals(method.getParameterTypes(), mostSpecificMethod.getParameterTypes()); } - private void addAnnotatedArguments(T objectToModify, List toBeAdded) { + private void addAnnotatedArguments(T objectToModify, List toBeAdded) { Set seen = new HashSet<>(); - for (AnnotatedParameter container : toBeAdded) { - KeyValue keyValue = toKeyValue.apply(container.annotation, container.argument); + for (AnnotatedObject container : toBeAdded) { + KeyValue keyValue = toKeyValue.apply(container.annotation, container.object); if (seen.add(keyValue.getKey())) { keyValueConsumer.accept(keyValue, objectToModify); } diff --git a/micrometer-commons/src/main/java/io/micrometer/common/annotation/AnnotationUtils.java b/micrometer-commons/src/main/java/io/micrometer/common/annotation/AnnotationUtils.java index f2a056241b..4969365dc2 100644 --- a/micrometer-commons/src/main/java/io/micrometer/common/annotation/AnnotationUtils.java +++ b/micrometer-commons/src/main/java/io/micrometer/common/annotation/AnnotationUtils.java @@ -35,14 +35,14 @@ private AnnotationUtils() { } - static List findAnnotatedParameters(Class annotationClazz, Method method, + static List findAnnotatedParameters(Class annotationClazz, Method method, Object[] args) { Parameter[] parameters = method.getParameters(); - List result = new ArrayList<>(); + List result = new ArrayList<>(); for (int i = 0; i < parameters.length; i++) { Parameter parameter = parameters[i]; for (Annotation annotation : parameter.getAnnotationsByType(annotationClazz)) { - result.add(new AnnotatedParameter(annotation, args[i])); + result.add(new AnnotatedObject(annotation, args[i])); } } return result; 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 94166eecf6..78fe2c4a5d 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 @@ -221,10 +221,10 @@ private Object perform(ProceedingJoinPoint pjp, Counted counted) throws Throwabl if (stopWhenCompleted) { try { return ((CompletionStage) pjp.proceed()) - .whenComplete((result, throwable) -> recordCompletionResult(pjp, counted, throwable)); + .whenComplete((result, throwable) -> recordCompletionResult(pjp, result, counted, throwable)); } catch (Throwable e) { - record(pjp, counted, e.getClass().getSimpleName(), RESULT_TAG_FAILURE_VALUE); + record(pjp, null, counted, e.getClass().getSimpleName(), RESULT_TAG_FAILURE_VALUE); throw e; } } @@ -232,38 +232,40 @@ private Object perform(ProceedingJoinPoint pjp, Counted counted) throws Throwabl try { Object result = pjp.proceed(); if (!counted.recordFailuresOnly()) { - record(pjp, counted, DEFAULT_EXCEPTION_TAG_VALUE, RESULT_TAG_SUCCESS_VALUE); + record(pjp, result, counted, DEFAULT_EXCEPTION_TAG_VALUE, RESULT_TAG_SUCCESS_VALUE); } return result; } catch (Throwable e) { - record(pjp, counted, e.getClass().getSimpleName(), RESULT_TAG_FAILURE_VALUE); + record(pjp, null, counted, e.getClass().getSimpleName(), RESULT_TAG_FAILURE_VALUE); throw e; } } - private void recordCompletionResult(ProceedingJoinPoint pjp, Counted counted, Throwable throwable) { + private void recordCompletionResult(ProceedingJoinPoint pjp, Object methodResult, Counted counted, + Throwable throwable) { if (throwable != null) { String exceptionTagValue = throwable.getCause() == null ? throwable.getClass().getSimpleName() : throwable.getCause().getClass().getSimpleName(); - record(pjp, counted, exceptionTagValue, RESULT_TAG_FAILURE_VALUE); + record(pjp, methodResult, counted, exceptionTagValue, RESULT_TAG_FAILURE_VALUE); } else if (!counted.recordFailuresOnly()) { - record(pjp, counted, DEFAULT_EXCEPTION_TAG_VALUE, RESULT_TAG_SUCCESS_VALUE); + record(pjp, methodResult, counted, DEFAULT_EXCEPTION_TAG_VALUE, RESULT_TAG_SUCCESS_VALUE); } } - private void record(ProceedingJoinPoint pjp, Counted counted, String exception, String result) { - counter(pjp, counted).tag(EXCEPTION_TAG, exception) + private void record(ProceedingJoinPoint pjp, Object methodResult, Counted counted, String exception, + String result) { + counter(pjp, methodResult, counted).tag(EXCEPTION_TAG, exception) .tag(RESULT_TAG, result) .tags(counted.extraTags()) .register(registry) .increment(); } - private Counter.Builder counter(ProceedingJoinPoint pjp, Counted counted) { + private Counter.Builder counter(ProceedingJoinPoint pjp, Object methodResult, Counted counted) { Counter.Builder builder = Counter.builder(counted.value()).tags(tagsBasedOnJoinPoint.apply(pjp)); String description = counted.description(); if (!description.isEmpty()) { @@ -271,6 +273,7 @@ private Counter.Builder counter(ProceedingJoinPoint pjp, Counted counted) { } if (meterTagAnnotationHandler != null) { meterTagAnnotationHandler.addAnnotatedParameters(builder, pjp); + meterTagAnnotationHandler.addAnnotatedMethodResult(builder, pjp, methodResult); } return builder; } diff --git a/micrometer-core/src/main/java/io/micrometer/core/aop/MeterTag.java b/micrometer-core/src/main/java/io/micrometer/core/aop/MeterTag.java index 07415baf65..f6243224a7 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/aop/MeterTag.java +++ b/micrometer-core/src/main/java/io/micrometer/core/aop/MeterTag.java @@ -37,7 +37,7 @@ */ @Retention(RetentionPolicy.RUNTIME) @Inherited -@Target(ElementType.PARAMETER) +@Target({ ElementType.PARAMETER, ElementType.METHOD }) @Repeatable(MeterTags.class) public @interface MeterTag { diff --git a/micrometer-core/src/main/java/io/micrometer/core/aop/MeterTags.java b/micrometer-core/src/main/java/io/micrometer/core/aop/MeterTags.java index 28a954333d..64f8708961 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/aop/MeterTags.java +++ b/micrometer-core/src/main/java/io/micrometer/core/aop/MeterTags.java @@ -37,7 +37,7 @@ */ @Retention(RetentionPolicy.RUNTIME) @Inherited -@Target(ElementType.PARAMETER) +@Target({ ElementType.METHOD, ElementType.PARAMETER }) @Documented public @interface MeterTags { diff --git a/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java b/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java index cb6712b983..80e06c76a1 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java +++ b/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java @@ -218,39 +218,41 @@ private Object processWithTimer(ProceedingJoinPoint pjp, Timed timed, String met if (stopWhenCompleted) { try { - return ((CompletionStage) pjp.proceed()).whenComplete( - (result, throwable) -> record(pjp, timed, metricName, sample, getExceptionTag(throwable))); + return ((CompletionStage) pjp.proceed()).whenComplete((result, throwable) -> record(pjp, result, + timed, metricName, sample, getExceptionTag(throwable))); } catch (Throwable e) { - record(pjp, timed, metricName, sample, e.getClass().getSimpleName()); + record(pjp, null, timed, metricName, sample, e.getClass().getSimpleName()); throw e; } } String exceptionClass = DEFAULT_EXCEPTION_TAG_VALUE; + Object result = null; try { - return pjp.proceed(); + result = pjp.proceed(); + return result; } catch (Throwable e) { exceptionClass = e.getClass().getSimpleName(); throw e; } finally { - record(pjp, timed, metricName, sample, exceptionClass); + record(pjp, result, timed, metricName, sample, exceptionClass); } } - private void record(ProceedingJoinPoint pjp, Timed timed, String metricName, Timer.Sample sample, - String exceptionClass) { + private void record(ProceedingJoinPoint pjp, Object methodResult, Timed timed, String metricName, + Timer.Sample sample, String exceptionClass) { try { - sample.stop(recordBuilder(pjp, timed, metricName, exceptionClass).register(registry)); + sample.stop(recordBuilder(pjp, methodResult, timed, metricName, exceptionClass).register(registry)); } catch (Exception e) { // ignoring on purpose } } - private Timer.Builder recordBuilder(ProceedingJoinPoint pjp, Timed timed, String metricName, + private Timer.Builder recordBuilder(ProceedingJoinPoint pjp, Object methodResult, Timed timed, String metricName, String exceptionClass) { Timer.Builder builder = Timer.builder(metricName) .description(timed.description().isEmpty() ? null : timed.description()) @@ -266,6 +268,7 @@ private Timer.Builder recordBuilder(ProceedingJoinPoint pjp, Timed timed, String if (meterTagAnnotationHandler != null) { meterTagAnnotationHandler.addAnnotatedParameters(builder, pjp); + meterTagAnnotationHandler.addAnnotatedMethodResult(builder, pjp, methodResult); } return builder; } diff --git a/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/CountedAspectTest.java b/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/CountedAspectTest.java index d41b84c12c..fe921d008c 100644 --- a/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/CountedAspectTest.java +++ b/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/CountedAspectTest.java @@ -26,6 +26,8 @@ import io.micrometer.core.instrument.search.MeterNotFoundException; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.aspectj.lang.ProceedingJoinPoint; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; @@ -370,7 +372,8 @@ String greet() { } - static class MeterTagsTests { + @Nested + class MeterTagsTests { ValueResolver valueResolver = parameter -> "Value from myCustomTagValueResolver [" + parameter + "]"; @@ -379,17 +382,21 @@ static class MeterTagsTests { CountedMeterTagAnnotationHandler meterTagAnnotationHandler = new CountedMeterTagAnnotationHandler( aClass -> valueResolver, aClass -> valueExpressionResolver); + MeterRegistry registry; + + CountedAspect countedAspect; + + @BeforeEach + void setup() { + registry = new SimpleMeterRegistry(); + countedAspect = new CountedAspect(registry); + countedAspect.setMeterTagAnnotationHandler(meterTagAnnotationHandler); + } + @ParameterizedTest @EnumSource(AnnotatedTestClass.class) void meterTagsWithText(AnnotatedTestClass annotatedClass) { - MeterRegistry registry = new SimpleMeterRegistry(); - CountedAspect countedAspect = new CountedAspect(registry); - countedAspect.setMeterTagAnnotationHandler(meterTagAnnotationHandler); - - AspectJProxyFactory pf = new AspectJProxyFactory(annotatedClass.newInstance()); - pf.addAspect(countedAspect); - - MeterTagClassInterface service = pf.getProxy(); + MeterTagClassInterface service = getProxyWithCountedAspect(annotatedClass.newInstance()); service.getAnnotationForArgumentToString(15L); @@ -399,14 +406,7 @@ void meterTagsWithText(AnnotatedTestClass annotatedClass) { @ParameterizedTest @EnumSource(AnnotatedTestClass.class) void meterTagsWithResolver(AnnotatedTestClass annotatedClass) { - MeterRegistry registry = new SimpleMeterRegistry(); - CountedAspect countedAspect = new CountedAspect(registry); - countedAspect.setMeterTagAnnotationHandler(meterTagAnnotationHandler); - - AspectJProxyFactory pf = new AspectJProxyFactory(annotatedClass.newInstance()); - pf.addAspect(countedAspect); - - MeterTagClassInterface service = pf.getProxy(); + MeterTagClassInterface service = getProxyWithCountedAspect(annotatedClass.newInstance()); service.getAnnotationForTagValueResolver("foo"); @@ -419,14 +419,7 @@ void meterTagsWithResolver(AnnotatedTestClass annotatedClass) { @ParameterizedTest @EnumSource(AnnotatedTestClass.class) void meterTagsWithExpression(AnnotatedTestClass annotatedClass) { - MeterRegistry registry = new SimpleMeterRegistry(); - CountedAspect countedAspect = new CountedAspect(registry); - countedAspect.setMeterTagAnnotationHandler(meterTagAnnotationHandler); - - AspectJProxyFactory pf = new AspectJProxyFactory(annotatedClass.newInstance()); - pf.addAspect(countedAspect); - - MeterTagClassInterface service = pf.getProxy(); + MeterTagClassInterface service = getProxyWithCountedAspect(annotatedClass.newInstance()); service.getAnnotationForTagValueExpression("15L"); @@ -435,10 +428,6 @@ void meterTagsWithExpression(AnnotatedTestClass annotatedClass) { @Test void meterTagOnPackagePrivateMethod() { - MeterRegistry registry = new SimpleMeterRegistry(); - CountedAspect countedAspect = new CountedAspect(registry); - countedAspect.setMeterTagAnnotationHandler(meterTagAnnotationHandler); - AspectJProxyFactory pf = new AspectJProxyFactory(new MeterTagClass()); pf.setProxyTargetClass(true); pf.addAspect(countedAspect); @@ -450,6 +439,58 @@ void meterTagOnPackagePrivateMethod() { assertThat(registry.get("method.counted").tag("foo", "bar").counter().count()).isEqualTo(1); } + @ParameterizedTest + @EnumSource(AnnotatedTestClass.class) + void meterTagsOnReturnValueWithText(AnnotatedTestClass annotatedClass) { + MeterTagClassInterface service = getProxyWithCountedAspect(annotatedClass.newInstance()); + + service.getAnnotationForArgumentToString(); + + assertThat(registry.get("method.counted").tag("test", "15").counter().count()).isEqualTo(1); + } + + @ParameterizedTest + @EnumSource(AnnotatedTestClass.class) + void meterTagsOnReturnValueWithResolver(AnnotatedTestClass annotatedClass) { + MeterTagClassInterface service = getProxyWithCountedAspect(annotatedClass.newInstance()); + + service.getAnnotationForTagValueResolver(); + + assertThat(registry.get("method.counted") + .tag("test", "Value from myCustomTagValueResolver [foo]") + .counter() + .count()).isEqualTo(1); + } + + @ParameterizedTest + @EnumSource(AnnotatedTestClass.class) + void meterTagsOnReturnValueWithExpression(AnnotatedTestClass annotatedClass) { + MeterTagClassInterface service = getProxyWithCountedAspect(annotatedClass.newInstance()); + + service.getAnnotationForTagValueExpression(); + + assertThat(registry.get("method.counted").tag("test", "hello characters").counter().count()).isEqualTo(1); + } + + @Test + void meterTagOnReturnValueOnPackagePrivateMethod() { + AspectJProxyFactory pf = new AspectJProxyFactory(new MeterTagClass()); + pf.setProxyTargetClass(true); + pf.addAspect(countedAspect); + + MeterTagClass service = pf.getProxy(); + + service.getAnnotationForPackagePrivateMethod(); + + assertThat(registry.get("method.counted").tag("foo", "bar").counter().count()).isEqualTo(1); + } + + private T getProxyWithCountedAspect(T object) { + AspectJProxyFactory pf = new AspectJProxyFactory(object); + pf.addAspect(countedAspect); + return pf.getProxy(); + } + enum AnnotatedTestClass { CLASS_WITHOUT_INTERFACE(MeterTagClass.class), CLASS_WITH_INTERFACE(MeterTagClassChild.class); @@ -477,13 +518,25 @@ interface MeterTagClassInterface { @Counted void getAnnotationForTagValueResolver(@MeterTag(key = "test", resolver = ValueResolver.class) String test); + @Counted + @MeterTag(key = "test", resolver = ValueResolver.class) + String getAnnotationForTagValueResolver(); + @Counted void getAnnotationForTagValueExpression( @MeterTag(key = "test", expression = "'hello' + ' characters'") String test); + @Counted + @MeterTag(key = "test", expression = "'hello' + ' characters'") + String getAnnotationForTagValueExpression(); + @Counted void getAnnotationForArgumentToString(@MeterTag("test") Long param); + @Counted + @MeterTag("test") + Long getAnnotationForArgumentToString(); + } static class MeterTagClass implements MeterTagClassInterface { @@ -494,21 +547,48 @@ public void getAnnotationForTagValueResolver( @MeterTag(key = "test", resolver = ValueResolver.class) String test) { } + @Counted + @MeterTag(key = "test", resolver = ValueResolver.class) + @Override + public String getAnnotationForTagValueResolver() { + return "foo"; + } + @Counted @Override public void getAnnotationForTagValueExpression( @MeterTag(key = "test", expression = "'hello' + ' characters'") String test) { } + @Counted + @MeterTag(key = "test", expression = "'hello' + ' characters'") + @Override + public String getAnnotationForTagValueExpression() { + return "15L"; + } + @Counted @Override public void getAnnotationForArgumentToString(@MeterTag("test") Long param) { } + @Counted + @MeterTag("test") + @Override + public Long getAnnotationForArgumentToString() { + return 15L; + } + @Counted void getAnnotationForPackagePrivateMethod(@MeterTag("foo") String foo) { } + @MeterTag("foo") + @Counted + String getAnnotationForPackagePrivateMethod() { + return "bar"; + } + } static class MeterTagClassChild implements MeterTagClassInterface { @@ -518,16 +598,34 @@ static class MeterTagClassChild implements MeterTagClassInterface { public void getAnnotationForTagValueResolver(String test) { } + @Counted + @Override + public String getAnnotationForTagValueResolver() { + return "foo"; + } + @Counted @Override public void getAnnotationForTagValueExpression(String test) { } + @Counted + @Override + public String getAnnotationForTagValueExpression() { + return "15L"; + } + @Counted @Override public void getAnnotationForArgumentToString(Long param) { } + @Counted + @Override + public Long getAnnotationForArgumentToString() { + return 15L; + } + } } diff --git a/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/TimedAspectTest.java b/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/TimedAspectTest.java index 343a5012a7..a36178f248 100644 --- a/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/TimedAspectTest.java +++ b/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/TimedAspectTest.java @@ -31,6 +31,7 @@ import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.micrometer.core.instrument.util.TimeUtils; import org.aspectj.lang.ProceedingJoinPoint; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -454,17 +455,21 @@ class MeterTagsTests { MeterTagAnnotationHandler meterTagAnnotationHandler = new MeterTagAnnotationHandler(aClass -> valueResolver, aClass -> valueExpressionResolver); + MeterRegistry registry; + + TimedAspect timedAspect; + + @BeforeEach + void setup() { + registry = new SimpleMeterRegistry(); + timedAspect = new TimedAspect(registry); + timedAspect.setMeterTagAnnotationHandler(meterTagAnnotationHandler); + } + @ParameterizedTest @EnumSource(AnnotatedTestClass.class) void meterTagsWithText(AnnotatedTestClass annotatedClass) { - MeterRegistry registry = new SimpleMeterRegistry(); - TimedAspect timedAspect = new TimedAspect(registry); - timedAspect.setMeterTagAnnotationHandler(meterTagAnnotationHandler); - - AspectJProxyFactory pf = new AspectJProxyFactory(annotatedClass.newInstance()); - pf.addAspect(timedAspect); - - MeterTagClassInterface service = pf.getProxy(); + MeterTagClassInterface service = getProxyWithTimedAspect(annotatedClass.newInstance()); service.getAnnotationForArgumentToString(15L); @@ -474,14 +479,7 @@ void meterTagsWithText(AnnotatedTestClass annotatedClass) { @ParameterizedTest @EnumSource(AnnotatedTestClass.class) void meterTagsWithResolver(AnnotatedTestClass annotatedClass) { - MeterRegistry registry = new SimpleMeterRegistry(); - TimedAspect timedAspect = new TimedAspect(registry); - timedAspect.setMeterTagAnnotationHandler(meterTagAnnotationHandler); - - AspectJProxyFactory pf = new AspectJProxyFactory(annotatedClass.newInstance()); - pf.addAspect(timedAspect); - - MeterTagClassInterface service = pf.getProxy(); + MeterTagClassInterface service = getProxyWithTimedAspect(annotatedClass.newInstance()); service.getAnnotationForTagValueResolver("foo"); @@ -494,14 +492,7 @@ void meterTagsWithResolver(AnnotatedTestClass annotatedClass) { @ParameterizedTest @EnumSource(AnnotatedTestClass.class) void meterTagsWithExpression(AnnotatedTestClass annotatedClass) { - MeterRegistry registry = new SimpleMeterRegistry(); - TimedAspect timedAspect = new TimedAspect(registry); - timedAspect.setMeterTagAnnotationHandler(meterTagAnnotationHandler); - - AspectJProxyFactory pf = new AspectJProxyFactory(annotatedClass.newInstance()); - pf.addAspect(timedAspect); - - MeterTagClassInterface service = pf.getProxy(); + MeterTagClassInterface service = getProxyWithTimedAspect(annotatedClass.newInstance()); service.getAnnotationForTagValueExpression("15L"); @@ -512,14 +503,7 @@ void meterTagsWithExpression(AnnotatedTestClass annotatedClass) { @ParameterizedTest @EnumSource(AnnotatedTestClass.class) void multipleMeterTagsWithExpression(AnnotatedTestClass annotatedClass) { - MeterRegistry registry = new SimpleMeterRegistry(); - TimedAspect timedAspect = new TimedAspect(registry); - timedAspect.setMeterTagAnnotationHandler(meterTagAnnotationHandler); - - AspectJProxyFactory pf = new AspectJProxyFactory(annotatedClass.newInstance()); - pf.addAspect(timedAspect); - - MeterTagClassInterface service = pf.getProxy(); + MeterTagClassInterface service = getProxyWithTimedAspect(annotatedClass.newInstance()); service.getMultipleAnnotationsForTagValueExpression(new DataHolder("zxe", "qwe")); @@ -533,14 +517,7 @@ void multipleMeterTagsWithExpression(AnnotatedTestClass annotatedClass) { @ParameterizedTest @EnumSource(AnnotatedTestClass.class) void multipleMeterTagsWithinContainerWithExpression(AnnotatedTestClass annotatedClass) { - MeterRegistry registry = new SimpleMeterRegistry(); - TimedAspect timedAspect = new TimedAspect(registry); - timedAspect.setMeterTagAnnotationHandler(meterTagAnnotationHandler); - - AspectJProxyFactory pf = new AspectJProxyFactory(annotatedClass.newInstance()); - pf.addAspect(timedAspect); - - MeterTagClassInterface service = pf.getProxy(); + MeterTagClassInterface service = getProxyWithTimedAspect(annotatedClass.newInstance()); service.getMultipleAnnotationsWithContainerForTagValueExpression(new DataHolder("zxe", "qwe")); @@ -554,10 +531,6 @@ void multipleMeterTagsWithinContainerWithExpression(AnnotatedTestClass annotated @Test void meterTagOnPackagePrivateMethod() { - MeterRegistry registry = new SimpleMeterRegistry(); - TimedAspect timedAspect = new TimedAspect(registry); - timedAspect.setMeterTagAnnotationHandler(meterTagAnnotationHandler); - AspectJProxyFactory pf = new AspectJProxyFactory(new MeterTagClass()); pf.setProxyTargetClass(true); pf.addAspect(timedAspect); @@ -571,21 +544,104 @@ void meterTagOnPackagePrivateMethod() { @Test void meterTagOnSuperClass() { - MeterRegistry registry = new SimpleMeterRegistry(); - TimedAspect timedAspect = new TimedAspect(registry); - timedAspect.setMeterTagAnnotationHandler(meterTagAnnotationHandler); + MeterTagSub service = getProxyWithTimedAspect(new MeterTagSub()); + + service.superMethod("someValue"); - AspectJProxyFactory pf = new AspectJProxyFactory(new MeterTagSub()); + assertThat(registry.get("method.timed").tag("superTag", "someValue").timer().count()).isEqualTo(1); + } + + @ParameterizedTest + @EnumSource(AnnotatedTestClass.class) + void meterTagsOnReturnValueWithText(AnnotatedTestClass annotatedClass) { + MeterTagClassInterface service = getProxyWithTimedAspect(annotatedClass.newInstance()); + + service.getAnnotationForArgumentToString(); + + assertThat(registry.get("method.timed").tag("test", "15").timer().count()).isEqualTo(1); + } + + @ParameterizedTest + @EnumSource(AnnotatedTestClass.class) + void meterTagsOnReturnValueWithResolver(AnnotatedTestClass annotatedClass) { + MeterTagClassInterface service = getProxyWithTimedAspect(annotatedClass.newInstance()); + + service.getAnnotationForTagValueResolver(); + + assertThat(registry.get("method.timed") + .tag("test", "Value from myCustomTagValueResolver [foo]") + .timer() + .count()).isEqualTo(1); + } + + @ParameterizedTest + @EnumSource(AnnotatedTestClass.class) + void meterTagsOnReturnValueWithExpression(AnnotatedTestClass annotatedClass) { + MeterTagClassInterface service = getProxyWithTimedAspect(annotatedClass.newInstance()); + + service.getAnnotationForTagValueExpression(); + + assertThat(registry.get("method.timed").tag("test", "hello characters. overridden").timer().count()) + .isEqualTo(1); + } + + @ParameterizedTest + @EnumSource(AnnotatedTestClass.class) + void multipleMeterTagsOnReturnValueWithExpression(AnnotatedTestClass annotatedClass) { + MeterTagClassInterface service = getProxyWithTimedAspect(annotatedClass.newInstance()); + + service.getMultipleAnnotationsForTagValueExpression(); + + assertThat(registry.get("method.timed") + .tag("value1", "value1: zxe") + .tag("value2", "value2. overridden: qwe") + .timer() + .count()).isEqualTo(1); + } + + @ParameterizedTest + @EnumSource(AnnotatedTestClass.class) + void multipleMeterTagsOnReturnValueWithinContainerWithExpression(AnnotatedTestClass annotatedClass) { + MeterTagClassInterface service = getProxyWithTimedAspect(annotatedClass.newInstance()); + + service.getMultipleAnnotationsWithContainerForTagValueExpression(); + + assertThat(registry.get("method.timed") + .tag("value1", "value1: zxe") + .tag("value2", "value2: qwe") + .tag("value3", "value3. overridden: ZXEQWE") + .timer() + .count()).isEqualTo(1); + } + + @Test + void meterTagOnReturnValueOnPackagePrivateMethod() { + AspectJProxyFactory pf = new AspectJProxyFactory(new MeterTagClass()); pf.setProxyTargetClass(true); pf.addAspect(timedAspect); - MeterTagSub service = pf.getProxy(); + MeterTagClass service = pf.getProxy(); - service.superMethod("someValue"); + service.getAnnotationForPackagePrivateMethod(); + + assertThat(registry.get("method.timed").tag("foo", "bar").timer().count()).isEqualTo(1); + } + + @Test + void meterTagOnReturnValueOnSuperClass() { + MeterTagSub service = getProxyWithTimedAspect(new MeterTagSub()); + + service.superMethod(); assertThat(registry.get("method.timed").tag("superTag", "someValue").timer().count()).isEqualTo(1); } + private T getProxyWithTimedAspect(T object) { + AspectJProxyFactory pf = new AspectJProxyFactory(object); + pf.addAspect(timedAspect); + return pf.getProxy(); + } + } enum AnnotatedTestClass { @@ -615,24 +671,47 @@ interface MeterTagClassInterface { @Timed void getAnnotationForTagValueResolver(@MeterTag(key = "test", resolver = ValueResolver.class) String test); + @Timed + @MeterTag(key = "test", resolver = ValueResolver.class) + String getAnnotationForTagValueResolver(); + @Timed void getAnnotationForTagValueExpression( @MeterTag(key = "test", expression = "'hello' + ' characters'") String test); + @Timed + @MeterTag(key = "test", expression = "'hello' + ' characters'") + String getAnnotationForTagValueExpression(); + @Timed void getAnnotationForArgumentToString(@MeterTag("test") Long param); + @Timed + @MeterTag("test") + Long getAnnotationForArgumentToString(); + @Timed void getMultipleAnnotationsForTagValueExpression( @MeterTag(key = "value1", expression = "'value1: ' + value1") @MeterTag(key = "value2", expression = "'value2: ' + value2") DataHolder param); + @Timed + @MeterTag(key = "value1", expression = "'value1: ' + value1") + @MeterTag(key = "value2", expression = "'value2: ' + value2") + DataHolder getMultipleAnnotationsForTagValueExpression(); + @Timed void getMultipleAnnotationsWithContainerForTagValueExpression(@MeterTags({ @MeterTag(key = "value1", expression = "'value1: ' + value1"), @MeterTag(key = "value2", expression = "'value2: ' + value2"), @MeterTag(key = "value3", expression = "'value3: ' + value1.toUpperCase + value2.toUpperCase") }) DataHolder param); + @Timed + @MeterTags({ @MeterTag(key = "value1", expression = "'value1: ' + value1"), + @MeterTag(key = "value2", expression = "'value2: ' + value2"), + @MeterTag(key = "value3", expression = "'value3: ' + value1.toUpperCase + value2.toUpperCase") }) + DataHolder getMultipleAnnotationsWithContainerForTagValueExpression(); + } static class MeterTagClass implements MeterTagClassInterface { @@ -643,21 +722,48 @@ public void getAnnotationForTagValueResolver( @MeterTag(key = "test", resolver = ValueResolver.class) String test) { } + @Timed + @MeterTag(key = "test", resolver = ValueResolver.class) + @Override + public String getAnnotationForTagValueResolver() { + return "foo"; + } + @Timed @Override public void getAnnotationForTagValueExpression( @MeterTag(key = "test", expression = "'hello' + ' characters. overridden'") String test) { } + @Timed + @MeterTag(key = "test", expression = "'hello' + ' characters. overridden'") + @Override + public String getAnnotationForTagValueExpression() { + return "foo"; + } + @Timed @Override public void getAnnotationForArgumentToString(@MeterTag("test") Long param) { } + @Timed + @MeterTag("test") + @Override + public Long getAnnotationForArgumentToString() { + return 15L; + } + @Timed void getAnnotationForPackagePrivateMethod(@MeterTag("foo") String foo) { } + @Timed + @MeterTag("foo") + String getAnnotationForPackagePrivateMethod() { + return "bar"; + } + @Timed @Override public void getMultipleAnnotationsForTagValueExpression( @@ -666,6 +772,14 @@ public void getMultipleAnnotationsForTagValueExpression( } + @Timed + @MeterTag(key = "value1", expression = "'value1: ' + value1") + @MeterTag(key = "value2", expression = "'value2. overridden: ' + value2") + @Override + public DataHolder getMultipleAnnotationsForTagValueExpression() { + return new DataHolder("zxe", "qwe"); + } + @Timed @Override public void getMultipleAnnotationsWithContainerForTagValueExpression(@MeterTags({ @@ -674,6 +788,16 @@ public void getMultipleAnnotationsWithContainerForTagValueExpression(@MeterTags( expression = "'value3. overridden: ' + value1.toUpperCase + value2.toUpperCase") }) DataHolder param) { } + @Timed + @MeterTags({ @MeterTag(key = "value1", expression = "'value1: ' + value1"), + @MeterTag(key = "value2", expression = "'value2: ' + value2"), + @MeterTag(key = "value3", + expression = "'value3. overridden: ' + value1.toUpperCase + value2.toUpperCase") }) + @Override + public DataHolder getMultipleAnnotationsWithContainerForTagValueExpression() { + return new DataHolder("zxe", "qwe"); + } + } static class MeterTagClassChild implements MeterTagClassInterface { @@ -683,29 +807,62 @@ static class MeterTagClassChild implements MeterTagClassInterface { public void getAnnotationForTagValueResolver(String test) { } + @Timed + @Override + public String getAnnotationForTagValueResolver() { + return "foo"; + } + @Timed @Override public void getAnnotationForTagValueExpression( @MeterTag(key = "test", expression = "'hello' + ' characters. overridden'") String test) { } + @Timed + @MeterTag(key = "test", expression = "'hello' + ' characters. overridden'") + @Override + public String getAnnotationForTagValueExpression() { + return "foo"; + } + @Timed @Override public void getAnnotationForArgumentToString(Long param) { } + @Timed + @Override + public Long getAnnotationForArgumentToString() { + return 15L; + } + @Timed @Override public void getMultipleAnnotationsForTagValueExpression( @MeterTag(key = "value2", expression = "'value2. overridden: ' + value2") DataHolder param) { } + @Timed + @Override + @MeterTag(key = "value2", expression = "'value2. overridden: ' + value2") + public DataHolder getMultipleAnnotationsForTagValueExpression() { + return new DataHolder("zxe", "qwe"); + } + @Timed @Override public void getMultipleAnnotationsWithContainerForTagValueExpression(@MeterTag(key = "value3", expression = "'value3. overridden: ' + value1.toUpperCase + value2.toUpperCase") DataHolder param) { } + @Timed + @MeterTag(key = "value3", expression = "'value3. overridden: ' + value1.toUpperCase + value2.toUpperCase") + @Override + public DataHolder getMultipleAnnotationsWithContainerForTagValueExpression() { + return new DataHolder("zxe", "qwe"); + } + } static class MeterTagSuper { @@ -714,6 +871,12 @@ static class MeterTagSuper { public void superMethod(@MeterTag("superTag") String foo) { } + @Timed + @MeterTag("superTag") + public String superMethod() { + return "someValue"; + } + } static class MeterTagSub extends MeterTagSuper { @@ -722,6 +885,12 @@ static class MeterTagSub extends MeterTagSuper { public void subMethod(@MeterTag("subTag") String foo) { } + @Timed + @MeterTag("subTag") + public String subMethod() { + return "someValue"; + } + } private static final class FailingMeterRegistry extends SimpleMeterRegistry {