Skip to content

Commit

Permalink
Added missing tests for parameter handling
Browse files Browse the repository at this point in the history
  • Loading branch information
marcingrzejszczak committed May 8, 2024
1 parent 7c85c65 commit 7fa9545
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Copyright 2022 the original author or authors.
*
* 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.observation.aop;

import io.micrometer.common.annotation.NoOpValueResolver;
import io.micrometer.common.annotation.ValueResolver;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target(ElementType.PARAMETER)
@interface HighCardinality {

/**
* The name of the key of the tag which should be created.
* @return the tag key
*/
String value() default "";

/**
* The name of the key of the tag which should be created.
* @return the tag value
*/
String key() default "";

/**
* Execute this expression to calculate the tag value. Will be analyzed if no value of
* the {@link HighCardinality#resolver()} was set.
* @return an expression
*/
String expression() default "";

/**
* Use this bean to resolve the tag value. Has the highest precedence.
* @return {@link ValueResolver} bean
*/
Class<? extends ValueResolver> resolver() default NoOpValueResolver.class;

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

import io.micrometer.common.KeyValue;
import io.micrometer.common.annotation.AnnotationHandler;
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 io.micrometer.observation.Observation;

import java.util.function.Function;

/**
* Taken from Tracing for testing.
*/
class HighCardinalityAnnotationHandler extends AnnotationHandler<Observation> {

public HighCardinalityAnnotationHandler(
Function<Class<? extends ValueResolver>, ? extends ValueResolver> resolverProvider,
Function<Class<? extends ValueExpressionResolver>, ? extends ValueExpressionResolver> expressionResolverProvider) {
super((keyValue, observation) -> observation.highCardinalityKeyValue(keyValue), resolverProvider,
expressionResolverProvider, HighCardinality.class, (annotation, o) -> {
if (!(annotation instanceof HighCardinality)) {
return null;
}
HighCardinality highCardinality = (HighCardinality) annotation;
return KeyValue.of(resolveTagKey(highCardinality),
resolveTagValue(highCardinality, o, resolverProvider, expressionResolverProvider));
});
}

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

static String resolveTagValue(HighCardinality 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 @@ -23,11 +23,15 @@
import io.micrometer.context.ContextSnapshotFactory;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.ObservationTextPublisher;
import io.micrometer.observation.annotation.Observed;
import io.micrometer.observation.tck.TestObservationRegistry;
import io.micrometer.observation.tck.TestObservationRegistryAssert;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.junit.jupiter.api.Test;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;

Expand Down Expand Up @@ -70,6 +74,27 @@ void annotatedCallShouldBeObserved() {
.doesNotHaveError();
}

@Test
void annotatedCallOnAnInterfaceObserved() {
registry.observationConfig().observationHandler(new ObservationTextPublisher());

AspectJProxyFactory pf = new AspectJProxyFactory(new TestBean());
pf.addAspect(new ObservedAspect(registry));
pf.addAspect(new AspectWithParameterHandler());

TestBeanInterface service = pf.getProxy();
service.testMethod("bar");

TestObservationRegistryAssert.assertThat(registry)
.doesNotHaveAnyRemainingCurrentObservation()
.hasSingleObservationThat()
.hasBeenStopped()
.hasNameEqualTo("test.method")
.hasContextualNameEqualTo("foo")
.hasHighCardinalityKeyValue("foo", "bar")
.doesNotHaveError();
}

@Test
void annotatedCallShouldBeObservedAndErrorRecorded() {
registry.observationConfig().observationHandler(new ObservationTextPublisher());
Expand Down Expand Up @@ -363,6 +388,38 @@ CompletableFuture<String> async(FakeAsyncTask fakeAsyncTask) {

}

interface TestBeanInterface {

@Observed(name = "test.method", contextualName = "foo")
default void testMethod(@HighCardinality(key = "foo") String foo) {

}

}

// Example of an implementation class
static class TestBean implements TestBeanInterface {

}

@Aspect
static class AspectWithParameterHandler {

private final HighCardinalityAnnotationHandler handler = new HighCardinalityAnnotationHandler(
aClass -> parameter -> "", aClass -> (expression, parameter) -> "");

private final ObservationRegistry observationRegistry = ObservationRegistry.create();

@Around("execution (@io.micrometer.observation.annotation.Observed * *.*(..))")
@Nullable
public Object observeMethod(ProceedingJoinPoint pjp) throws Throwable {
Observation observation = observationRegistry.getCurrentObservation();
handler.addAnnotatedParameters(observation, pjp);
return pjp.proceed();
}

}

@Observed(name = "test.class", contextualName = "test.class#call",
lowCardinalityKeyValues = { "abc", "123", "test", "42" })
static class ObservedClassLevelAnnotatedService {
Expand Down

0 comments on commit 7fa9545

Please sign in to comment.