diff --git a/docs/modules/ROOT/pages/concepts/counters.adoc b/docs/modules/ROOT/pages/concepts/counters.adoc index fffe04f66c..1a552f4384 100644 --- a/docs/modules/ROOT/pages/concepts/counters.adoc +++ b/docs/modules/ROOT/pages/concepts/counters.adoc @@ -50,8 +50,6 @@ Counter counter = Counter The `micrometer-core` module contains a `@Counted` annotation that frameworks can use to add counting support to either specific types of methods such as those serving web request endpoints or, more generally, to all methods. -WARNING: Micrometer's Spring Boot configuration does _not_ recognize `@Counted` on arbitrary methods. - Also, an incubating AspectJ aspect is included in `micrometer-core`. You can use it in your application either through compile/load time AspectJ weaving or through framework facilities that interpret AspectJ aspects and proxy targeted methods in some other way, such as Spring AOP. Here is a sample Spring AOP configuration: [source,java] diff --git a/docs/modules/ROOT/pages/concepts/timers.adoc b/docs/modules/ROOT/pages/concepts/timers.adoc index 03f98825f3..d5efd25519 100644 --- a/docs/modules/ROOT/pages/concepts/timers.adoc +++ b/docs/modules/ROOT/pages/concepts/timers.adoc @@ -73,8 +73,6 @@ Note how we do not decide the timer to which to accumulate the sample until it i The `micrometer-core` module contains a `@Timed` annotation that frameworks can use to add timing support to either specific types of methods such as those serving web request endpoints or, more generally, to all methods. -WARNING: Micrometer's Spring Boot configuration does _not_ recognize `@Timed` on arbitrary methods. - Also, an incubating AspectJ aspect is included in `micrometer-core`. You can use it in your application either through compile/load time AspectJ weaving or through framework facilities that interpret AspectJ aspects and proxy targeted methods in some other way, such as Spring AOP. Here is a sample Spring AOP configuration: [source,java] diff --git a/docs/src/test/java/io/micrometer/docs/metrics/TimedAspectTest.java b/docs/src/test/java/io/micrometer/docs/metrics/TimedAspectTest.java index 77fcb77e88..7539097ac3 100644 --- a/docs/src/test/java/io/micrometer/docs/metrics/TimedAspectTest.java +++ b/docs/src/test/java/io/micrometer/docs/metrics/TimedAspectTest.java @@ -40,7 +40,7 @@ class TimedAspectTest { // end::resolvers[] @ParameterizedTest - @EnumSource(AnnotatedTestClass.class) + @EnumSource void meterTagsWithText(AnnotatedTestClass annotatedClass) { MeterRegistry registry = new SimpleMeterRegistry(); TimedAspect timedAspect = new TimedAspect(registry); @@ -63,7 +63,7 @@ void meterTagsWithText(AnnotatedTestClass annotatedClass) { } @ParameterizedTest - @EnumSource(AnnotatedTestClass.class) + @EnumSource void meterTagsWithResolver(AnnotatedTestClass annotatedClass) { MeterRegistry registry = new SimpleMeterRegistry(); TimedAspect timedAspect = new TimedAspect(registry); @@ -88,7 +88,7 @@ void meterTagsWithResolver(AnnotatedTestClass annotatedClass) { } @ParameterizedTest - @EnumSource(AnnotatedTestClass.class) + @EnumSource void meterTagsWithExpression(AnnotatedTestClass annotatedClass) { MeterRegistry registry = new SimpleMeterRegistry(); TimedAspect timedAspect = new TimedAspect(registry); diff --git a/implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/StatsdMeterRegistryPublishTest.java b/implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/StatsdMeterRegistryPublishTest.java index 36e4e21db7..ae1d89fb6e 100644 --- a/implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/StatsdMeterRegistryPublishTest.java +++ b/implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/StatsdMeterRegistryPublishTest.java @@ -80,7 +80,7 @@ void cleanUp() { } @ParameterizedTest - @EnumSource(StatsdProtocol.class) + @EnumSource void receiveAllBufferedMetricsAfterCloseSuccessfully(StatsdProtocol protocol) throws InterruptedException { skipUdsTestOnWindows(protocol); serverLatch = new CountDownLatch(3); @@ -98,7 +98,7 @@ void receiveAllBufferedMetricsAfterCloseSuccessfully(StatsdProtocol protocol) th } @ParameterizedTest - @EnumSource(StatsdProtocol.class) + @EnumSource void receiveMetricsSuccessfully(StatsdProtocol protocol) throws InterruptedException { skipUdsTestOnWindows(protocol); serverLatch = new CountDownLatch(3); @@ -116,7 +116,7 @@ void receiveMetricsSuccessfully(StatsdProtocol protocol) throws InterruptedExcep } @ParameterizedTest - @EnumSource(StatsdProtocol.class) + @EnumSource void resumeSendingMetrics_whenServerIntermittentlyFails(StatsdProtocol protocol) throws InterruptedException { skipUdsTestOnWindows(protocol); serverLatch = new CountDownLatch(1); @@ -163,7 +163,7 @@ void resumeSendingMetrics_whenServerIntermittentlyFails(StatsdProtocol protocol) } @ParameterizedTest - @EnumSource(StatsdProtocol.class) + @EnumSource @Issue("#1676") void stopAndStartMeterRegistrySendsMetrics(StatsdProtocol protocol) throws InterruptedException { skipUdsTestOnWindows(protocol); @@ -206,7 +206,7 @@ void stopAndStartMeterRegistryWithLineSink() throws InterruptedException { } @ParameterizedTest - @EnumSource(StatsdProtocol.class) + @EnumSource void whenBackendInitiallyDown_metricsSentAfterBackendStarts(StatsdProtocol protocol) throws InterruptedException { skipUdsTestOnWindows(protocol); AtomicInteger writeCount = new AtomicInteger(); @@ -245,7 +245,7 @@ void whenBackendInitiallyDown_metricsSentAfterBackendStarts(StatsdProtocol proto } @ParameterizedTest - @EnumSource(StatsdProtocol.class) + @EnumSource void whenRegistryStopped_doNotConnectToBackend(StatsdProtocol protocol) throws InterruptedException { skipUdsTestOnWindows(protocol); serverLatch = new CountDownLatch(3); @@ -264,7 +264,7 @@ void whenRegistryStopped_doNotConnectToBackend(StatsdProtocol protocol) throws I } @ParameterizedTest - @EnumSource(StatsdProtocol.class) + @EnumSource @Issue("#2177") void whenSendError_reconnectsAndWritesNewMetrics(StatsdProtocol protocol) throws InterruptedException { skipUdsTestOnWindows(protocol); @@ -296,7 +296,7 @@ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) @Issue("#2880") @ParameterizedTest - @EnumSource(StatsdProtocol.class) + @EnumSource void receiveParallelMetricsSuccessfully(StatsdProtocol protocol) throws InterruptedException { final int n = 10; diff --git a/implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/StatsdMeterRegistryTest.java b/implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/StatsdMeterRegistryTest.java index c3fc93166a..582d6cca98 100644 --- a/implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/StatsdMeterRegistryTest.java +++ b/implementations/micrometer-registry-statsd/src/test/java/io/micrometer/statsd/StatsdMeterRegistryTest.java @@ -79,7 +79,7 @@ public StatsdFlavor flavor() { } @ParameterizedTest - @EnumSource(StatsdFlavor.class) + @EnumSource void counterLineProtocol(StatsdFlavor flavor) { String line = null; switch (flavor) { @@ -112,7 +112,7 @@ void counterLineProtocol(StatsdFlavor flavor) { } @ParameterizedTest - @EnumSource(StatsdFlavor.class) + @EnumSource void gaugeLineProtocol(StatsdFlavor flavor) { final AtomicInteger n = new AtomicInteger(2); final StatsdConfig config = configWithFlavor(flavor); @@ -145,7 +145,7 @@ void gaugeLineProtocol(StatsdFlavor flavor) { } @ParameterizedTest - @EnumSource(StatsdFlavor.class) + @EnumSource void timerLineProtocol(StatsdFlavor flavor) { String line = null; switch (flavor) { @@ -178,7 +178,7 @@ void timerLineProtocol(StatsdFlavor flavor) { } @ParameterizedTest - @EnumSource(StatsdFlavor.class) + @EnumSource void summaryLineProtocol(StatsdFlavor flavor) { String line = null; switch (flavor) { @@ -211,7 +211,7 @@ void summaryLineProtocol(StatsdFlavor flavor) { } @ParameterizedTest - @EnumSource(StatsdFlavor.class) + @EnumSource void longTaskTimerLineProtocol(StatsdFlavor flavor) { final StatsdConfig config = configWithFlavor(flavor); long stepMillis = config.step().toMillis(); @@ -286,7 +286,7 @@ void counterIncrementDoesNotCauseStackOverflow() { } @ParameterizedTest - @EnumSource(StatsdFlavor.class) + @EnumSource @Issue("#370") void serviceLevelObjectivesOnlyNoPercentileHistogram(StatsdFlavor flavor) { StatsdConfig config = configWithFlavor(flavor); @@ -390,7 +390,7 @@ void interactWithStoppedRegistry() { } @ParameterizedTest - @EnumSource(StatsdFlavor.class) + @EnumSource @Issue("#600") void memoryPerformanceOfNamingConventionInHotLoops(StatsdFlavor flavor) { AtomicInteger namingConventionUses = new AtomicInteger(); diff --git a/micrometer-core/src/main/java/io/micrometer/core/aop/CountedAspect.java b/micrometer-core/src/main/java/io/micrometer/core/aop/CountedAspect.java index c0f199e38c..fcddc4cc2b 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/aop/CountedAspect.java +++ b/micrometer-core/src/main/java/io/micrometer/core/aop/CountedAspect.java @@ -71,6 +71,7 @@ * @author Jonatan Ivanov * @author Johnny Lim * @author Yanming Zhou + * @author Jeonggi Kim * @since 1.2.0 * @see Counted */ @@ -237,8 +238,17 @@ private Object perform(ProceedingJoinPoint pjp, Counted counted) throws Throwabl if (stopWhenCompleted) { try { - return ((CompletionStage) pjp.proceed()) - .whenComplete((result, throwable) -> recordCompletionResult(pjp, counted, throwable)); + Object result = pjp.proceed(); + if (result == null) { + if (!counted.recordFailuresOnly()) { + record(pjp, counted, DEFAULT_EXCEPTION_TAG_VALUE, RESULT_TAG_SUCCESS_VALUE); + } + return result; + } + else { + CompletionStage stage = ((CompletionStage) result); + return stage.whenComplete((res, throwable) -> recordCompletionResult(pjp, counted, throwable)); + } } catch (Throwable e) { record(pjp, counted, e.getClass().getSimpleName(), RESULT_TAG_FAILURE_VALUE); diff --git a/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java b/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java index ee51c7f0a8..fc2138d839 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java +++ b/micrometer-core/src/main/java/io/micrometer/core/aop/TimedAspect.java @@ -82,6 +82,7 @@ * @author Nejc Korasa * @author Jonatan Ivanov * @author Yanming Zhou + * @author Jeonggi Kim * @since 1.0.0 */ @Aspect @@ -235,8 +236,16 @@ private Object processWithTimer(ProceedingJoinPoint pjp, Timed timed, String met if (stopWhenCompleted) { try { - return ((CompletionStage) pjp.proceed()).whenComplete( - (result, throwable) -> record(pjp, timed, metricName, sample, getExceptionTag(throwable))); + Object result = pjp.proceed(); + if (result == null) { + record(pjp, timed, metricName, sample, DEFAULT_EXCEPTION_TAG_VALUE); + return result; + } + else { + CompletionStage stage = ((CompletionStage) result); + return stage.whenComplete( + (res, throwable) -> record(pjp, timed, metricName, sample, getExceptionTag(throwable))); + } } catch (Throwable e) { record(pjp, timed, metricName, sample, e.getClass().getSimpleName()); @@ -307,8 +316,15 @@ private Object processWithLongTaskTimer(ProceedingJoinPoint pjp, Timed timed, St if (stopWhenCompleted) { try { - return ((CompletionStage) pjp.proceed()) - .whenComplete((result, throwable) -> sample.ifPresent(this::stopTimer)); + Object result = pjp.proceed(); + if (result == null) { + sample.ifPresent(this::stopTimer); + return result; + } + else { + CompletionStage stage = ((CompletionStage) result); + return stage.whenComplete((res, throwable) -> sample.ifPresent(this::stopTimer)); + } } catch (Throwable e) { sample.ifPresent(this::stopTimer); diff --git a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/cache/CaffeineStatsCounterTest.java b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/cache/CaffeineStatsCounterTest.java index 358969bd7c..b10fb495a6 100644 --- a/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/cache/CaffeineStatsCounterTest.java +++ b/micrometer-core/src/test/java/io/micrometer/core/instrument/binder/cache/CaffeineStatsCounterTest.java @@ -94,7 +94,7 @@ void loadFailure() { } @ParameterizedTest - @EnumSource(RemovalCause.class) + @EnumSource void evictionWithCause(RemovalCause cause) { stats.recordEviction(3, cause); DistributionSummary summary = fetch("cache.evictions", "cause", cause.name()).summary(); diff --git a/micrometer-observation/src/main/java/io/micrometer/observation/aop/ObservedAspect.java b/micrometer-observation/src/main/java/io/micrometer/observation/aop/ObservedAspect.java index 080692ee1a..c6d7d4fdb8 100644 --- a/micrometer-observation/src/main/java/io/micrometer/observation/aop/ObservedAspect.java +++ b/micrometer-observation/src/main/java/io/micrometer/observation/aop/ObservedAspect.java @@ -70,6 +70,7 @@ * * @author Jonatan Ivanov * @author Yanming Zhou + * @author Jeonggi Kim * @since 1.10.0 */ @Aspect @@ -148,8 +149,15 @@ private Object observe(ProceedingJoinPoint pjp, Method method, Observed observed observation.start(); Observation.Scope scope = observation.openScope(); try { - return ((CompletionStage) pjp.proceed()) - .whenComplete((result, error) -> stopObservation(observation, scope, error)); + Object result = pjp.proceed(); + if (result == null) { + stopObservation(observation, scope, null); + return result; + } + else { + CompletionStage stage = (CompletionStage) result; + return stage.whenComplete((res, error) -> stopObservation(observation, scope, error)); + } } catch (Throwable error) { stopObservation(observation, scope, error); diff --git a/micrometer-test/src/main/java/io/micrometer/core/instrument/HttpClientTimingInstrumentationVerificationTests.java b/micrometer-test/src/main/java/io/micrometer/core/instrument/HttpClientTimingInstrumentationVerificationTests.java index 945c3e18fb..67a0bd0a94 100644 --- a/micrometer-test/src/main/java/io/micrometer/core/instrument/HttpClientTimingInstrumentationVerificationTests.java +++ b/micrometer-test/src/main/java/io/micrometer/core/instrument/HttpClientTimingInstrumentationVerificationTests.java @@ -144,7 +144,7 @@ protected String substitutePathVariables(String templatedPath, String... pathVar } @ParameterizedTest - @EnumSource(TestType.class) + @EnumSource void getTemplatedPathForUri(TestType testType, WireMockRuntimeInfo wmRuntimeInfo) { checkAndSetupTestForTestType(testType); @@ -162,7 +162,7 @@ void getTemplatedPathForUri(TestType testType, WireMockRuntimeInfo wmRuntimeInfo } @ParameterizedTest - @EnumSource(TestType.class) + @EnumSource @Disabled("apache/jetty http client instrumentation currently fails this test") void timedWhenServerIsMissing(TestType testType) throws IOException { checkAndSetupTestForTestType(testType); @@ -186,7 +186,7 @@ void timedWhenServerIsMissing(TestType testType) throws IOException { } @ParameterizedTest - @EnumSource(TestType.class) + @EnumSource void serverException(TestType testType, WireMockRuntimeInfo wmRuntimeInfo) { checkAndSetupTestForTestType(testType); @@ -203,7 +203,7 @@ void serverException(TestType testType, WireMockRuntimeInfo wmRuntimeInfo) { } @ParameterizedTest - @EnumSource(TestType.class) + @EnumSource void clientException(TestType testType, WireMockRuntimeInfo wmRuntimeInfo) { checkAndSetupTestForTestType(testType); @@ -223,7 +223,7 @@ void clientException(TestType testType, WireMockRuntimeInfo wmRuntimeInfo) { // TODO this test doesn't need to be parameterized but the custom resolver for // Before/After methods doesn't like when it isn't. @ParameterizedTest - @EnumSource(TestType.class) + @EnumSource void headerIsPropagatedFromContext(TestType testType, WireMockRuntimeInfo wmRuntimeInfo) { checkAndSetupTestForTestType(testType); diff --git a/micrometer-test/src/main/java/io/micrometer/core/instrument/HttpServerTimingInstrumentationVerificationTests.java b/micrometer-test/src/main/java/io/micrometer/core/instrument/HttpServerTimingInstrumentationVerificationTests.java index 2b304d9aec..075fe118c9 100644 --- a/micrometer-test/src/main/java/io/micrometer/core/instrument/HttpServerTimingInstrumentationVerificationTests.java +++ b/micrometer-test/src/main/java/io/micrometer/core/instrument/HttpServerTimingInstrumentationVerificationTests.java @@ -120,14 +120,14 @@ void afterEach() throws Exception { } @ParameterizedTest - @EnumSource(TestType.class) + @EnumSource void uriIsNotFound_whenRouteIsUnmapped(TestType testType) throws Throwable { sender.get(baseUri + "notFound").send(); checkTimer(rs -> rs.tags("uri", "NOT_FOUND", "status", "404", "method", "GET").timer().count() == 1); } @ParameterizedTest - @EnumSource(TestType.class) + @EnumSource void uriTemplateIsTagged(TestType testType) throws Throwable { sender.get(baseUri + "hello/world").send(); checkTimer(rs -> rs.tags("uri", InstrumentedRoutes.TEMPLATED_ROUTE, "status", "200", "method", "GET") @@ -136,7 +136,7 @@ void uriTemplateIsTagged(TestType testType) throws Throwable { } @ParameterizedTest - @EnumSource(TestType.class) + @EnumSource void redirect(TestType testType) throws Throwable { sender.get(baseUri + "foundRedirect").send(); checkTimer(rs -> rs.tags("uri", InstrumentedRoutes.REDIRECT, "status", "302", "method", "GET") @@ -145,7 +145,7 @@ void redirect(TestType testType) throws Throwable { } @ParameterizedTest - @EnumSource(TestType.class) + @EnumSource void errorResponse(TestType testType) throws Throwable { sender.post(baseUri + "error").send(); checkTimer( @@ -153,7 +153,7 @@ void errorResponse(TestType testType) throws Throwable { } @ParameterizedTest - @EnumSource(TestType.class) + @EnumSource void canExtractContextFromHeaders(TestType testType) throws Throwable { sender.get(baseUri + "hello/micrometer").withHeader("Test-Propagation", "someValue").send(); diff --git a/micrometer-test/src/main/java/io/micrometer/core/ipc/http/HttpSenderCompatibilityKit.java b/micrometer-test/src/main/java/io/micrometer/core/ipc/http/HttpSenderCompatibilityKit.java index a81ecc24ea..5cfbd5d84a 100644 --- a/micrometer-test/src/main/java/io/micrometer/core/ipc/http/HttpSenderCompatibilityKit.java +++ b/micrometer-test/src/main/java/io/micrometer/core/ipc/http/HttpSenderCompatibilityKit.java @@ -56,7 +56,7 @@ void httpSenderIsNotNull() { @ParameterizedTest @DisplayName("successfully send a request with NO body and receive a response with NO body") - @EnumSource(HttpSender.Method.class) + @EnumSource void successfulRequestSentWithNoBody(HttpSender.Method method, @WiremockResolver.Wiremock WireMockServer server) throws Throwable { server.stubFor(any(urlEqualTo("/metrics"))); @@ -104,7 +104,7 @@ void successfulRequestSentWithBody(HttpSender.Method method, @WiremockResolver.W @ParameterizedTest @DisplayName("receive an error response") - @EnumSource(HttpSender.Method.class) + @EnumSource void errorResponseReceived(HttpSender.Method method, @WiremockResolver.Wiremock WireMockServer server) throws Throwable { server.stubFor(any(urlEqualTo("/metrics")).willReturn(badRequest().withBody("Error processing metrics"))); @@ -121,7 +121,7 @@ void errorResponseReceived(HttpSender.Method method, @WiremockResolver.Wiremock } @ParameterizedTest - @EnumSource(HttpSender.Method.class) + @EnumSource void basicAuth(HttpSender.Method method, @WiremockResolver.Wiremock WireMockServer server) throws Throwable { server.stubFor(any(urlEqualTo("/metrics")).willReturn(unauthorized())); @@ -140,7 +140,7 @@ void basicAuth(HttpSender.Method method, @WiremockResolver.Wiremock WireMockServ } @ParameterizedTest - @EnumSource(HttpSender.Method.class) + @EnumSource void customHeader(HttpSender.Method method, @WiremockResolver.Wiremock WireMockServer server) throws Throwable { server.stubFor(any(urlEqualTo("/metrics")).willReturn(unauthorized())); diff --git a/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/CountedAspectTest.java b/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/CountedAspectTest.java index e0b730b250..0f2e004837 100644 --- a/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/CountedAspectTest.java +++ b/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/CountedAspectTest.java @@ -258,6 +258,27 @@ void pjpFunctionThrows() { assertThat(counter.getId().getDescription()).isNull(); } + @Test + void countedWithSuccessfulMetricsWhenReturnsCompletionStageNull() { + CompletableFuture completableFuture = asyncCountedService.successButNull(); + assertThat(completableFuture).isNull(); + assertThat(meterRegistry.get("metric.success") + .tag("method", "successButNull") + .tag("class", getClass().getName() + "$AsyncCountedService") + .tag("extra", "tag") + .tag("exception", "none") + .tag("result", "success") + .counter() + .count()).isOne(); + } + + @Test + void countedWithoutSuccessfulMetricsWhenReturnsCompletionStageNull() { + CompletableFuture completableFuture = asyncCountedService.successButNullWithoutMetrics(); + assertThat(completableFuture).isNull(); + assertThatThrownBy(() -> meterRegistry.get("metric.none").counter()).isInstanceOf(MeterNotFoundException.class); + } + static class CountedService { @Counted(value = "metric.none", recordFailuresOnly = true) @@ -323,6 +344,16 @@ CompletableFuture emptyMetricName(GuardedResult guardedResult) { return supplyAsync(guardedResult::get); } + @Counted(value = "metric.success", extraTags = { "extra", "tag" }) + CompletableFuture successButNull() { + return null; + } + + @Counted(value = "metric.none", recordFailuresOnly = true) + CompletableFuture successButNullWithoutMetrics() { + return null; + } + } static class GuardedResult { @@ -441,7 +472,7 @@ class MeterTagsTests { aClass -> valueResolver, aClass -> valueExpressionResolver); @ParameterizedTest - @EnumSource(AnnotatedTestClass.class) + @EnumSource void meterTagsWithText(AnnotatedTestClass annotatedClass) { MeterRegistry registry = new SimpleMeterRegistry(); CountedAspect countedAspect = new CountedAspect(registry); @@ -458,7 +489,7 @@ void meterTagsWithText(AnnotatedTestClass annotatedClass) { } @ParameterizedTest - @EnumSource(AnnotatedTestClass.class) + @EnumSource void meterTagsWithResolver(AnnotatedTestClass annotatedClass) { MeterRegistry registry = new SimpleMeterRegistry(); CountedAspect countedAspect = new CountedAspect(registry); @@ -478,7 +509,7 @@ void meterTagsWithResolver(AnnotatedTestClass annotatedClass) { } @ParameterizedTest - @EnumSource(AnnotatedTestClass.class) + @EnumSource void meterTagsWithExpression(AnnotatedTestClass annotatedClass) { MeterRegistry registry = new SimpleMeterRegistry(); CountedAspect countedAspect = new CountedAspect(registry); @@ -495,7 +526,7 @@ void meterTagsWithExpression(AnnotatedTestClass annotatedClass) { } @ParameterizedTest - @EnumSource(AnnotatedTestClass.class) + @EnumSource void multipleMeterTagsWithExpression(AnnotatedTestClass annotatedClass) { MeterRegistry registry = new SimpleMeterRegistry(); CountedAspect countedAspect = new CountedAspect(registry); @@ -516,7 +547,7 @@ void multipleMeterTagsWithExpression(AnnotatedTestClass annotatedClass) { } @ParameterizedTest - @EnumSource(AnnotatedTestClass.class) + @EnumSource void multipleMeterTagsWithinContainerWithExpression(AnnotatedTestClass annotatedClass) { MeterRegistry registry = new SimpleMeterRegistry(); CountedAspect countedAspect = new CountedAspect(registry); diff --git a/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/ObservedAspectTests.java b/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/ObservedAspectTests.java index c3476d4b8e..a01ef02ac5 100644 --- a/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/ObservedAspectTests.java +++ b/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/ObservedAspectTests.java @@ -357,6 +357,25 @@ void ignoreClassLevelAnnotationIfMethodLevelPresent() { .hasContextualNameEqualTo("test.class#annotatedOnMethod"); } + @Test + void annotatedAsyncClassCallWithNullShouldBeObserved() { + registry.observationConfig().observationHandler(new ObservationTextPublisher()); + AspectJProxyFactory pf = new AspectJProxyFactory(new ObservedClassLevelAnnotatedService()); + pf.addAspect(new ObservedAspect(registry)); + ObservedClassLevelAnnotatedService service = pf.getProxy(); + CompletableFuture asyncResult = service.asyncNull(); + assertThat(asyncResult).isNull(); + assertThat(registry).doesNotHaveAnyRemainingCurrentObservation() + .hasSingleObservationThat() + .hasNameEqualTo("test.class") + .hasContextualNameEqualTo("test.class#call") + .hasLowCardinalityKeyValue("abc", "123") + .hasLowCardinalityKeyValue("test", "42") + .hasLowCardinalityKeyValue("class", ObservedClassLevelAnnotatedService.class.getName()) + .hasLowCardinalityKeyValue("method", "asyncNull") + .doesNotHaveError(); + } + static class ObservedService { @Observed(name = "test.call", contextualName = "test#call", @@ -445,6 +464,10 @@ CompletableFuture async(FakeAsyncTask fakeAsyncTask) { void annotatedOnMethod() { } + CompletableFuture asyncNull() { + return null; + } + } static class FakeAsyncTask implements Supplier { diff --git a/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/TimedAspectTest.java b/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/TimedAspectTest.java index 1afede1331..80b11cc955 100644 --- a/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/TimedAspectTest.java +++ b/samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/TimedAspectTest.java @@ -264,6 +264,24 @@ void timeMethodWhenCompletedExceptionally() { .count()).isEqualTo(1); } + @Test + void timeMethodWhenReturnCompletionStageNull() { + MeterRegistry registry = new SimpleMeterRegistry(); + AspectJProxyFactory pf = new AspectJProxyFactory(new AsyncTimedService()); + pf.addAspect(new TimedAspect(registry)); + AsyncTimedService service = pf.getProxy(); + CompletableFuture completableFuture = service.callNull(); + assertThat(completableFuture).isNull(); + assertThat(registry.getMeters()).isNotEmpty(); + assertThat(registry.get("callNull") + .tag("class", getClass().getName() + "$AsyncTimedService") + .tag("method", "callNull") + .tag("extra", "tag") + .tag("exception", "none") + .timer() + .count()).isEqualTo(1); + } + @Test void timeMethodWithLongTaskTimerWhenCompleted() { MeterRegistry registry = new SimpleMeterRegistry(); @@ -324,6 +342,27 @@ void timeMethodWithLongTaskTimerWhenCompletedExceptionally() { .activeTasks()).isEqualTo(0); } + @Test + void timeMethodWithLongTaskTimerWhenReturnCompletionStageNull() { + MeterRegistry registry = new SimpleMeterRegistry(); + AspectJProxyFactory pf = new AspectJProxyFactory(new AsyncTimedService()); + pf.addAspect(new TimedAspect(registry)); + AsyncTimedService service = pf.getProxy(); + CompletableFuture completableFuture = service.longCallNull(); + assertThat(completableFuture).isNull(); + assertThat(registry.get("longCallNull") + .tag("class", getClass().getName() + "$AsyncTimedService") + .tag("method", "longCallNull") + .tag("extra", "tag") + .longTaskTimers()).hasSize(1); + assertThat(registry.find("longCallNull") + .tag("class", getClass().getName() + "$AsyncTimedService") + .tag("method", "longCallNull") + .tag("extra", "tag") + .longTaskTimer() + .activeTasks()).isEqualTo(0); + } + @Test void timeMethodFailureWhenCompletedExceptionally() { MeterRegistry failingRegistry = new FailingMeterRegistry(); @@ -487,7 +526,7 @@ class MeterTagsTests { aClass -> valueExpressionResolver); @ParameterizedTest - @EnumSource(AnnotatedTestClass.class) + @EnumSource void meterTagsWithText(AnnotatedTestClass annotatedClass) { MeterRegistry registry = new SimpleMeterRegistry(); TimedAspect timedAspect = new TimedAspect(registry); @@ -504,7 +543,7 @@ void meterTagsWithText(AnnotatedTestClass annotatedClass) { } @ParameterizedTest - @EnumSource(AnnotatedTestClass.class) + @EnumSource void meterTagsWithResolver(AnnotatedTestClass annotatedClass) { MeterRegistry registry = new SimpleMeterRegistry(); TimedAspect timedAspect = new TimedAspect(registry); @@ -524,7 +563,7 @@ void meterTagsWithResolver(AnnotatedTestClass annotatedClass) { } @ParameterizedTest - @EnumSource(AnnotatedTestClass.class) + @EnumSource void meterTagsWithExpression(AnnotatedTestClass annotatedClass) { MeterRegistry registry = new SimpleMeterRegistry(); TimedAspect timedAspect = new TimedAspect(registry); @@ -542,7 +581,7 @@ void meterTagsWithExpression(AnnotatedTestClass annotatedClass) { } @ParameterizedTest - @EnumSource(AnnotatedTestClass.class) + @EnumSource void multipleMeterTagsWithExpression(AnnotatedTestClass annotatedClass) { MeterRegistry registry = new SimpleMeterRegistry(); TimedAspect timedAspect = new TimedAspect(registry); @@ -563,7 +602,7 @@ void multipleMeterTagsWithExpression(AnnotatedTestClass annotatedClass) { } @ParameterizedTest - @EnumSource(AnnotatedTestClass.class) + @EnumSource void multipleMeterTagsWithinContainerWithExpression(AnnotatedTestClass annotatedClass) { MeterRegistry registry = new SimpleMeterRegistry(); TimedAspect timedAspect = new TimedAspect(registry); @@ -816,11 +855,21 @@ CompletableFuture call(GuardedResult guardedResult) { return supplyAsync(guardedResult::get); } + @Timed(value = "callNull", extraTags = { "extra", "tag" }) + CompletableFuture callNull() { + return null; + } + @Timed(value = "longCall", extraTags = { "extra", "tag" }, longTask = true) CompletableFuture longCall(GuardedResult guardedResult) { return supplyAsync(guardedResult::get); } + @Timed(value = "longCallNull", extraTags = { "extra", "tag" }, longTask = true) + CompletableFuture longCallNull() { + return null; + } + } static class GuardedResult {