Skip to content

Commit

Permalink
Added support of @MeterTag annotation on method level
Browse files Browse the repository at this point in the history
Signed-off-by: Marina Moiseenko <[email protected]>
  • Loading branch information
MarinaMoiseenko committed Jan 26, 2025
1 parent c39bda3 commit 0caa68f
Show file tree
Hide file tree
Showing 9 changed files with 430 additions and 122 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -89,16 +90,36 @@ public void addAnnotatedParameters(T objectToModify, ProceedingJoinPoint pjp) {
try {
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
method = tryToTakeMethodFromTargetClass(pjp, method);
List<AnnotatedParameter> annotatedParameters = AnnotationUtils.findAnnotatedParameters(annotationClass,
method, pjp.getArgs());
getAnnotationsFromInterfaces(pjp, method, annotatedParameters);
List<AnnotatedObject> annotatedParameters = AnnotationUtils.findAnnotatedParameters(annotationClass, method,
pjp.getArgs());
getParametersAnnotationsFromInterfaces(pjp, method, annotatedParameters);
addAnnotatedArguments(objectToModify, annotatedParameters);
}
catch (Exception ex) {
log.error("Exception occurred while trying to add annotated parameters", ex);
}
}

public void addAnnotatedMethodResult(T objectToModify, ProceedingJoinPoint pjp, Object result) {
try {
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
method = tryToTakeMethodFromTargetClass(pjp, method);

List<AnnotatedObject> 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());
Expand All @@ -109,34 +130,48 @@ private static Method tryToTakeMethodFromTargetClass(ProceedingJoinPoint pjp, Me
return method;
}

private void getAnnotationsFromInterfaces(ProceedingJoinPoint pjp, Method mostSpecificMethod,
List<AnnotatedParameter> annotatedParameters) {
private void getParametersAnnotationsFromInterfaces(ProceedingJoinPoint pjp, Method mostSpecificMethod,
List<AnnotatedObject> annotatedParameters) {
traverseInterfacesHierarchy(pjp, mostSpecificMethod, method -> {
List<AnnotatedObject> 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<Method> consumer) {
Class<?>[] implementedInterfaces = pjp.getThis().getClass().getInterfaces();
for (Class<?> implementedInterface : implementedInterfaces) {
for (Method methodFromInterface : implementedInterface.getMethods()) {
if (methodsAreTheSame(mostSpecificMethod, methodFromInterface)) {
List<AnnotatedParameter> 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<Annotation> getMethodAnnotationsFromInterfaces(ProceedingJoinPoint pjp, Method mostSpecificMethod) {
List<Annotation> 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<AnnotatedParameter> toBeAdded) {
private void addAnnotatedArguments(T objectToModify, List<AnnotatedObject> toBeAdded) {
Set<String> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ private AnnotationUtils() {

}

static List<AnnotatedParameter> findAnnotatedParameters(Class<? extends Annotation> annotationClazz, Method method,
static List<AnnotatedObject> findAnnotatedParameters(Class<? extends Annotation> annotationClazz, Method method,
Object[] args) {
Parameter[] parameters = method.getParameters();
List<AnnotatedParameter> result = new ArrayList<>();
List<AnnotatedObject> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ private Function<ProceedingJoinPoint, Iterable<Tag>> makeSafe(

@Around("@within(io.micrometer.core.annotation.Counted) && !@annotation(io.micrometer.core.annotation.Counted) && execution(* *(..))")
@Nullable

public Object countedClass(ProceedingJoinPoint pjp) throws Throwable {
if (shouldSkip.test(pjp)) {
return pjp.proceed();
Expand Down Expand Up @@ -241,48 +242,51 @@ private Object perform(ProceedingJoinPoint pjp, Counted counted) throws Throwabl
Object result = pjp.proceed();
if (result == null) {
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;
}
else {
CompletionStage<?> stage = ((CompletionStage<?>) result);
return stage.whenComplete((res, throwable) -> recordCompletionResult(pjp, counted, throwable));
return stage
.whenComplete((res, 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;
}
}

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) {
private void record(ProceedingJoinPoint pjp, Object methodResult, Counted counted, String exception,
String result) {
Counter.Builder builder = Counter.builder(counted.value())
.description(counted.description().isEmpty() ? null : counted.description())
.tags(counted.extraTags())
Expand All @@ -291,6 +295,7 @@ private void record(ProceedingJoinPoint pjp, Counted counted, String exception,
.tags(tagsBasedOnJoinPoint.apply(pjp));
if (meterTagAnnotationHandler != null) {
meterTagAnnotationHandler.addAnnotatedParameters(builder, pjp);
meterTagAnnotationHandler.addAnnotatedMethodResult(builder, pjp, methodResult);
}
builder.register(registry).increment();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
*/
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target(ElementType.PARAMETER)
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Repeatable(MeterTags.class)
public @interface MeterTag {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
*/
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target(ElementType.PARAMETER)
@Target({ ElementType.METHOD, ElementType.PARAMETER })
@Documented
public @interface MeterTags {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,45 +238,47 @@ private Object processWithTimer(ProceedingJoinPoint pjp, Timed timed, String met
try {
Object result = pjp.proceed();
if (result == null) {
record(pjp, timed, metricName, sample, DEFAULT_EXCEPTION_TAG_VALUE);
record(pjp, result, timed, metricName, sample, DEFAULT_EXCEPTION_TAG_VALUE);
return result;
}
else {
CompletionStage<?> stage = ((CompletionStage<?>) result);
return stage.whenComplete(
(res, throwable) -> record(pjp, timed, metricName, sample, getExceptionTag(throwable)));
return stage.whenComplete((res, 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())
Expand All @@ -292,6 +294,7 @@ private Timer.Builder recordBuilder(ProceedingJoinPoint pjp, Timed timed, String

if (meterTagAnnotationHandler != null) {
meterTagAnnotationHandler.addAnnotatedParameters(builder, pjp);
meterTagAnnotationHandler.addAnnotatedMethodResult(builder, pjp, methodResult);
}
return builder;
}
Expand Down
Loading

0 comments on commit 0caa68f

Please sign in to comment.