Skip to content

Commit d706cc6

Browse files
Merge branch '1.11.x' into 1.12.x
2 parents 0de9dcd + 7fa9545 commit d706cc6

File tree

4 files changed

+191
-0
lines changed

4 files changed

+191
-0
lines changed

micrometer-commons/src/main/java/io/micrometer/common/annotation/AnnotationHandler.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public AnnotationHandler(BiConsumer<KeyValue, T> keyValueConsumer,
8989
public void addAnnotatedParameters(T objectToModify, ProceedingJoinPoint pjp) {
9090
try {
9191
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
92+
method = tryToTakeMethodFromTargetClass(pjp, method);
9293
List<AnnotatedParameter> annotatedParameters = AnnotationUtils.findAnnotatedParameters(annotationClass,
9394
method, pjp.getArgs());
9495
getAnnotationsFromInterfaces(pjp, method, annotatedParameters);
@@ -99,6 +100,16 @@ public void addAnnotatedParameters(T objectToModify, ProceedingJoinPoint pjp) {
99100
}
100101
}
101102

103+
private static Method tryToTakeMethodFromTargetClass(ProceedingJoinPoint pjp, Method method) {
104+
try {
105+
return pjp.getTarget().getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());
106+
}
107+
catch (NoSuchMethodException ex) {
108+
// matching method not found - will be taken from parent
109+
}
110+
return method;
111+
}
112+
102113
private void getAnnotationsFromInterfaces(ProceedingJoinPoint pjp, Method mostSpecificMethod,
103114
List<AnnotatedParameter> annotatedParameters) {
104115
Class<?>[] implementedInterfaces = pjp.getThis().getClass().getInterfaces();
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micrometer.observation.aop;
17+
18+
import io.micrometer.common.annotation.NoOpValueResolver;
19+
import io.micrometer.common.annotation.ValueResolver;
20+
21+
import java.lang.annotation.*;
22+
23+
@Retention(RetentionPolicy.RUNTIME)
24+
@Inherited
25+
@Target(ElementType.PARAMETER)
26+
@interface HighCardinality {
27+
28+
/**
29+
* The name of the key of the tag which should be created.
30+
* @return the tag key
31+
*/
32+
String value() default "";
33+
34+
/**
35+
* The name of the key of the tag which should be created.
36+
* @return the tag value
37+
*/
38+
String key() default "";
39+
40+
/**
41+
* Execute this expression to calculate the tag value. Will be analyzed if no value of
42+
* the {@link HighCardinality#resolver()} was set.
43+
* @return an expression
44+
*/
45+
String expression() default "";
46+
47+
/**
48+
* Use this bean to resolve the tag value. Has the highest precedence.
49+
* @return {@link ValueResolver} bean
50+
*/
51+
Class<? extends ValueResolver> resolver() default NoOpValueResolver.class;
52+
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2023 VMware, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micrometer.observation.aop;
17+
18+
import io.micrometer.common.KeyValue;
19+
import io.micrometer.common.annotation.AnnotationHandler;
20+
import io.micrometer.common.annotation.NoOpValueResolver;
21+
import io.micrometer.common.annotation.ValueExpressionResolver;
22+
import io.micrometer.common.annotation.ValueResolver;
23+
import io.micrometer.common.util.StringUtils;
24+
25+
import io.micrometer.observation.Observation;
26+
27+
import java.util.function.Function;
28+
29+
/**
30+
* Taken from Tracing for testing.
31+
*/
32+
class HighCardinalityAnnotationHandler extends AnnotationHandler<Observation> {
33+
34+
public HighCardinalityAnnotationHandler(
35+
Function<Class<? extends ValueResolver>, ? extends ValueResolver> resolverProvider,
36+
Function<Class<? extends ValueExpressionResolver>, ? extends ValueExpressionResolver> expressionResolverProvider) {
37+
super((keyValue, observation) -> observation.highCardinalityKeyValue(keyValue), resolverProvider,
38+
expressionResolverProvider, HighCardinality.class, (annotation, o) -> {
39+
if (!(annotation instanceof HighCardinality)) {
40+
return null;
41+
}
42+
HighCardinality highCardinality = (HighCardinality) annotation;
43+
return KeyValue.of(resolveTagKey(highCardinality),
44+
resolveTagValue(highCardinality, o, resolverProvider, expressionResolverProvider));
45+
});
46+
}
47+
48+
private static String resolveTagKey(HighCardinality annotation) {
49+
return StringUtils.isNotBlank(annotation.value()) ? annotation.value() : annotation.key();
50+
}
51+
52+
static String resolveTagValue(HighCardinality annotation, Object argument,
53+
Function<Class<? extends ValueResolver>, ? extends ValueResolver> resolverProvider,
54+
Function<Class<? extends ValueExpressionResolver>, ? extends ValueExpressionResolver> expressionResolverProvider) {
55+
String value = null;
56+
if (annotation.resolver() != NoOpValueResolver.class) {
57+
ValueResolver ValueResolver = resolverProvider.apply(annotation.resolver());
58+
value = ValueResolver.resolve(argument);
59+
}
60+
else if (StringUtils.isNotBlank(annotation.expression())) {
61+
value = expressionResolverProvider.apply(ValueExpressionResolver.class)
62+
.resolve(annotation.expression(), argument);
63+
}
64+
else if (argument != null) {
65+
value = argument.toString();
66+
}
67+
return value == null ? "" : value;
68+
}
69+
70+
}

micrometer-observation/src/test/java/io/micrometer/observation/aop/ObservedAspectTests.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@
2323
import io.micrometer.context.ContextSnapshotFactory;
2424
import io.micrometer.observation.Observation;
2525
import io.micrometer.observation.ObservationConvention;
26+
import io.micrometer.observation.ObservationRegistry;
2627
import io.micrometer.observation.ObservationTextPublisher;
2728
import io.micrometer.observation.annotation.Observed;
2829
import io.micrometer.observation.tck.TestObservationRegistry;
2930
import io.micrometer.observation.tck.TestObservationRegistryAssert;
31+
3032
import org.aspectj.lang.ProceedingJoinPoint;
33+
import org.aspectj.lang.annotation.Around;
34+
import org.aspectj.lang.annotation.Aspect;
3135
import org.junit.jupiter.api.Test;
3236
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
3337

@@ -70,6 +74,27 @@ void annotatedCallShouldBeObserved() {
7074
.doesNotHaveError();
7175
}
7276

77+
@Test
78+
void annotatedCallOnAnInterfaceObserved() {
79+
registry.observationConfig().observationHandler(new ObservationTextPublisher());
80+
81+
AspectJProxyFactory pf = new AspectJProxyFactory(new TestBean());
82+
pf.addAspect(new ObservedAspect(registry));
83+
pf.addAspect(new AspectWithParameterHandler());
84+
85+
TestBeanInterface service = pf.getProxy();
86+
service.testMethod("bar");
87+
88+
TestObservationRegistryAssert.assertThat(registry)
89+
.doesNotHaveAnyRemainingCurrentObservation()
90+
.hasSingleObservationThat()
91+
.hasBeenStopped()
92+
.hasNameEqualTo("test.method")
93+
.hasContextualNameEqualTo("foo")
94+
.hasHighCardinalityKeyValue("foo", "bar")
95+
.doesNotHaveError();
96+
}
97+
7398
@Test
7499
void annotatedCallShouldBeObservedAndErrorRecorded() {
75100
registry.observationConfig().observationHandler(new ObservationTextPublisher());
@@ -363,6 +388,38 @@ CompletableFuture<String> async(FakeAsyncTask fakeAsyncTask) {
363388

364389
}
365390

391+
interface TestBeanInterface {
392+
393+
@Observed(name = "test.method", contextualName = "foo")
394+
default void testMethod(@HighCardinality(key = "foo") String foo) {
395+
396+
}
397+
398+
}
399+
400+
// Example of an implementation class
401+
static class TestBean implements TestBeanInterface {
402+
403+
}
404+
405+
@Aspect
406+
static class AspectWithParameterHandler {
407+
408+
private final HighCardinalityAnnotationHandler handler = new HighCardinalityAnnotationHandler(
409+
aClass -> parameter -> "", aClass -> (expression, parameter) -> "");
410+
411+
private final ObservationRegistry observationRegistry = ObservationRegistry.create();
412+
413+
@Around("execution (@io.micrometer.observation.annotation.Observed * *.*(..))")
414+
@Nullable
415+
public Object observeMethod(ProceedingJoinPoint pjp) throws Throwable {
416+
Observation observation = observationRegistry.getCurrentObservation();
417+
handler.addAnnotatedParameters(observation, pjp);
418+
return pjp.proceed();
419+
}
420+
421+
}
422+
366423
@Observed(name = "test.class", contextualName = "test.class#call",
367424
lowCardinalityKeyValues = { "abc", "123", "test", "42" })
368425
static class ObservedClassLevelAnnotatedService {

0 commit comments

Comments
 (0)