diff --git a/pom.xml b/pom.xml index 9bb75370..fa7d42a2 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,8 @@ 3.1.1 1.17.6 3.1.0 + + 1.5.0-SNAPSHOT @@ -97,6 +99,14 @@ limitations under the License. pom import + + + io.micrometer + micrometer-tracing-bom + ${micrometer-tracing.version} + pom + import + org.springframework.boot spring-boot-dependencies diff --git a/spring-modulith-observability/pom.xml b/spring-modulith-observability/pom.xml index bd0f2363..6a285292 100644 --- a/spring-modulith-observability/pom.xml +++ b/spring-modulith-observability/pom.xml @@ -13,7 +13,11 @@ org.springframework.modulith.observability - 2021.0.0 + + 1.0.4 + ${project.basedir}/src/main/java/ + .* + ${project.build.directory}/observation-documentation/ @@ -73,12 +77,24 @@ true + + io.micrometer + micrometer-core + true + + io.zipkin.brave brave true + + io.opentelemetry + opentelemetry-api + true + + @@ -107,4 +123,39 @@ + + + + org.codehaus.mojo + exec-maven-plugin + + + generate-docs + prepare-package + + java + + + io.micrometer.docs.DocsGeneratorCommand + true + + ${micrometer-docs-generator.inputPath} + ${micrometer-docs-generator.inclusionPattern} + ${micrometer-docs-generator.outputPath} + + + + + + + io.micrometer + micrometer-docs-generator + ${micrometer-docs-generator.version} + jar + + + + + + 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 new file mode 100644 index 00000000..d42bb4b5 --- /dev/null +++ b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/DefaultModulithObservationConvention.java @@ -0,0 +1,50 @@ +package org.springframework.modulith.observability; + +import java.lang.reflect.Method; + +import io.micrometer.common.KeyValues; + +import org.springframework.modulith.observability.ModulithObservations.HighKeys; +import org.springframework.modulith.observability.ModulithObservations.LowKeys; + +/** + * Default implementation of {@link ModulithObservationConvention}. + * + * @author Marcin Grzejszczak + * @since 1.4 + */ +public class DefaultModulithObservationConvention implements ModulithObservationConvention { + + @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; + } + + 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 5a54e29a..e97fc963 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,6 +25,7 @@ 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; 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 ee6ffd74..8ac94a0a 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,47 +15,79 @@ */ package org.springframework.modulith.observability; -import io.micrometer.tracing.BaggageInScope; -import io.micrometer.tracing.Tracer; -import io.micrometer.tracing.Tracer.SpanInScope; +import io.micrometer.common.KeyValue; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; +import org.springframework.lang.Nullable; +import org.springframework.modulith.core.ApplicationModuleIdentifier; +import org.springframework.modulith.observability.ModulithObservations.LowKeys; import org.springframework.util.Assert; class ModuleEntryInterceptor implements MethodInterceptor { private static Logger LOGGER = LoggerFactory.getLogger(ModuleEntryInterceptor.class); - private static Map CACHE = new HashMap<>(); - private static final String MODULE_KEY = ModuleTracingBeanPostProcessor.MODULE_BAGGAGE_KEY; + private static Map CACHE = new HashMap<>(); + + private static final ModulithObservationConvention DEFAULT = new DefaultModulithObservationConvention(); private final ObservedModule module; - private final Tracer tracer; + private final ObservationRegistry observationRegistry; + @Nullable private final ModulithObservationConvention customModulithObservationConvention; + private final Environment environment; /** - * Creates a new {@link ModuleEntryInterceptor} for the given {@link ObservedModule} and {@link Tracer}. + * Creates a new {@link ModuleEntryInterceptor} for the given {@link ObservedModule} and {@link ObservationRegistry}. * * @param module must not be {@literal null}. - * @param tracer must not be {@literal null}. + * @param observationRegistry must not be {@literal null}. + * @param environment must not be {@literal null}. */ - private ModuleEntryInterceptor(ObservedModule module, Tracer tracer) { - - Assert.notNull(module, "ObservedModule must not be null!"); - Assert.notNull(tracer, "Tracer must not be null!"); - - this.module = module; - this.tracer = tracer; + private ModuleEntryInterceptor(ObservedModule module, ObservationRegistry observationRegistry, + Environment environment) { + this(module, observationRegistry, null, environment); } - public static ModuleEntryInterceptor of(ObservedModule module, Tracer tracer) { + /** + * Creates a new {@link ModuleEntryInterceptor} for the given {@link ObservedModule}, {@link ObservationRegistry} and + * {@link ModulithObservationConvention}. + * + * @param module must not be {@literal null}. + * @param observationRegistry must not be {@literal null}. + * @param custom must not be {@literal null}. + * @param environment must not be {@literal null}. + */ + private ModuleEntryInterceptor(ObservedModule module, ObservationRegistry observationRegistry, + ModulithObservationConvention custom, Environment environment) { - return CACHE.computeIfAbsent(module.getName(), __ -> { - return new ModuleEntryInterceptor(module, tracer); + Assert.notNull(module, "ObservedModule must not be null!"); + Assert.notNull(observationRegistry, "ObservationRegistry must not be null!"); + + this.module = module; + this.observationRegistry = observationRegistry; + this.customModulithObservationConvention = custom; + this.environment = environment; + } + + public static ModuleEntryInterceptor of(ObservedModule module, ObservationRegistry observationRegistry, + Environment environment) { + return of(module, observationRegistry, null, environment); + } + + public static ModuleEntryInterceptor of(ObservedModule module, ObservationRegistry observationRegistry, + ModulithObservationConvention custom, Environment environment) { + + return CACHE.computeIfAbsent(module.getIdentifier(), __ -> { + return new ModuleEntryInterceptor(module, observationRegistry, custom, environment); }); } @@ -66,12 +98,17 @@ class ModuleEntryInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { - var moduleName = module.getName(); - var currentSpan = tracer.currentSpan(); - var currentBaggage = tracer.getBaggage(MODULE_KEY); - var currentModule = currentBaggage != null ? currentBaggage.get() : null; + var moduleIdentifier = module.getIdentifier(); + var currentObservation = observationRegistry.getCurrentObservation(); + String currentModule = null; - if (currentSpan != null && moduleName.equals(currentModule)) { + if (currentObservation != null) { + KeyValue moduleKey = currentObservation.getContextView().getLowCardinalityKeyValue(LowKeys.MODULE_KEY.asString()); + currentModule = moduleKey != null ? moduleKey.getValue() : null; + } + + if (currentObservation != null && Objects.equals(moduleIdentifier.toString(), currentModule)) { + // Same module return invocation.proceed(); } @@ -79,21 +116,20 @@ class ModuleEntryInterceptor implements MethodInterceptor { LOGGER.trace("Entering {} via {}.", module.getDisplayName(), invokedMethod); - var span = tracer.spanBuilder() - .name(moduleName) - .tag("module.method", invokedMethod) - .tag(MODULE_KEY, moduleName) - .start(); - - try (SpanInScope ws = tracer.withSpan(span); - BaggageInScope baggage = tracer.createBaggageInScope(MODULE_KEY, moduleName)) { - - return invocation.proceed(); - + ModulithContext 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(); + 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()); - span.end(); + 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 64648865..4eac054d 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 @@ -15,13 +15,17 @@ */ package org.springframework.modulith.observability; -import io.micrometer.tracing.Tracer; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.observation.Observation.Event; +import io.micrometer.observation.ObservationRegistry; import java.util.function.Supplier; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.PayloadApplicationEvent; +import org.springframework.modulith.observability.ModulithObservations.Events; import org.springframework.modulith.runtime.ApplicationModulesRuntime; import org.springframework.util.Assert; @@ -31,21 +35,25 @@ import org.springframework.util.Assert; public class ModuleEventListener implements ApplicationListener { private final ApplicationModulesRuntime runtime; - private final Supplier tracer; + private final Supplier observationRegistry; + private final Supplier meterRegistry; /** - * Creates a new {@link ModuleEventListener} for the given {@link ApplicationModulesRuntime} and {@link Tracer}. + * Creates a new {@link ModuleEventListener} for the given {@link ApplicationModulesRuntime} and {@link ObservationRegistry} and {@link MeterRegistry}. * * @param runtime must not be {@literal null}. - * @param tracer must not be {@literal null}. + * @param observationRegistrySupplier must not be {@literal null}. */ - public ModuleEventListener(ApplicationModulesRuntime runtime, Supplier tracer) { + public ModuleEventListener(ApplicationModulesRuntime runtime, Supplier observationRegistrySupplier, + Supplier meterRegistrySupplier) { Assert.notNull(runtime, "ApplicationModulesRuntime must not be null!"); - Assert.notNull(tracer, "Tracer must not be null!"); + Assert.notNull(observationRegistrySupplier, "ObservationRegistry must not be null!"); + Assert.notNull(meterRegistrySupplier, "MeterRegistry must not be null!"); this.runtime = runtime; - this.tracer = tracer; + this.observationRegistry = observationRegistrySupplier; + this.meterRegistry = meterRegistrySupplier; } /* @@ -74,12 +82,20 @@ public class ModuleEventListener implements ApplicationListener tracer; + private final Supplier observationRegistry; private final Map advisors; private final ConfigurableListableBeanFactory factory; + private final Environment environment; /** - * Creates a new {@link ModuleTracingBeanPostProcessor} for the given {@link ApplicationModulesRuntime} and - * {@link Tracer}. + * Creates a new {@link ModuleObservabilityBeanPostProcessor} for the given {@link ApplicationModulesRuntime} and + * {@link ObservationRegistry}. * * @param runtime must not be {@literal null}. - * @param tracer must not be {@literal null}. + * @param observationRegistry must not be {@literal null}. + * @param factory must not be {@literal null}. + * @param environment must not be {@literal null}. */ - public ModuleTracingBeanPostProcessor(ApplicationModulesRuntime runtime, Supplier tracer, - ConfigurableListableBeanFactory factory) { + public ModuleObservabilityBeanPostProcessor(ApplicationModulesRuntime runtime, + Supplier observationRegistry, ConfigurableListableBeanFactory factory, + Environment environment) { Assert.notNull(runtime, "ApplicationModulesRuntime must not be null!"); - Assert.notNull(tracer, "Tracer must not be null!"); + Assert.notNull(observationRegistry, "ObservationRegistry must not be null!"); + Assert.notNull(factory, "BeanFactory must not be null!"); + Assert.notNull(environment, "Environment must not be null!"); this.runtime = runtime; - this.tracer = tracer; + this.observationRegistry = observationRegistry; this.advisors = new HashMap<>(); this.factory = factory; + this.environment = environment; } /* @@ -99,15 +108,17 @@ public class ModuleTracingBeanPostProcessor extends ModuleTracingSupport impleme var modules = runtime.get(); - return modules.getModuleByType(type.getName()).map(DefaultObservedModule::new).map(it -> { + return modules.getModuleByType(type.getName()) + .map(DefaultObservedModule::new) + .map(it -> { - var moduleType = it.getObservedModuleType(type, modules); + var moduleType = it.getObservedModuleType(type, modules); - return moduleType != null // - ? addAdvisor(bean, getOrBuildAdvisor(it, moduleType)) // - : bean; + return moduleType != null // + ? addAdvisor(bean, getOrBuildAdvisor(it, moduleType)) // + : bean; - }).orElse(bean); + }).orElse(bean); } private boolean isInfrastructureBean(String beanName) { @@ -129,8 +140,9 @@ public class ModuleTracingBeanPostProcessor extends ModuleTracingSupport impleme private Advisor getOrBuildAdvisor(ObservedModule module, ObservedModuleType type) { - return advisors.computeIfAbsent(module.getName(), __ -> { - return new ApplicationModuleObservingAdvisor(type, ModuleEntryInterceptor.of(module, tracer.get())); + return advisors.computeIfAbsent(module.getIdentifier().toString(), __ -> { + return new ApplicationModuleObservingAdvisor(type, + ModuleEntryInterceptor.of(module, observationRegistry.get(), environment)); }); } diff --git a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModuleTracingSupport.java b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModuleObservabilitySupport.java similarity index 97% rename from spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModuleTracingSupport.java rename to spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModuleObservabilitySupport.java index 49ed862d..1dc64a5a 100644 --- a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModuleTracingSupport.java +++ b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModuleObservabilitySupport.java @@ -31,7 +31,7 @@ import org.springframework.scheduling.annotation.AsyncAnnotationAdvisor; /** * @author Oliver Drotbohm */ -class ModuleTracingSupport implements BeanClassLoaderAware, BeanFactoryAware { +class ModuleObservabilitySupport implements BeanClassLoaderAware, BeanFactoryAware { private ClassLoader classLoader; private @Nullable AbstractAutoProxyCreator creator; diff --git a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModulePassingObservationFilter.java b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModulePassingObservationFilter.java new file mode 100644 index 00000000..4e20bd47 --- /dev/null +++ b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModulePassingObservationFilter.java @@ -0,0 +1,45 @@ +/* + * Copyright 2022-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.observation.Observation; +import io.micrometer.observation.ObservationFilter; + +/** + * Ensures that {@link ModulithObservations.LowKeys#MODULE_KEY} gets propagated from parent + * to child. + * + * @author Marcin Grzejszczak + * @since 1.4 + */ +public class ModulePassingObservationFilter implements ObservationFilter { + + @Override + public Observation.Context map(Observation.Context context) { + if (isModuleKeyValueAbsentInCurrent(context) && isModuleKeyValuePresentInParent(context)) { + return context.addLowCardinalityKeyValue(ModulithObservations.LowKeys.MODULE_KEY.withValue(context.getParentObservation().getContextView().getLowCardinalityKeyValue(ModulithObservations.LowKeys.MODULE_KEY.asString()).getValue())); + } + return context; + } + + private static boolean isModuleKeyValueAbsentInCurrent(Observation.ContextView context) { + return context.getLowCardinalityKeyValue(ModulithObservations.LowKeys.MODULE_KEY.asString()) == null; + } + + private static boolean isModuleKeyValuePresentInParent(Observation.ContextView context) { + return context.getParentObservation() != null && context.getParentObservation().getContextView().getLowCardinalityKeyValue(ModulithObservations.LowKeys.MODULE_KEY.asString()) != null; + } +} \ No newline at end of file diff --git a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModulithContext.java b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModulithContext.java new file mode 100644 index 00000000..b6a5b189 --- /dev/null +++ b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModulithContext.java @@ -0,0 +1,39 @@ +package org.springframework.modulith.observability; + +import io.micrometer.observation.Observation; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.core.env.Environment; + +/** + * A {@link Observation.Context} for Modulithic applications. + * + * @author Marcin Grzejsczak + * @since 1.4 + */ +public class ModulithContext extends Observation.Context { + + private final ObservedModule module; + + private final MethodInvocation invocation; + + private final String applicationName; + + public ModulithContext(ObservedModule module, MethodInvocation invocation, Environment environment) { + this.module = module; + this.invocation = invocation; + this.applicationName = environment.getProperty("spring.application.name"); + } + + public ObservedModule getModule() { + return module; + } + + public MethodInvocation getInvocation() { + return invocation; + } + + public String getApplicationName() { + return applicationName; + } +} diff --git a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModulithMetrics.java b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModulithMetrics.java new file mode 100644 index 00000000..377d5531 --- /dev/null +++ b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModulithMetrics.java @@ -0,0 +1,50 @@ +package org.springframework.modulith.observability; + +import io.micrometer.common.docs.KeyName; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.docs.MeterDocumentation; + +enum ModulithMetrics implements MeterDocumentation { + + /** + * Counter for the events. + */ + EVENTS { + @Override + public String getName() { + return "modulith.events.processed"; + } + + @Override + public Meter.Type getType() { + return Meter.Type.COUNTER; + } + + @Override + public KeyName[] getKeyNames() { + return LowKeys.values(); + } + }; + + enum LowKeys implements KeyName { + /** + * Type of the emitted event. + */ + EVENT_TYPE { + @Override + public String asString() { + return "event.type"; + } + }, + + /** + * Name of the module. + */ + MODULE_NAME { + @Override + public String asString() { + return "module.name"; + } + } + } +} diff --git a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModulithObservationConvention.java b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModulithObservationConvention.java new file mode 100644 index 00000000..b6df45b4 --- /dev/null +++ b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModulithObservationConvention.java @@ -0,0 +1,18 @@ +package org.springframework.modulith.observability; + +import io.micrometer.observation.Observation.Context; +import io.micrometer.observation.ObservationConvention; + +/** + * {@link ObservationConvention} for {@link ModulithContext}. + * + * @author Marcin Grzejszczak + * @since 1.4 + */ +public interface ModulithObservationConvention extends ObservationConvention { + + @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 new file mode 100644 index 00000000..bf68bbe2 --- /dev/null +++ b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModulithObservations.java @@ -0,0 +1,92 @@ +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.ObservationConvention; +import io.micrometer.observation.docs.ObservationDocumentation; + +enum ModulithObservations implements ObservationDocumentation { + + /** + * Observation related to entering a module. + */ + MODULE_ENTRY { + @Override public Class> getDefaultConvention() { + return DefaultModulithObservationConvention.class; + } + + @Override public KeyName[] getLowCardinalityKeyNames() { + return LowKeys.values(); + } + + @Override public KeyName[] getHighCardinalityKeyNames() { + return HighKeys.values(); + } + + @Override public Observation.Event[] getEvents() { + return Events.values(); + } + }; + + enum LowKeys implements KeyName { + + /** + * Name of the module. + */ + MODULE_KEY { + @Override public String asString() { + return "module.key"; + } + }, + + /** + * Type of invocation (e.g. event listener). + */ + INVOCATION_TYPE { + @Override public String asString() { + return "module.invocation-type"; + } + } + } + + enum HighKeys implements KeyName { + /** + * Method executed on a module. + */ + MODULE_METHOD { + @Override public String asString() { + return "module.method"; + } + } + } + + enum Events implements Observation.Event { + + /** + * Published when an event is sent successfully. + */ + EVENT_PUBLICATION_SUCCESS { + @Override public String getName() { + return "event.publication.success"; + } + + @Override public String getContextualName() { + return getName(); + } + }, + + /** + * Published when an event is sent with a failure. + */ + EVENT_PUBLICATION_FAILURE { + @Override public String getName() { + return "event.publication.failure"; + } + + @Override public String getContextualName() { + return getName(); + } + } + } +} diff --git a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ObservedModule.java b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ObservedModule.java index 763c0d8a..44a99baf 100644 --- a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ObservedModule.java +++ b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ObservedModule.java @@ -24,11 +24,15 @@ import org.springframework.modulith.core.ApplicationModuleIdentifier; import org.springframework.modulith.core.ApplicationModules; import com.tngtech.archunit.core.domain.JavaClass; +import org.springframework.modulith.core.ArchitecturallyEvidentType; /** + * Information about observed module. + * * @author Oliver Drotbohm + * @since 1.4 */ -interface ObservedModule { +public interface ObservedModule { /** * Returns the name of the application module. @@ -86,7 +90,7 @@ interface ObservedModule { * method invocation on a Spring bean. * * @param invocation must not be {@literal null}. - * @since 1.3 + * @since 1.4 */ boolean isEventListenerInvocation(MethodInvocation invocation); } diff --git a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ObservedModuleType.java b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ObservedModuleType.java index c0c616c5..0362cb2c 100644 --- a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ObservedModuleType.java +++ b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ObservedModuleType.java @@ -37,8 +37,9 @@ import com.tngtech.archunit.core.domain.JavaClass; * Represents a type in an {@link ObservedModule}. * * @author Oliver Drotbohm + * @since 1.4 */ -class ObservedModuleType { +public class ObservedModuleType { private static final Collection> IGNORED_TYPES = List.of(Advised.class, TargetClassAware.class); private static final Predicate IS_USER_METHOD = it -> !Modifier.isPrivate(it.getModifiers()) diff --git a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/SpringDataRestModuleTracingBeanPostProcessor.java b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/SpringDataRestModuleObservabilityBeanPostProcessor.java similarity index 67% rename from spring-modulith-observability/src/main/java/org/springframework/modulith/observability/SpringDataRestModuleTracingBeanPostProcessor.java rename to spring-modulith-observability/src/main/java/org/springframework/modulith/observability/SpringDataRestModuleObservabilityBeanPostProcessor.java index 830baf25..c7c8cdf4 100644 --- a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/SpringDataRestModuleTracingBeanPostProcessor.java +++ b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/SpringDataRestModuleObservabilityBeanPostProcessor.java @@ -15,7 +15,7 @@ */ package org.springframework.modulith.observability; -import io.micrometer.tracing.Tracer; +import io.micrometer.observation.ObservationRegistry; import java.util.function.Supplier; @@ -27,6 +27,7 @@ import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; 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.modulith.core.ApplicationModule; @@ -37,25 +38,28 @@ import org.springframework.util.Assert; /** * @author Oliver Drotbohm */ -public class SpringDataRestModuleTracingBeanPostProcessor extends ModuleTracingSupport implements BeanPostProcessor { +public class SpringDataRestModuleObservabilityBeanPostProcessor extends ModuleObservabilitySupport implements BeanPostProcessor { private final ApplicationModulesRuntime runtime; - private final Supplier tracer; + private final Supplier observationRegistry; + private final Environment environment; /** - * Creates a new {@link SpringDataRestModuleTracingBeanPostProcessor} for the given {@link ApplicationModulesRuntime} - * and {@link Tracer}. + * Creates a new {@link SpringDataRestModuleObservabilityBeanPostProcessor} for the given {@link ApplicationModulesRuntime} + * and {@link ObservationRegistry}. * * @param runtime must not be {@literal null}. - * @param tracer must not be {@literal null}. + * @param observationRegistry must not be {@literal null}. + * @param environment must not be {@literal null}. */ - public SpringDataRestModuleTracingBeanPostProcessor(ApplicationModulesRuntime runtime, Supplier tracer) { + public SpringDataRestModuleObservabilityBeanPostProcessor(ApplicationModulesRuntime runtime, Supplier observationRegistry, Environment environment) { Assert.notNull(runtime, "ApplicationModulesRuntime must not be null!"); - Assert.notNull(tracer, "Tracer must not be null!"); + Assert.notNull(observationRegistry, "ObservationRegistry must not be null!"); this.runtime = runtime; - this.tracer = tracer; + this.observationRegistry = observationRegistry; + this.environment = environment; } /* @@ -71,7 +75,7 @@ public class SpringDataRestModuleTracingBeanPostProcessor extends ModuleTracingS return bean; } - Advice interceptor = new DataRestControllerInterceptor(runtime, tracer); + Advice interceptor = new DataRestControllerInterceptor(runtime, observationRegistry, environment); Advisor advisor = new DefaultPointcutAdvisor(interceptor); return addAdvisor(bean, advisor, it -> it.setProxyTargetClass(true)); @@ -80,21 +84,26 @@ public class SpringDataRestModuleTracingBeanPostProcessor extends ModuleTracingS private static class DataRestControllerInterceptor implements MethodInterceptor { private final Supplier modules; - private final Supplier tracer; + private final Supplier observationRegistry; + private final Environment environment; /** * Creates a new {@link DataRestControllerInterceptor} for the given {@link ApplicationModules} and {@link Tracer}. * - * @param modules must not be {@literal null}. - * @param tracer 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}. */ - private DataRestControllerInterceptor(Supplier modules, Supplier tracer) { + private DataRestControllerInterceptor(Supplier modules, Supplier observationRegistry, + Environment environment) { Assert.notNull(modules, "ApplicationModules must not be null!"); - Assert.notNull(tracer, "Tracer must not be null!"); + Assert.notNull(observationRegistry, "ObservationRegistry must not be null!"); + Assert.notNull(environment, "Environment must not be null!"); this.modules = modules; - this.tracer = tracer; + this.observationRegistry = observationRegistry; + this.environment = environment; } /* @@ -112,7 +121,7 @@ public class SpringDataRestModuleTracingBeanPostProcessor extends ModuleTracingS var observed = new DefaultObservedModule(module); - return ModuleEntryInterceptor.of(observed, tracer.get()).invoke(invocation); + return ModuleEntryInterceptor.of(observed, observationRegistry.get(), environment).invoke(invocation); } private ApplicationModule getModuleFrom(Object[] arguments) { diff --git a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/autoconfigure/ModuleObservabilityAutoConfiguration.java b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/autoconfigure/ModuleObservabilityAutoConfiguration.java index 2023d3d5..2254e43e 100644 --- a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/autoconfigure/ModuleObservabilityAutoConfiguration.java +++ b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/autoconfigure/ModuleObservabilityAutoConfiguration.java @@ -15,14 +15,12 @@ */ package org.springframework.modulith.observability.autoconfigure; -import brave.TracingCustomizer; -import brave.baggage.BaggageField; -import brave.baggage.BaggagePropagationConfig; -import brave.baggage.BaggagePropagationCustomizer; import brave.handler.MutableSpan; import brave.handler.SpanHandler; import brave.propagation.TraceContext; -import io.micrometer.tracing.Tracer; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.observation.ObservationFilter; +import io.micrometer.observation.ObservationRegistry; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -34,9 +32,11 @@ import org.springframework.boot.task.SimpleAsyncTaskExecutorCustomizer; import org.springframework.boot.task.ThreadPoolTaskExecutorCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import org.springframework.core.task.support.ContextPropagatingTaskDecorator; import org.springframework.modulith.observability.ModuleEventListener; -import org.springframework.modulith.observability.ModuleTracingBeanPostProcessor; +import org.springframework.modulith.observability.ModuleObservabilityBeanPostProcessor; +import org.springframework.modulith.observability.ModulePassingObservationFilter; import org.springframework.modulith.runtime.ApplicationModulesRuntime; /** @@ -47,17 +47,18 @@ import org.springframework.modulith.runtime.ApplicationModulesRuntime; class ModuleObservabilityAutoConfiguration { @Bean - static ModuleTracingBeanPostProcessor moduleTracingBeanPostProcessor(ApplicationModulesRuntime runtime, - ObjectProvider tracer, ConfigurableListableBeanFactory factory) { - return new ModuleTracingBeanPostProcessor(runtime, () -> tracer.getObject(), factory); + static ModuleObservabilityBeanPostProcessor moduleTracingBeanPostProcessor(ApplicationModulesRuntime runtime, + ObjectProvider observationRegistry, ConfigurableListableBeanFactory factory, Environment environment) { + return new ModuleObservabilityBeanPostProcessor(runtime, observationRegistry::getObject, factory, environment); } @Bean static ModuleEventListener tracingModuleEventListener(ApplicationModulesRuntime runtime, - ObjectProvider tracer) { - return new ModuleEventListener(runtime, () -> tracer.getObject()); + ObjectProvider observationRegistry, ObjectProvider meterRegistry) { + return new ModuleEventListener(runtime, observationRegistry::getObject, meterRegistry::getObject); } + // TODO: Have a custom thread pool for modulith @Bean @ConditionalOnThreading(Threading.VIRTUAL) SimpleAsyncTaskExecutorCustomizer simpleAsyncTaskExecutorCustomizer() { @@ -70,51 +71,31 @@ class ModuleObservabilityAutoConfiguration { return executor -> executor.setTaskDecorator(new ContextPropagatingTaskDecorator()); } - /** - * Brave-specific auto configuration. - * - * @author Oliver Drotbohm - */ - @ConditionalOnClass(TracingCustomizer.class) - static class ModulithsBraveIntegrationAutoConfiguration { + @Bean + ObservationFilter modulePassingObservationFilter() { + return new ModulePassingObservationFilter(); + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(SpanHandler.class) + static class BraveConfiguration { + /** + * Corresponds to {@link ModulithObservations.LowKeys#MODULE_KEY} + */ + private static final String MODULE_KEY_TAG_NAME = "module.key"; @Bean - BaggagePropagationCustomizer moduleBaggagePropagationCustomizer() { - - return builder -> builder - .add(BaggagePropagationConfig.SingleBaggageField - .local(BaggageField.create(ModuleTracingBeanPostProcessor.MODULE_BAGGAGE_KEY))); - } - - @Bean - SpanHandler spanHandler() { - + SpanHandler localServiceNameRenamingSpanHandler() { return new SpanHandler() { - - /* - * (non-Javadoc) - * @see brave.handler.SpanHandler#end(brave.propagation.TraceContext, brave.handler.MutableSpan, brave.handler.SpanHandler.Cause) - */ - @Override - public boolean end(TraceContext context, MutableSpan span, Cause cause) { - - String value = span.tag(ModuleTracingBeanPostProcessor.MODULE_BAGGAGE_KEY); - - if (value != null) { - span.localServiceName(value); - return true; + @Override public boolean end(TraceContext context, MutableSpan span, Cause cause) { + String tag = span.tag(MODULE_KEY_TAG_NAME); + if (tag != null) { + span.localServiceName(tag); } - - BaggageField field = BaggageField.getByName(context, ModuleTracingBeanPostProcessor.MODULE_BAGGAGE_KEY); - value = field.getValue(); - - if (value != null) { - span.localServiceName(value); - } - - return true; + return super.end(context, span, cause); } }; } } + } diff --git a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/autoconfigure/SpringDataRestModuleObservabilityAutoConfiguration.java b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/autoconfigure/SpringDataRestModuleObservabilityAutoConfiguration.java index 2b71ac46..8ef88e5c 100644 --- a/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/autoconfigure/SpringDataRestModuleObservabilityAutoConfiguration.java +++ b/spring-modulith-observability/src/main/java/org/springframework/modulith/observability/autoconfigure/SpringDataRestModuleObservabilityAutoConfiguration.java @@ -15,14 +15,15 @@ */ package org.springframework.modulith.observability.autoconfigure; -import io.micrometer.tracing.Tracer; +import io.micrometer.observation.ObservationRegistry; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import org.springframework.data.rest.webmvc.RepositoryController; -import org.springframework.modulith.observability.SpringDataRestModuleTracingBeanPostProcessor; +import org.springframework.modulith.observability.SpringDataRestModuleObservabilityBeanPostProcessor; import org.springframework.modulith.runtime.ApplicationModulesRuntime; /** @@ -33,9 +34,9 @@ import org.springframework.modulith.runtime.ApplicationModulesRuntime; class SpringDataRestModuleObservabilityAutoConfiguration { @Bean - static SpringDataRestModuleTracingBeanPostProcessor springDataRestModuleTracingBeanPostProcessor( - ApplicationModulesRuntime runtime, ObjectProvider tracer) { + static SpringDataRestModuleObservabilityBeanPostProcessor springDataRestModuleTracingBeanPostProcessor( + ApplicationModulesRuntime runtime, ObjectProvider observationRegistry, Environment environment) { - return new SpringDataRestModuleTracingBeanPostProcessor(runtime, () -> tracer.getObject()); + return new SpringDataRestModuleObservabilityBeanPostProcessor(runtime, () -> observationRegistry.getObject(), environment); } } diff --git a/spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleTracingBeanPostProcessorIntegrationTests.java b/spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleObservabilityBeanPostProcessorIntegrationTests.java similarity index 77% rename from spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleTracingBeanPostProcessorIntegrationTests.java rename to spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleObservabilityBeanPostProcessorIntegrationTests.java index 3e12cd76..bc94420b 100644 --- a/spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleTracingBeanPostProcessorIntegrationTests.java +++ b/spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleObservabilityBeanPostProcessorIntegrationTests.java @@ -16,11 +16,10 @@ package org.springframework.modulith.observability; import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; import example.ExampleApplication; import example.sample.SampleComponent; -import io.micrometer.tracing.Tracer; +import io.micrometer.observation.ObservationRegistry; import org.junit.jupiter.api.Test; import org.springframework.aop.Advisor; @@ -29,24 +28,24 @@ import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.modulith.observability.ModuleTracingBeanPostProcessor.ApplicationModuleObservingAdvisor; +import org.springframework.modulith.observability.ModuleObservabilityBeanPostProcessor.ApplicationModuleObservingAdvisor; import org.springframework.modulith.runtime.ApplicationModulesRuntime; import org.springframework.modulith.runtime.ApplicationRuntime; import org.springframework.modulith.test.TestApplicationModules; import org.springframework.scheduling.annotation.AsyncAnnotationAdvisor; /** - * Integration tests for {@link ModuleTracingBeanPostProcessor}. + * Integration tests for {@link ModuleObservabilityBeanPostProcessor}. * * @author Oliver Drotbohm */ -class ModuleTracingBeanPostProcessorIntegrationTests { +class ModuleObservabilityBeanPostProcessorIntegrationTests { @Test void decoratesExposedComponentsWithTracingInterceptor() { SampleComponent bean = SpringApplication - .run(new Class[] { ExampleApplication.class, ModuleTracingConfiguration.class }, new String[] {}) + .run(new Class[] { ExampleApplication.class, ModuleObservabilityConfiguration.class }, new String[] {}) .getBean(SampleComponent.class); assertThat(bean).isInstanceOfSatisfying(Advised.class, it -> { @@ -61,16 +60,15 @@ class ModuleTracingBeanPostProcessorIntegrationTests { } @Configuration - static class ModuleTracingConfiguration { + static class ModuleObservabilityConfiguration { - @Bean - ModuleTracingBeanPostProcessor foo(ConfigurableApplicationContext context) { + @Bean ModuleObservabilityBeanPostProcessor foo(ConfigurableApplicationContext context) { var runtime = ApplicationRuntime.of(context); var modulesRuntime = new ApplicationModulesRuntime(() -> TestApplicationModules.of(ExampleApplication.class), runtime); - return new ModuleTracingBeanPostProcessor(modulesRuntime, () -> mock(Tracer.class), context.getBeanFactory()); + return new ModuleObservabilityBeanPostProcessor(modulesRuntime, () -> ObservationRegistry.NOOP, context.getBeanFactory(), context.getEnvironment()); } } diff --git a/spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleTracingBeanPostProcessorUnitTests.java b/spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleObservabilityBeanPostProcessorUnitTests.java similarity index 81% rename from spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleTracingBeanPostProcessorUnitTests.java rename to spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleObservabilityBeanPostProcessorUnitTests.java index 747dcabe..694839ac 100644 --- a/spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleTracingBeanPostProcessorUnitTests.java +++ b/spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleObservabilityBeanPostProcessorUnitTests.java @@ -25,14 +25,15 @@ import org.junit.jupiter.api.Test; import org.springframework.amqp.rabbit.annotation.RabbitListenerAnnotationBeanPostProcessor; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.mock.env.MockEnvironment; import org.springframework.modulith.runtime.ApplicationModulesRuntime; /** - * Unit tests for {@link ModuleTracingBeanPostProcessor}. + * Unit tests for {@link ModuleObservabilityBeanPostProcessor}. * * @author Oliver Drotbohm */ -class ModuleTracingBeanPostProcessorUnitTests { +class ModuleObservabilityBeanPostProcessorUnitTests { @Test // GH-498 void doesNotProxyConfiguationProperties() { @@ -44,7 +45,7 @@ class ModuleTracingBeanPostProcessorUnitTests { doReturn(SampleProperties.class).when(mock).getUserClass(any(), any()); doReturn(true).when(mock).isApplicationClass(any()); - var processor = new ModuleTracingBeanPostProcessor(mock, () -> null, beanFactory); + var processor = new ModuleObservabilityBeanPostProcessor(mock, () -> null, beanFactory, new MockEnvironment()); var bean = new SampleProperties(); var result = processor.postProcessAfterInitialization(bean, "properties"); @@ -56,8 +57,8 @@ class ModuleTracingBeanPostProcessorUnitTests { void processorIsRegisteredBefore() { var rabbitProcessor = new RabbitListenerAnnotationBeanPostProcessor(); - var tracingProcessor = new ModuleTracingBeanPostProcessor(mock(ApplicationModulesRuntime.class), () -> null, - new DefaultListableBeanFactory()); + var tracingProcessor = new ModuleObservabilityBeanPostProcessor(mock(ApplicationModulesRuntime.class), () -> null, + new DefaultListableBeanFactory(), new MockEnvironment()); assertThat(rabbitProcessor.getOrder()).isGreaterThan(tracingProcessor.getOrder());