From 47ec371f2a15e38f9d2bcce845b970c9b7676ab0 Mon Sep 17 00:00:00 2001 From: Ralf Ueberfuhr Date: Thu, 29 Aug 2024 15:35:48 +0200 Subject: [PATCH 1/3] Introduce performance logging interceptor --- .../quarkus/domain/CustomersService.java | 4 ++- .../quarkus/shared/LogPerformance.java | 19 ++++++++++++ .../shared/LogPerformanceInterceptor.java | 29 +++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/LogPerformance.java create mode 100644 customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/LogPerformanceInterceptor.java diff --git a/customer-api-provider/src/main/java/de/samples/schulung/quarkus/domain/CustomersService.java b/customer-api-provider/src/main/java/de/samples/schulung/quarkus/domain/CustomersService.java index cbbda5e..da1437f 100644 --- a/customer-api-provider/src/main/java/de/samples/schulung/quarkus/domain/CustomersService.java +++ b/customer-api-provider/src/main/java/de/samples/schulung/quarkus/domain/CustomersService.java @@ -1,6 +1,7 @@ package de.samples.schulung.quarkus.domain; import de.samples.schulung.quarkus.domain.Customer.CustomerState; +import de.samples.schulung.quarkus.shared.LogPerformance; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.event.Event; import jakarta.validation.Valid; @@ -39,7 +40,8 @@ public Stream findCustomersByState(@NotNull CustomerState state) { public Optional findCustomerByUuid(@NotNull UUID uuid) { return Optional.ofNullable(customers.get(uuid)); } - + + @LogPerformance public void createCustomer(@Valid @NotNull Customer customer) { customer.setUuid(UUID.randomUUID()); customers.put(customer.getUuid(), customer); diff --git a/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/LogPerformance.java b/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/LogPerformance.java new file mode 100644 index 0000000..810400d --- /dev/null +++ b/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/LogPerformance.java @@ -0,0 +1,19 @@ +package de.samples.schulung.quarkus.shared; + +import jakarta.interceptor.InterceptorBinding; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ + ElementType.METHOD, + ElementType.TYPE +}) +@Documented +@Inherited +@InterceptorBinding +public @interface LogPerformance { + + // TODO Level konfigurierbar? + +} diff --git a/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/LogPerformanceInterceptor.java b/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/LogPerformanceInterceptor.java new file mode 100644 index 0000000..37e8eb1 --- /dev/null +++ b/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/LogPerformanceInterceptor.java @@ -0,0 +1,29 @@ +package de.samples.schulung.quarkus.shared; + +import io.quarkus.arc.log.LoggerName; +import jakarta.annotation.Priority; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InvocationContext; +import org.jboss.logging.Logger; + +@Interceptor +@LogPerformance +@Priority(10) +public class LogPerformanceInterceptor { + + @LoggerName("performance") + Logger logger; + + @AroundInvoke + public Object logPerformance(InvocationContext invocationContext) throws Exception { + var ts1 = System.currentTimeMillis(); + try { + return invocationContext.proceed(); // Weiterleitung an das Original + } finally { + var ts2 = System.currentTimeMillis(); + logger.info("Dauer der Methode '" + invocationContext.getMethod().getName() + "': " + (ts2 - ts1) + "ms"); + } + } + +} From cc6486da17fc5d56dd475a4f981b08aace4cb8a2 Mon Sep 17 00:00:00 2001 From: Ralf Ueberfuhr Date: Thu, 29 Aug 2024 15:42:44 +0200 Subject: [PATCH 2/3] Add docs --- docs/06-interceptors.png | Bin 0 -> 70872 bytes docs/README.md | 4 ++++ 2 files changed, 4 insertions(+) create mode 100644 docs/06-interceptors.png diff --git a/docs/06-interceptors.png b/docs/06-interceptors.png new file mode 100644 index 0000000000000000000000000000000000000000..0a6fe4344a01d933f2b4f871e5ed1aee2ad4d976 GIT binary patch literal 70872 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 Z=l@N5zWI9^Vy`j)fv2mV%Q~loCIAL+uLb}B literal 0 HcmV?d00001 diff --git a/docs/README.md b/docs/README.md index 2bd21af..81fa32b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,3 +21,7 @@ # Layers ![Layers](05-layers.png) + +# Interceptors + +![Proxy Objects](06-interceptors.png) From ef30c8309824fcbfd6845dd14c67f376d8fd7566 Mon Sep 17 00:00:00 2001 From: Ralf Ueberfuhr Date: Fri, 30 Aug 2024 06:32:54 +0200 Subject: [PATCH 3/3] Add test and interceptor to fire event --- .../quarkus/domain/CustomersService.java | 13 ++- .../quarkus/shared/AnnotationUtils.java | 23 ++++++ .../schulung/quarkus/shared/FireEvent.java | 49 ++++++++++++ .../quarkus/shared/FireEventInterceptor.java | 62 +++++++++++++++ .../quarkus/shared/LogPerformance.java | 5 +- .../shared/LogPerformanceInterceptor.java | 15 +++- .../infrastructure/CustomersLoggingTests.java | 46 +---------- .../shared/LogPerformanceInterceptorTest.java | 79 +++++++++++++++++++ .../utilities/ProfileWithMockedLogger.java | 53 +++++++++++++ 9 files changed, 292 insertions(+), 53 deletions(-) create mode 100644 customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/AnnotationUtils.java create mode 100644 customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/FireEvent.java create mode 100644 customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/FireEventInterceptor.java create mode 100644 customer-api-provider/src/test/java/de/samples/schulung/quarkus/shared/LogPerformanceInterceptorTest.java create mode 100644 customer-api-provider/src/test/java/de/samples/schulung/quarkus/utilities/ProfileWithMockedLogger.java diff --git a/customer-api-provider/src/main/java/de/samples/schulung/quarkus/domain/CustomersService.java b/customer-api-provider/src/main/java/de/samples/schulung/quarkus/domain/CustomersService.java index da1437f..003dde7 100644 --- a/customer-api-provider/src/main/java/de/samples/schulung/quarkus/domain/CustomersService.java +++ b/customer-api-provider/src/main/java/de/samples/schulung/quarkus/domain/CustomersService.java @@ -1,12 +1,12 @@ package de.samples.schulung.quarkus.domain; import de.samples.schulung.quarkus.domain.Customer.CustomerState; +import de.samples.schulung.quarkus.shared.FireEvent; import de.samples.schulung.quarkus.shared.LogPerformance; import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.event.Event; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; +import org.jboss.logging.Logger; import java.util.HashMap; import java.util.Map; @@ -15,13 +15,10 @@ import java.util.stream.Stream; @ApplicationScoped -@RequiredArgsConstructor public class CustomersService { private final Map customers = new HashMap<>(); - private final Event eventPublisher; - public long getCount() { return customers.size(); } @@ -40,12 +37,12 @@ public Stream findCustomersByState(@NotNull CustomerState state) { public Optional findCustomerByUuid(@NotNull UUID uuid) { return Optional.ofNullable(customers.get(uuid)); } - - @LogPerformance + + @LogPerformance(Logger.Level.DEBUG) + @FireEvent(CustomerCreatedEvent.class) public void createCustomer(@Valid @NotNull Customer customer) { customer.setUuid(UUID.randomUUID()); customers.put(customer.getUuid(), customer); - eventPublisher.fireAsync(new CustomerCreatedEvent(customer)); } public void updateCustomer(@Valid @NotNull Customer customer) { diff --git a/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/AnnotationUtils.java b/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/AnnotationUtils.java new file mode 100644 index 0000000..2a9f385 --- /dev/null +++ b/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/AnnotationUtils.java @@ -0,0 +1,23 @@ +package de.samples.schulung.quarkus.shared; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Optional; + +class AnnotationUtils { + + private AnnotationUtils() { + } + + static Optional findAnnotation(Method method, Class annotationClass) { + return Optional + .ofNullable(method.getAnnotation(annotationClass)) + .or(() -> findAnnotation(method.getDeclaringClass(), annotationClass)); + } + + static Optional findAnnotation(Class clazz, Class annotationClass) { + return Optional + .ofNullable(clazz.getAnnotation(annotationClass)); + } + +} diff --git a/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/FireEvent.java b/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/FireEvent.java new file mode 100644 index 0000000..e35bdee --- /dev/null +++ b/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/FireEvent.java @@ -0,0 +1,49 @@ +package de.samples.schulung.quarkus.shared; + +import jakarta.enterprise.util.Nonbinding; +import jakarta.interceptor.InterceptorBinding; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.lang.annotation.*; + +/** + * Annotate a method to get an event fired after method execution. + */ +@Inherited +@Documented +@InterceptorBinding +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface FireEvent { + + /** + * The event class. This class needs a constructor with the same parameters as the method. + * + * @return the event class + */ + @Nonbinding Class value(); + + /** + * Whether the event has to be fired synchronously, asynchronously or both. + * Defaults to both. + * + * @return the mode + */ + @Nonbinding FireMode mode() default FireMode.SYNC_AND_ASYNC; + + @RequiredArgsConstructor + @Getter(AccessLevel.PACKAGE) + enum FireMode { + + ONLY_SYNC(true, false), + ONLY_ASYNC(false, true), + SYNC_AND_ASYNC(true, true); + + private final boolean fireSync; + private final boolean fireAsync; + + } + +} diff --git a/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/FireEventInterceptor.java b/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/FireEventInterceptor.java new file mode 100644 index 0000000..146ac91 --- /dev/null +++ b/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/FireEventInterceptor.java @@ -0,0 +1,62 @@ +package de.samples.schulung.quarkus.shared; + +import jakarta.annotation.Priority; +import jakarta.enterprise.event.Event; +import jakarta.inject.Inject; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InvocationContext; +import lombok.SneakyThrows; + +import java.util.Optional; + +@Priority(5) +@Interceptor +@FireEvent(Object.class) +public class FireEventInterceptor { + + @Inject + Event eventPublisher; + + @SneakyThrows + private static T createEventObject(InvocationContext invocation, Class eventType) { + return eventType + .getConstructor(invocation.getMethod().getParameterTypes()) + .newInstance(invocation.getParameters()); + } + + @AroundInvoke + public Object fireEvent(InvocationContext invocation) throws Exception { + final Optional annotation = AnnotationUtils + .findAnnotation(invocation.getMethod(), FireEvent.class); + @SuppressWarnings("unchecked") final Optional> eventType = AnnotationUtils + .findAnnotation(invocation.getMethod(), FireEvent.class) + .map((FireEvent publishEvent) -> (Class) publishEvent.value()); + final FireEvent.FireMode mode = annotation + .map(FireEvent::mode) + .orElse(FireEvent.FireMode.SYNC_AND_ASYNC); + final Optional event = eventType + .map(clazz -> createEventObject(invocation, clazz)); + // if something is wrong until here, we do not invoke the service's create-method + // now, we invoke the service + final Object result = invocation.proceed(); + // if an exception occured, the event is not fired + // now, we fire the event + event.ifPresent( + e -> eventType + .map(eventPublisher::select) + .ifPresent(publisher -> { + // fire synchronous events + if (mode.isFireSync()) { + publisher.fire(e); + } + // if no error occured, fire asynchronous events + if (mode.isFireAsync()) { + publisher.fireAsync(e); + } + }) + ); + // and we need to return the service's result to the invoker (the controller) + return result; + } +} diff --git a/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/LogPerformance.java b/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/LogPerformance.java index 810400d..c4a6547 100644 --- a/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/LogPerformance.java +++ b/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/LogPerformance.java @@ -1,6 +1,8 @@ package de.samples.schulung.quarkus.shared; +import jakarta.enterprise.util.Nonbinding; import jakarta.interceptor.InterceptorBinding; +import org.jboss.logging.Logger; import java.lang.annotation.*; @@ -14,6 +16,7 @@ @InterceptorBinding public @interface LogPerformance { - // TODO Level konfigurierbar? + @Nonbinding + Logger.Level value() default Logger.Level.INFO; } diff --git a/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/LogPerformanceInterceptor.java b/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/LogPerformanceInterceptor.java index 37e8eb1..2edba1d 100644 --- a/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/LogPerformanceInterceptor.java +++ b/customer-api-provider/src/main/java/de/samples/schulung/quarkus/shared/LogPerformanceInterceptor.java @@ -15,14 +15,27 @@ public class LogPerformanceInterceptor { @LoggerName("performance") Logger logger; + private Logger.Level findLevel(InvocationContext ic) { + return AnnotationUtils + .findAnnotation(ic.getMethod(), LogPerformance.class) + .map(LogPerformance::value) + .orElse(Logger.Level.INFO); + } + @AroundInvoke public Object logPerformance(InvocationContext invocationContext) throws Exception { + final var methodName = invocationContext.getMethod().getName(); + final var level = findLevel(invocationContext); var ts1 = System.currentTimeMillis(); try { return invocationContext.proceed(); // Weiterleitung an das Original } finally { var ts2 = System.currentTimeMillis(); - logger.info("Dauer der Methode '" + invocationContext.getMethod().getName() + "': " + (ts2 - ts1) + "ms"); + logger.logf( + level, + "Dauer der Methode '%s': %d ms", + new Object[]{methodName, ts2 - ts1} + ); } } diff --git a/customer-api-provider/src/test/java/de/samples/schulung/quarkus/infrastructure/CustomersLoggingTests.java b/customer-api-provider/src/test/java/de/samples/schulung/quarkus/infrastructure/CustomersLoggingTests.java index d556e7b..84f38be 100644 --- a/customer-api-provider/src/test/java/de/samples/schulung/quarkus/infrastructure/CustomersLoggingTests.java +++ b/customer-api-provider/src/test/java/de/samples/schulung/quarkus/infrastructure/CustomersLoggingTests.java @@ -2,24 +2,17 @@ import de.samples.schulung.quarkus.domain.Customer; import de.samples.schulung.quarkus.domain.CustomersService; +import de.samples.schulung.quarkus.utilities.ProfileWithMockedLogger; import io.quarkus.arc.log.LoggerName; import io.quarkus.test.junit.QuarkusTest; -import jakarta.annotation.PreDestroy; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.context.Dependent; -import jakarta.enterprise.inject.Produces; -import jakarta.enterprise.inject.spi.InjectionPoint; +import io.quarkus.test.junit.TestProfile; import jakarta.inject.Inject; import org.jboss.logging.Logger; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import java.time.LocalDate; import java.time.Month; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -28,6 +21,7 @@ import static org.mockito.Mockito.*; @QuarkusTest +@TestProfile(ProfileWithMockedLogger.class) class CustomersLoggingTests { /* @@ -76,38 +70,4 @@ void shouldLogWhenCustomerCreated() throws InterruptedException { } - // just for this class, not for all tests! - @ApplicationScoped - static class LoggerMocksProducer { - private final Map loggers = new HashMap<>(); - - private Optional findLoggerName(InjectionPoint injectionPoint) { - return injectionPoint.getQualifiers() - .stream() - .filter(q -> q.annotationType().equals(LoggerName.class)) - .findFirst() - .map(LoggerName.class::cast); - } - - private Logger createLogger(String name) { - return Mockito.mock(Logger.class); - } - - @Produces - @Dependent - @LoggerName("") - Logger getMockedLogger(InjectionPoint injectionPoint) { - return findLoggerName(injectionPoint) - .map(LoggerName::value) - .map(name -> loggers.computeIfAbsent(name, this::createLogger)) - .orElseThrow(() -> new IllegalStateException("Unable to derive the logger name at " + injectionPoint)); - } - - @PreDestroy - void clear() { - loggers.clear(); - } - - } - } diff --git a/customer-api-provider/src/test/java/de/samples/schulung/quarkus/shared/LogPerformanceInterceptorTest.java b/customer-api-provider/src/test/java/de/samples/schulung/quarkus/shared/LogPerformanceInterceptorTest.java new file mode 100644 index 0000000..9e76bcb --- /dev/null +++ b/customer-api-provider/src/test/java/de/samples/schulung/quarkus/shared/LogPerformanceInterceptorTest.java @@ -0,0 +1,79 @@ +package de.samples.schulung.quarkus.shared; + +import de.samples.schulung.quarkus.utilities.ProfileWithMockedLogger; +import io.quarkus.arc.log.LoggerName; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.jboss.logging.Logger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.verifyNoInteractions; + +@QuarkusTest +@TestProfile(ProfileWithMockedLogger.class) +public class LogPerformanceInterceptorTest { + + @Inject + SampleBean sampleBean; + @LoggerName("performance") + Logger logger; + + @BeforeEach + void setup() { + Mockito.reset(logger); + } + + @Test + @DisplayName("[SHARED] No @LogPerformance -> no logging") + void given_whenNoAnnotation_thenDoNotLog() { + sampleBean.dontLog(); + verifyNoInteractions(logger); + } + + @Test + @DisplayName("[SHARED] @LogPerformance -> default level logging") + void given_whenAnnotation_thenLogDefaultLevel() { + sampleBean.logDefaultLevel(); + Mockito.verify(logger).logf( + eq(Logger.Level.INFO), + anyString(), + any(Object[].class) + ); + } + + @Test + @DisplayName("[SHARED] @LogPerformance(DEBUG) -> debug logging") + void given_whenAnnotationWithDebugLevel_thenLogDebugLevel() { + sampleBean.logDebugLevel(); + Mockito.verify(logger).logf( + eq(Logger.Level.DEBUG), + anyString(), + any(Object[].class) + ); + } + + + // Initialized for all tests, but does not disturb + @ApplicationScoped + static class SampleBean { + + void dontLog() { + } + + @LogPerformance + void logDefaultLevel() { + } + + @LogPerformance(Logger.Level.DEBUG) + void logDebugLevel() { + } + + } + +} diff --git a/customer-api-provider/src/test/java/de/samples/schulung/quarkus/utilities/ProfileWithMockedLogger.java b/customer-api-provider/src/test/java/de/samples/schulung/quarkus/utilities/ProfileWithMockedLogger.java new file mode 100644 index 0000000..b2a9534 --- /dev/null +++ b/customer-api-provider/src/test/java/de/samples/schulung/quarkus/utilities/ProfileWithMockedLogger.java @@ -0,0 +1,53 @@ +package de.samples.schulung.quarkus.utilities; + +import io.quarkus.arc.log.LoggerName; +import io.quarkus.test.junit.QuarkusTestProfile; +import jakarta.annotation.PreDestroy; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.InjectionPoint; +import org.jboss.logging.Logger; +import org.mockito.Mockito; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class ProfileWithMockedLogger implements QuarkusTestProfile { + + @ApplicationScoped + static class LoggerMocksProducer { + + private final Map loggers = new HashMap<>(); + + private Optional findLoggerName(InjectionPoint injectionPoint) { + return injectionPoint.getQualifiers() + .stream() + .filter(q -> q.annotationType().equals(LoggerName.class)) + .findFirst() + .map(LoggerName.class::cast); + } + + private Logger createLogger(String name) { + return Mockito.mock(Logger.class); + } + + @Produces + @Dependent + @LoggerName("") + Logger getMockedLogger(InjectionPoint injectionPoint) { + return findLoggerName(injectionPoint) + .map(LoggerName::value) + .map(name -> loggers.computeIfAbsent(name, this::createLogger)) + .orElseThrow(() -> new IllegalStateException("Unable to derive the logger name at " + injectionPoint)); + } + + @PreDestroy + void clear() { + loggers.clear(); + } + + } + +}