diff --git a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/DefaultModulithObservationConvention.java b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/DefaultModulithObservationConvention.java index d42bb4b5..0e9b7704 100644 --- a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/DefaultModulithObservationConvention.java +++ b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/DefaultModulithObservationConvention.java @@ -1,7 +1,20 @@ +/* + * Copyright 2024 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 org.springframework.modulith.observability; -import java.lang.reflect.Method; - import io.micrometer.common.KeyValues; import org.springframework.modulith.observability.ModulithObservations.HighKeys; @@ -11,40 +24,62 @@ import org.springframework.modulith.observability.ModulithObservations.LowKeys; * Default implementation of {@link ModulithObservationConvention}. * * @author Marcin Grzejszczak + * @author Oliver Drotbohm * @since 1.4 */ -public class DefaultModulithObservationConvention implements ModulithObservationConvention { +class DefaultModulithObservationConvention implements ModulithObservationConvention { + /* + * (non-Javadoc) + * @see io.micrometer.observation.ObservationConvention#getLowCardinalityKeyValues(io.micrometer.observation.Observation.Context) + */ @Override public KeyValues getLowCardinalityKeyValues(ModulithContext context) { - KeyValues keyValues = KeyValues.of(LowKeys.MODULE_KEY.withValue(context.getModule().getIdentifier().toString())); - if (isEventListener(context)) { - return keyValues.and(LowKeys.INVOCATION_TYPE.withValue("event-listener")); - } - return keyValues; + + var keyValues = KeyValues.of(LowKeys.MODULE_KEY.withValue(context.getModule().getIdentifier().toString())); + + return isEventListener(context) + ? keyValues.and(LowKeys.INVOCATION_TYPE.withValue("event-listener")) + : keyValues; + } + + /* + * + * (non-Javadoc) + * @see io.micrometer.observation.ObservationConvention#getHighCardinalityKeyValues(io.micrometer.observation.Observation.Context) + */ + @Override + public KeyValues getHighCardinalityKeyValues(ModulithContext context) { + + var method = context.getInvocation().getMethod(); + + return KeyValues.of(HighKeys.MODULE_METHOD.withValue(method.getName())); + } + + /* + * (non-Javadoc) + * @see io.micrometer.observation.ObservationConvention#getName() + */ + @Override + public String getName() { + return "module.requests"; + } + + /* + * (non-Javadoc) + * @see io.micrometer.observation.ObservationConvention#getContextualName(io.micrometer.observation.Observation.Context) + */ + @Override + public String getContextualName(ModulithContext context) { + return "[" + context.getApplicationName() + "] " + context.getModule().getDisplayName(); } private boolean isEventListener(ModulithContext context) { + try { return context.getModule().isEventListenerInvocation(context.getInvocation()); } catch (Exception e) { return false; } } - - @Override - public KeyValues getHighCardinalityKeyValues(ModulithContext context) { - Method method = context.getInvocation().getMethod(); - return KeyValues.of(HighKeys.MODULE_METHOD.withValue(method.getName())); - } - - @Override - public String getName() { - return "module.requests"; - } - - @Override - public String getContextualName(ModulithContext context) { - return "[" + context.getApplicationName() + "] " + context.getModule().getDisplayName(); - } } diff --git a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/DefaultObservedModule.java b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/DefaultObservedModule.java index e97fc963..f6d53086 100644 --- a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/DefaultObservedModule.java +++ b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/DefaultObservedModule.java @@ -25,7 +25,6 @@ import org.springframework.aop.support.AopUtils; import org.springframework.modulith.core.ApplicationModule; import org.springframework.modulith.core.ApplicationModuleIdentifier; import org.springframework.modulith.core.ApplicationModules; -import org.springframework.modulith.core.ArchitecturallyEvidentType; import org.springframework.modulith.core.ArchitecturallyEvidentType.ReferenceMethod; import org.springframework.modulith.core.FormattableType; import org.springframework.modulith.core.SpringBean; @@ -163,7 +162,6 @@ class DefaultObservedModule implements ObservedModule { var targetClass = advised.getTargetClass(); if (module.contains(targetClass)) { - return AopUtils.getMostSpecificMethod(method, targetClass); } diff --git a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModuleEntryInterceptor.java b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModuleEntryInterceptor.java index 8ac94a0a..8dc6645e 100644 --- a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModuleEntryInterceptor.java +++ b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModuleEntryInterceptor.java @@ -15,8 +15,8 @@ */ package org.springframework.modulith.observability; -import io.micrometer.common.KeyValue; import io.micrometer.observation.Observation; +import io.micrometer.observation.Observation.Scope; import io.micrometer.observation.ObservationRegistry; import java.util.HashMap; @@ -33,16 +33,22 @@ import org.springframework.modulith.core.ApplicationModuleIdentifier; import org.springframework.modulith.observability.ModulithObservations.LowKeys; import org.springframework.util.Assert; +/** + * {@link MethodInterceptor} to create {@link Observation}s. + * + * @author Marcin Grzejszczak + * @author Oliver Drotbohm + */ class ModuleEntryInterceptor implements MethodInterceptor { - private static Logger LOGGER = LoggerFactory.getLogger(ModuleEntryInterceptor.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ModuleEntryInterceptor.class); private static Map CACHE = new HashMap<>(); private static final ModulithObservationConvention DEFAULT = new DefaultModulithObservationConvention(); private final ObservedModule module; private final ObservationRegistry observationRegistry; - @Nullable private final ModulithObservationConvention customModulithObservationConvention; + private final @Nullable ModulithObservationConvention customModulithObservationConvention; private final Environment environment; /** @@ -103,7 +109,8 @@ class ModuleEntryInterceptor implements MethodInterceptor { String currentModule = null; if (currentObservation != null) { - KeyValue moduleKey = currentObservation.getContextView().getLowCardinalityKeyValue(LowKeys.MODULE_KEY.asString()); + + var moduleKey = currentObservation.getContextView().getLowCardinalityKeyValue(LowKeys.MODULE_KEY.asString()); currentModule = moduleKey != null ? moduleKey.getValue() : null; } @@ -116,18 +123,26 @@ class ModuleEntryInterceptor implements MethodInterceptor { LOGGER.trace("Entering {} via {}.", module.getDisplayName(), invokedMethod); - ModulithContext modulithContext = new ModulithContext(module, invocation, environment); + var modulithContext = new ModulithContext(module, invocation, environment); var observation = Observation.createNotStarted(customModulithObservationConvention, DEFAULT, () -> modulithContext, observationRegistry); - try (Observation.Scope scope = observation.start().openScope()) { - Object proceed = invocation.proceed(); + + try (Scope scope = observation.start().openScope()) { + + var proceed = invocation.proceed(); observation.event(ModulithObservations.Events.EVENT_PUBLICATION_SUCCESS); + return proceed; + } catch (Exception ex) { + observation.error(ex); observation.event(ModulithObservations.Events.EVENT_PUBLICATION_FAILURE); + throw ex; + } finally { + LOGGER.trace("Leaving {}", module.getDisplayName()); observation.stop(); } diff --git a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModuleEventListener.java b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModuleEventListener.java index 4eac054d..1b707dbc 100644 --- a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModuleEventListener.java +++ b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModuleEventListener.java @@ -39,13 +39,15 @@ public class ModuleEventListener implements ApplicationListener meterRegistry; /** - * Creates a new {@link ModuleEventListener} for the given {@link ApplicationModulesRuntime} and {@link ObservationRegistry} and {@link MeterRegistry}. + * Creates a new {@link ModuleEventListener} for the given {@link ApplicationModulesRuntime} and + * {@link ObservationRegistry} and {@link MeterRegistry}. * * @param runtime must not be {@literal null}. * @param observationRegistrySupplier must not be {@literal null}. + * @param meterRegistrySupplier must not be {@literal null}. */ - public ModuleEventListener(ApplicationModulesRuntime runtime, Supplier observationRegistrySupplier, - Supplier meterRegistrySupplier) { + public ModuleEventListener(ApplicationModulesRuntime runtime, + Supplier observationRegistrySupplier, Supplier meterRegistrySupplier) { Assert.notNull(runtime, "ApplicationModulesRuntime must not be null!"); Assert.notNull(observationRegistrySupplier, "ObservationRegistry must not be null!"); @@ -82,8 +84,10 @@ public class ModuleEventListener implements ApplicationListener { - @Override - default boolean supportsContext(Context context) { - return context instanceof ModulithContext; - } + /* + * (non-Javadoc) + * @see io.micrometer.observation.ObservationConvention#supportsContext(io.micrometer.observation.Observation.Context) + */ + @Override + default boolean supportsContext(Context context) { + return context instanceof ModulithContext; + } } diff --git a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModulithObservations.java b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModulithObservations.java index bf68bbe2..0ccfc2e9 100644 --- a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModulithObservations.java +++ b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModulithObservations.java @@ -1,8 +1,24 @@ +/* + * Copyright 2024 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 org.springframework.modulith.observability; import io.micrometer.common.docs.KeyName; import io.micrometer.observation.Observation; import io.micrometer.observation.Observation.Context; +import io.micrometer.observation.Observation.Event; import io.micrometer.observation.ObservationConvention; import io.micrometer.observation.docs.ObservationDocumentation; @@ -12,19 +28,40 @@ enum ModulithObservations implements ObservationDocumentation { * Observation related to entering a module. */ MODULE_ENTRY { - @Override public Class> getDefaultConvention() { + + /* + * (non-Javadoc) + * @see io.micrometer.observation.docs.ObservationDocumentation#getDefaultConvention() + */ + @Override + public Class> getDefaultConvention() { return DefaultModulithObservationConvention.class; } - @Override public KeyName[] getLowCardinalityKeyNames() { + /* + * (non-Javadoc) + * @see io.micrometer.observation.docs.ObservationDocumentation#getLowCardinalityKeyNames() + */ + @Override + public KeyName[] getLowCardinalityKeyNames() { return LowKeys.values(); } - @Override public KeyName[] getHighCardinalityKeyNames() { + /* + * (non-Javadoc) + * @see io.micrometer.observation.docs.ObservationDocumentation#getHighCardinalityKeyNames() + */ + @Override + public KeyName[] getHighCardinalityKeyNames() { return HighKeys.values(); } - @Override public Observation.Event[] getEvents() { + /* + * (non-Javadoc) + * @see io.micrometer.observation.docs.ObservationDocumentation#getEvents() + */ + @Override + public Observation.Event[] getEvents() { return Events.values(); } }; @@ -35,7 +72,13 @@ enum ModulithObservations implements ObservationDocumentation { * Name of the module. */ MODULE_KEY { - @Override public String asString() { + + /* + * (non-Javadoc) + * @see io.micrometer.common.docs.KeyName#asString() + */ + @Override + public String asString() { return "module.key"; } }, @@ -44,7 +87,13 @@ enum ModulithObservations implements ObservationDocumentation { * Type of invocation (e.g. event listener). */ INVOCATION_TYPE { - @Override public String asString() { + + /* + * (non-Javadoc) + * @see io.micrometer.common.docs.KeyName#asString() + */ + @Override + public String asString() { return "module.invocation-type"; } } @@ -55,23 +104,35 @@ enum ModulithObservations implements ObservationDocumentation { * Method executed on a module. */ MODULE_METHOD { - @Override public String asString() { + @Override + public String asString() { return "module.method"; } } } - enum Events implements Observation.Event { + enum Events implements Event { /** * Published when an event is sent successfully. */ EVENT_PUBLICATION_SUCCESS { - @Override public String getName() { + + /* + * (non-Javadoc) + * @see io.micrometer.observation.Observation.Event#getName() + */ + @Override + public String getName() { return "event.publication.success"; } - @Override public String getContextualName() { + /* + * (non-Javadoc) + * @see io.micrometer.observation.Observation.Event#getContextualName() + */ + @Override + public String getContextualName() { return getName(); } }, @@ -80,11 +141,22 @@ enum ModulithObservations implements ObservationDocumentation { * Published when an event is sent with a failure. */ EVENT_PUBLICATION_FAILURE { - @Override public String getName() { + + /* + * (non-Javadoc) + * @see io.micrometer.observation.Observation.Event#getName() + */ + @Override + public String getName() { return "event.publication.failure"; } - @Override public String getContextualName() { + /* + * (non-Javadoc) + * @see io.micrometer.observation.Observation.Event#getContextualName() + */ + @Override + public String getContextualName() { return getName(); } } diff --git a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/SpringDataRestModuleObservabilityBeanPostProcessor.java b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/SpringDataRestModuleObservabilityBeanPostProcessor.java index c7c8cdf4..6ab821f2 100644 --- a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/SpringDataRestModuleObservabilityBeanPostProcessor.java +++ b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/SpringDataRestModuleObservabilityBeanPostProcessor.java @@ -19,10 +19,8 @@ import io.micrometer.observation.ObservationRegistry; import java.util.function.Supplier; -import org.aopalliance.aop.Advice; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; -import org.springframework.aop.Advisor; import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; @@ -30,6 +28,7 @@ import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.env.Environment; import org.springframework.data.rest.webmvc.BasePathAwareController; import org.springframework.data.rest.webmvc.RootResourceInformation; +import org.springframework.lang.Nullable; import org.springframework.modulith.core.ApplicationModule; import org.springframework.modulith.core.ApplicationModules; import org.springframework.modulith.runtime.ApplicationModulesRuntime; @@ -37,22 +36,25 @@ import org.springframework.util.Assert; /** * @author Oliver Drotbohm + * @author Marcin Grzejszczak */ -public class SpringDataRestModuleObservabilityBeanPostProcessor extends ModuleObservabilitySupport implements BeanPostProcessor { +public class SpringDataRestModuleObservabilityBeanPostProcessor extends ModuleObservabilitySupport + implements BeanPostProcessor { private final ApplicationModulesRuntime runtime; private final Supplier observationRegistry; private final Environment environment; /** - * Creates a new {@link SpringDataRestModuleObservabilityBeanPostProcessor} for the given {@link ApplicationModulesRuntime} - * and {@link ObservationRegistry}. + * Creates a new {@link SpringDataRestModuleObservabilityBeanPostProcessor} for the given + * {@link ApplicationModulesRuntime} and {@link ObservationRegistry}. * * @param runtime must not be {@literal null}. * @param observationRegistry must not be {@literal null}. * @param environment must not be {@literal null}. */ - public SpringDataRestModuleObservabilityBeanPostProcessor(ApplicationModulesRuntime runtime, Supplier observationRegistry, Environment environment) { + public SpringDataRestModuleObservabilityBeanPostProcessor(ApplicationModulesRuntime runtime, + Supplier observationRegistry, Environment environment) { Assert.notNull(runtime, "ApplicationModulesRuntime must not be null!"); Assert.notNull(observationRegistry, "ObservationRegistry must not be null!"); @@ -75,8 +77,8 @@ public class SpringDataRestModuleObservabilityBeanPostProcessor extends ModuleOb return bean; } - Advice interceptor = new DataRestControllerInterceptor(runtime, observationRegistry, environment); - Advisor advisor = new DefaultPointcutAdvisor(interceptor); + var interceptor = new DataRestControllerInterceptor(runtime, observationRegistry, environment); + var advisor = new DefaultPointcutAdvisor(interceptor); return addAdvisor(bean, advisor, it -> it.setProxyTargetClass(true)); } @@ -90,11 +92,12 @@ public class SpringDataRestModuleObservabilityBeanPostProcessor extends ModuleOb /** * Creates a new {@link DataRestControllerInterceptor} for the given {@link ApplicationModules} and {@link Tracer}. * - * @param modules must not be {@literal null}. + * @param modules must not be {@literal null}. * @param observationRegistry must not be {@literal null}. - * @param environment must not be {@literal null}. + * @param environment must not be {@literal null}. */ - private DataRestControllerInterceptor(Supplier modules, Supplier observationRegistry, + private DataRestControllerInterceptor(Supplier modules, + Supplier observationRegistry, Environment environment) { Assert.notNull(modules, "ApplicationModules must not be null!"); @@ -124,6 +127,7 @@ public class SpringDataRestModuleObservabilityBeanPostProcessor extends ModuleOb return ModuleEntryInterceptor.of(observed, observationRegistry.get(), environment).invoke(invocation); } + @Nullable private ApplicationModule getModuleFrom(Object[] arguments) { for (Object argument : arguments) { diff --git a/spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleObservabilityBeanPostProcessorIntegrationTests.java b/spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleObservabilityBeanPostProcessorIntegrationTests.java index bc94420b..24b4546d 100644 --- a/spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleObservabilityBeanPostProcessorIntegrationTests.java +++ b/spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleObservabilityBeanPostProcessorIntegrationTests.java @@ -38,13 +38,14 @@ import org.springframework.scheduling.annotation.AsyncAnnotationAdvisor; * Integration tests for {@link ModuleObservabilityBeanPostProcessor}. * * @author Oliver Drotbohm + * @author Marcin Grzejszczak */ class ModuleObservabilityBeanPostProcessorIntegrationTests { @Test void decoratesExposedComponentsWithTracingInterceptor() { - SampleComponent bean = SpringApplication + var bean = SpringApplication .run(new Class[] { ExampleApplication.class, ModuleObservabilityConfiguration.class }, new String[] {}) .getBean(SampleComponent.class); @@ -62,13 +63,15 @@ class ModuleObservabilityBeanPostProcessorIntegrationTests { @Configuration static class ModuleObservabilityConfiguration { - @Bean ModuleObservabilityBeanPostProcessor foo(ConfigurableApplicationContext context) { + @Bean + ModuleObservabilityBeanPostProcessor foo(ConfigurableApplicationContext context) { var runtime = ApplicationRuntime.of(context); var modulesRuntime = new ApplicationModulesRuntime(() -> TestApplicationModules.of(ExampleApplication.class), runtime); - return new ModuleObservabilityBeanPostProcessor(modulesRuntime, () -> ObservationRegistry.NOOP, context.getBeanFactory(), context.getEnvironment()); + return new ModuleObservabilityBeanPostProcessor(modulesRuntime, () -> ObservationRegistry.NOOP, + context.getBeanFactory(), context.getEnvironment()); } } diff --git a/spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleObservabilityBeanPostProcessorUnitTests.java b/spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleObservabilityBeanPostProcessorUnitTests.java index 694839ac..248fd084 100644 --- a/spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleObservabilityBeanPostProcessorUnitTests.java +++ b/spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleObservabilityBeanPostProcessorUnitTests.java @@ -32,6 +32,7 @@ import org.springframework.modulith.runtime.ApplicationModulesRuntime; * Unit tests for {@link ModuleObservabilityBeanPostProcessor}. * * @author Oliver Drotbohm + * @author Marcin Grzejszczak */ class ModuleObservabilityBeanPostProcessorUnitTests {