Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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