Skip to content

Commit

Permalink
Support @MeterTag for @counted
Browse files Browse the repository at this point in the history
  • Loading branch information
izeye committed Apr 10, 2024
1 parent 73f8879 commit 627fdd0
Show file tree
Hide file tree
Showing 7 changed files with 321 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ public class CountedAspect {
*/
private final Predicate<ProceedingJoinPoint> shouldSkip;

private CountedMeterTagAnnotationHandler meterTagAnnotationHandler;

/**
* Creates a {@code CountedAspect} instance with {@link Metrics#globalRegistry}.
*
Expand Down Expand Up @@ -267,7 +269,18 @@ private Counter.Builder counter(ProceedingJoinPoint pjp, Counted counted) {
if (!description.isEmpty()) {
builder.description(description);
}
if (meterTagAnnotationHandler != null) {
meterTagAnnotationHandler.addAnnotatedParameters(builder, pjp);
}
return builder;
}

/**
* Setting this enables support for {@link MeterTag}.
* @param meterTagAnnotationHandler meter tag annotation handler
*/
public void setMeterTagAnnotationHandler(CountedMeterTagAnnotationHandler meterTagAnnotationHandler) {
this.meterTagAnnotationHandler = meterTagAnnotationHandler;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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.core.aop;

import io.micrometer.common.KeyValue;
import io.micrometer.common.annotation.AnnotationHandler;
import io.micrometer.common.annotation.ValueExpressionResolver;
import io.micrometer.common.annotation.ValueResolver;
import io.micrometer.core.instrument.Counter;

import java.util.function.Function;

/**
* Annotation handler for {@link MeterTag}. To add support for {@link MeterTag} on
* {@link CountedAspect} check the
* {@link CountedAspect#setMeterTagAnnotationHandler(CountedMeterTagAnnotationHandler)}
* method.
*
* @author Marcin Grzejszczak
* @author Johnny Lim
*/
public class CountedMeterTagAnnotationHandler extends AnnotationHandler<Counter.Builder> {

/**
* Creates a new instance of {@link CountedMeterTagAnnotationHandler}.
* @param resolverProvider function to retrieve a {@link ValueResolver}
* @param expressionResolverProvider function to retrieve a
* {@link ValueExpressionResolver}
*/
public CountedMeterTagAnnotationHandler(
Function<Class<? extends ValueResolver>, ? extends ValueResolver> resolverProvider,
Function<Class<? extends ValueExpressionResolver>, ? extends ValueExpressionResolver> expressionResolverProvider) {
super((keyValue, builder) -> builder.tag(keyValue.getKey(), keyValue.getValue()), resolverProvider,
expressionResolverProvider, MeterTag.class, (annotation, o) -> {
if (!(annotation instanceof MeterTag)) {
return null;
}
MeterTag meterTag = (MeterTag) annotation;
return KeyValue.of(MeterTagSupport.resolveTagKey(meterTag),
MeterTagSupport.resolveTagValue(meterTag, o, resolverProvider, expressionResolverProvider));
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@
package io.micrometer.core.aop;

import io.micrometer.common.KeyValue;
import io.micrometer.common.annotation.NoOpValueResolver;
import io.micrometer.common.annotation.AnnotationHandler;
import io.micrometer.common.annotation.ValueExpressionResolver;
import io.micrometer.common.annotation.ValueResolver;
import io.micrometer.common.util.StringUtils;
import io.micrometer.core.instrument.Timer;

import java.util.function.Function;
Expand All @@ -31,6 +29,7 @@
* {@link TimedAspect#setMeterTagAnnotationHandler(MeterTagAnnotationHandler)} method.
*
* @since 1.11.0
* @author Marcin Grzejszczak
*/
public class MeterTagAnnotationHandler extends AnnotationHandler<Timer.Builder> {

Expand All @@ -48,31 +47,9 @@ public MeterTagAnnotationHandler(Function<Class<? extends ValueResolver>, ? exte
return null;
}
MeterTag meterTag = (MeterTag) annotation;
return KeyValue.of(resolveTagKey(meterTag),
resolveTagValue(meterTag, o, resolverProvider, expressionResolverProvider));
return KeyValue.of(MeterTagSupport.resolveTagKey(meterTag),
MeterTagSupport.resolveTagValue(meterTag, o, resolverProvider, expressionResolverProvider));
});
}

private static String resolveTagKey(MeterTag annotation) {
return StringUtils.isNotBlank(annotation.value()) ? annotation.value() : annotation.key();
}

static String resolveTagValue(MeterTag annotation, Object argument,
Function<Class<? extends ValueResolver>, ? extends ValueResolver> resolverProvider,
Function<Class<? extends ValueExpressionResolver>, ? extends ValueExpressionResolver> expressionResolverProvider) {
String value = null;
if (annotation.resolver() != NoOpValueResolver.class) {
ValueResolver valueResolver = resolverProvider.apply(annotation.resolver());
value = valueResolver.resolve(argument);
}
else if (StringUtils.isNotBlank(annotation.expression())) {
value = expressionResolverProvider.apply(ValueExpressionResolver.class)
.resolve(annotation.expression(), argument);
}
else if (argument != null) {
value = argument.toString();
}
return value == null ? "" : value;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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.core.aop;

import io.micrometer.common.annotation.NoOpValueResolver;
import io.micrometer.common.annotation.ValueExpressionResolver;
import io.micrometer.common.annotation.ValueResolver;
import io.micrometer.common.util.StringUtils;

import java.util.function.Function;

/**
* Support for {@link MeterTag}.
*
* @author Marcin Grzejszczak
* @author Johnny Lim
*/
final class MeterTagSupport {

static String resolveTagKey(MeterTag annotation) {
return StringUtils.isNotBlank(annotation.value()) ? annotation.value() : annotation.key();
}

static String resolveTagValue(MeterTag annotation, Object argument,
Function<Class<? extends ValueResolver>, ? extends ValueResolver> resolverProvider,
Function<Class<? extends ValueExpressionResolver>, ? extends ValueExpressionResolver> expressionResolverProvider) {
String value = null;
if (annotation.resolver() != NoOpValueResolver.class) {
ValueResolver valueResolver = resolverProvider.apply(annotation.resolver());
value = valueResolver.resolve(argument);
}
else if (StringUtils.isNotBlank(annotation.expression())) {
value = expressionResolverProvider.apply(ValueExpressionResolver.class)
.resolve(annotation.expression(), argument);
}
else if (argument != null) {
value = argument.toString();
}
return value == null ? "" : value;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@
*/
package io.micrometer.core.aop;

import io.micrometer.common.annotation.ValueExpressionResolver;
import io.micrometer.common.annotation.ValueResolver;
import io.micrometer.core.annotation.Counted;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.search.MeterNotFoundException;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.aspectj.lang.ProceedingJoinPoint;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;

import java.util.concurrent.CompletableFuture;
Expand Down Expand Up @@ -363,4 +367,166 @@ String greet() {

}

static class MeterTagsTests {

ValueResolver valueResolver = parameter -> "Value from myCustomTagValueResolver [" + parameter + "]";

ValueExpressionResolver valueExpressionResolver = new SpelValueExpressionResolver();

CountedMeterTagAnnotationHandler meterTagAnnotationHandler = new CountedMeterTagAnnotationHandler(
aClass -> valueResolver, aClass -> valueExpressionResolver);

@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();

service.getAnnotationForArgumentToString(15L);

assertThat(registry.get("method.counted").tag("test", "15").counter().count()).isEqualTo(1);
}

@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();

service.getAnnotationForTagValueResolver("foo");

assertThat(registry.get("method.counted")
.tag("test", "Value from myCustomTagValueResolver [foo]")
.counter()
.count()).isEqualTo(1);
}

@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();

service.getAnnotationForTagValueExpression("15L");

assertThat(registry.get("method.counted").tag("test", "hello characters").counter().count()).isEqualTo(1);
}

@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);

MeterTagClass service = pf.getProxy();

service.getAnnotationForPackagePrivateMethod("bar");

assertThat(registry.get("method.counted").tag("foo", "bar").counter().count()).isEqualTo(1);
}

enum AnnotatedTestClass {

CLASS_WITHOUT_INTERFACE(MeterTagClass.class), CLASS_WITH_INTERFACE(MeterTagClassChild.class);

private final Class<? extends MeterTagClassInterface> clazz;

AnnotatedTestClass(Class<? extends MeterTagClassInterface> clazz) {
this.clazz = clazz;
}

@SuppressWarnings("unchecked")
<T extends MeterTagClassInterface> T newInstance() {
try {
return (T) clazz.getDeclaredConstructor().newInstance();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}

}

interface MeterTagClassInterface {

@Counted
void getAnnotationForTagValueResolver(@MeterTag(key = "test", resolver = ValueResolver.class) String test);

@Counted
void getAnnotationForTagValueExpression(
@MeterTag(key = "test", expression = "'hello' + ' characters'") String test);

@Counted
void getAnnotationForArgumentToString(@MeterTag("test") Long param);

}

static class MeterTagClass implements MeterTagClassInterface {

@Counted
@Override
public void getAnnotationForTagValueResolver(
@MeterTag(key = "test", resolver = ValueResolver.class) String test) {
}

@Counted
@Override
public void getAnnotationForTagValueExpression(
@MeterTag(key = "test", expression = "'hello' + ' characters'") String test) {
}

@Counted
@Override
public void getAnnotationForArgumentToString(@MeterTag("test") Long param) {
}

@Counted
void getAnnotationForPackagePrivateMethod(@MeterTag("foo") String foo) {
}

}

static class MeterTagClassChild implements MeterTagClassInterface {

@Counted
@Override
public void getAnnotationForTagValueResolver(String test) {
}

@Counted
@Override
public void getAnnotationForTagValueExpression(String test) {
}

@Counted
@Override
public void getAnnotationForArgumentToString(Long param) {
}

}

}

}
Loading

0 comments on commit 627fdd0

Please sign in to comment.