Skip to content

Commit adfdd3e

Browse files
authored
Catch runtime exception thrown from pjp function in aspects (#5585)
Before, this would result in an exception bubbling up to user code (CountedAspect) or the metric recording not happening (TimedAspect). This changes the behavior to consistently record the metric without the tags from the pjp function if it throws. This aligns the behavior between the two and is safer (in the case of CountedAspect). An alternative that would also be safe and consistent would be to align to the existing behavior of TimedAspect - not recording any metric when an exception is thrown.
1 parent 5435fc4 commit adfdd3e

File tree

4 files changed

+74
-5
lines changed

4 files changed

+74
-5
lines changed

micrometer-core/src/main/java/io/micrometer/core/aop/CountedAspect.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import io.micrometer.common.lang.NonNullApi;
1919
import io.micrometer.common.lang.Nullable;
20+
import io.micrometer.common.util.internal.logging.WarnThenDebugLogger;
2021
import io.micrometer.core.annotation.Counted;
2122
import io.micrometer.core.instrument.*;
2223
import org.aspectj.lang.ProceedingJoinPoint;
@@ -75,6 +76,8 @@
7576
@NonNullApi
7677
public class CountedAspect {
7778

79+
private static final WarnThenDebugLogger joinPointTagsFunctionLogger = new WarnThenDebugLogger(CountedAspect.class);
80+
7881
private static final Predicate<ProceedingJoinPoint> DONT_SKIP_ANYTHING = pjp -> false;
7982

8083
public final String DEFAULT_EXCEPTION_TAG_VALUE = "none";
@@ -161,10 +164,24 @@ public CountedAspect(MeterRegistry registry, Predicate<ProceedingJoinPoint> shou
161164
public CountedAspect(MeterRegistry registry, Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint,
162165
Predicate<ProceedingJoinPoint> shouldSkip) {
163166
this.registry = registry;
164-
this.tagsBasedOnJoinPoint = tagsBasedOnJoinPoint;
167+
this.tagsBasedOnJoinPoint = makeSafe(tagsBasedOnJoinPoint);
165168
this.shouldSkip = shouldSkip;
166169
}
167170

171+
private Function<ProceedingJoinPoint, Iterable<Tag>> makeSafe(
172+
Function<ProceedingJoinPoint, Iterable<Tag>> function) {
173+
return pjp -> {
174+
try {
175+
return function.apply(pjp);
176+
}
177+
catch (Throwable t) {
178+
joinPointTagsFunctionLogger
179+
.log("Exception thrown from the tagsBasedOnJoinPoint function configured on CountedAspect.", t);
180+
return Tags.empty();
181+
}
182+
};
183+
}
184+
168185
/**
169186
* Intercept methods annotated with the {@link Counted} annotation and expose a few
170187
* counters about their execution status. By default, this aspect records both failed

micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import io.micrometer.common.lang.NonNullApi;
1919
import io.micrometer.common.lang.Nullable;
20+
import io.micrometer.common.util.internal.logging.WarnThenDebugLogger;
2021
import io.micrometer.core.annotation.Incubating;
2122
import io.micrometer.core.annotation.Timed;
2223
import io.micrometer.core.instrument.*;
@@ -83,6 +84,8 @@
8384
@Incubating(since = "1.0.0")
8485
public class TimedAspect {
8586

87+
private static final WarnThenDebugLogger joinPointTagsFunctionLogger = new WarnThenDebugLogger(TimedAspect.class);
88+
8689
private static final Predicate<ProceedingJoinPoint> DONT_SKIP_ANYTHING = pjp -> false;
8790

8891
public static final String DEFAULT_METRIC_NAME = "method.timed";
@@ -156,10 +159,24 @@ public TimedAspect(MeterRegistry registry, Predicate<ProceedingJoinPoint> should
156159
public TimedAspect(MeterRegistry registry, Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint,
157160
Predicate<ProceedingJoinPoint> shouldSkip) {
158161
this.registry = registry;
159-
this.tagsBasedOnJoinPoint = tagsBasedOnJoinPoint;
162+
this.tagsBasedOnJoinPoint = makeSafe(tagsBasedOnJoinPoint);
160163
this.shouldSkip = shouldSkip;
161164
}
162165

166+
private Function<ProceedingJoinPoint, Iterable<Tag>> makeSafe(
167+
Function<ProceedingJoinPoint, Iterable<Tag>> function) {
168+
return pjp -> {
169+
try {
170+
return function.apply(pjp);
171+
}
172+
catch (Throwable t) {
173+
joinPointTagsFunctionLogger
174+
.log("Exception thrown from the tagsBasedOnJoinPoint function configured on TimedAspect.", t);
175+
return Tags.empty();
176+
}
177+
};
178+
}
179+
163180
@Around("@within(io.micrometer.core.annotation.Timed)")
164181
@Nullable
165182
public Object timedClass(ProceedingJoinPoint pjp) throws Throwable {

micrometer-core/src/test/java/io/micrometer/core/aop/CountedAspectTest.java

+18
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,19 @@
1515
*/
1616
package io.micrometer.core.aop;
1717

18+
import io.micrometer.core.Issue;
1819
import io.micrometer.core.annotation.Counted;
1920
import io.micrometer.core.instrument.Counter;
2021
import io.micrometer.core.instrument.MeterRegistry;
22+
import io.micrometer.core.instrument.Tag;
2123
import io.micrometer.core.instrument.search.MeterNotFoundException;
2224
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
2325
import org.aspectj.lang.ProceedingJoinPoint;
2426
import org.junit.jupiter.api.Test;
2527
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
2628

2729
import java.util.concurrent.CompletableFuture;
30+
import java.util.function.Function;
2831
import java.util.function.Predicate;
2932

3033
import static java.util.concurrent.CompletableFuture.supplyAsync;
@@ -193,6 +196,21 @@ void countedWithEmptyMetricNamesWhenCompleted() {
193196
assertThat(meterRegistry.get("method.counted").tag("result", "failure").counter().count()).isOne();
194197
}
195198

199+
@Test
200+
@Issue("#5584")
201+
void pjpFunctionThrows() {
202+
CountedService countedService = getAdvisedService(new CountedService(),
203+
new CountedAspect(meterRegistry, (Function<ProceedingJoinPoint, Iterable<Tag>>) jp -> {
204+
throw new RuntimeException("test");
205+
}));
206+
countedService.succeedWithMetrics();
207+
208+
Counter counter = meterRegistry.get("metric.success").tag("extra", "tag").tag("result", "success").counter();
209+
210+
assertThat(counter.count()).isOne();
211+
assertThat(counter.getId().getDescription()).isNull();
212+
}
213+
196214
static class CountedService {
197215

198216
@Counted(value = "metric.none", recordFailuresOnly = true)

micrometer-core/src/test/java/io/micrometer/core/aop/TimedAspectTest.java

+20-3
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@
1818
import io.micrometer.common.annotation.ValueExpressionResolver;
1919
import io.micrometer.common.annotation.ValueResolver;
2020
import io.micrometer.common.lang.NonNull;
21+
import io.micrometer.core.Issue;
2122
import io.micrometer.core.annotation.Timed;
22-
import io.micrometer.core.instrument.LongTaskTimer;
23+
import io.micrometer.core.instrument.*;
2324
import io.micrometer.core.instrument.Meter.Id;
24-
import io.micrometer.core.instrument.MeterRegistry;
25-
import io.micrometer.core.instrument.Timer;
2625
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
2726
import io.micrometer.core.instrument.distribution.pause.PauseDetector;
2827
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
@@ -36,6 +35,7 @@
3635
import javax.annotation.Nonnull;
3736
import java.util.concurrent.CompletableFuture;
3837
import java.util.concurrent.CompletionException;
38+
import java.util.function.Function;
3939
import java.util.function.Predicate;
4040

4141
import static java.util.concurrent.CompletableFuture.supplyAsync;
@@ -377,6 +377,23 @@ void timeClassFailure() {
377377
assertThat(failingRegistry.getMeters()).isEmpty();
378378
}
379379

380+
@Test
381+
@Issue("#5584")
382+
void pjpFunctionThrows() {
383+
MeterRegistry registry = new SimpleMeterRegistry();
384+
385+
AspectJProxyFactory pf = new AspectJProxyFactory(new TimedService());
386+
pf.addAspect(new TimedAspect(registry, (Function<ProceedingJoinPoint, Iterable<Tag>>) jp -> {
387+
throw new RuntimeException("test");
388+
}));
389+
390+
TimedService service = pf.getProxy();
391+
392+
service.call();
393+
394+
assertThat(registry.get("call").tag("extra", "tag").timer().count()).isEqualTo(1);
395+
}
396+
380397
@Nested
381398
class MeterTagsTests {
382399

0 commit comments

Comments
 (0)