Skip to content

Commit

Permalink
WIP. Current code works with tracing 1.5.0 - leaving this for future …
Browse files Browse the repository at this point in the history
…reference
  • Loading branch information
marcingrzejszczak committed Nov 14, 2024
1 parent e84ca0d commit 52dcb59
Show file tree
Hide file tree
Showing 13 changed files with 228 additions and 86 deletions.
10 changes: 10 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
<spring-framework.version>6.2.0-RC3</spring-framework.version> <!-- For Javadoc links only -->
<spring-cloud-aws-bom.version>3.1.1</spring-cloud-aws-bom.version>
<testcontainers.version>1.17.6</testcontainers.version>
<!-- TODO: For snapshots -->
<micrometer-tracing.version>1.5.0-SNAPSHOT</micrometer-tracing.version>

</properties>

Expand Down Expand Up @@ -96,6 +98,14 @@ limitations under the License.
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- TODO: For snapshots -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bom</artifactId>
<version>${micrometer-tracing.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,48 @@
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.3
*/
public class DefaultModulithObservationConvention implements ModulithObservationConvention {

@Override
public KeyValues getLowCardinalityKeyValues(ModulithContext context) {
ObservedModule currentModule = context.getModule();
if (currentModule != null) {
return KeyValues.of(LowKeys.MODULE_KEY.withValue(currentModule.getName()));
}
return KeyValues.empty();
}

@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) {
// TODO: Change this to application name
return "[" + context.getModule().getDisplayName() + "] " + context.getModule()
.getDisplayName();
}
@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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.springframework.modulith.observability;

import io.micrometer.tracing.exporter.FinishedSpan;
import io.micrometer.tracing.exporter.SpanFilter;

/**
* {@link SpanFilter} that sets a local service name according
* to the current module's name.
*
* @author Marcin Grzejszczak
* @since 1.3
*/
public class LocalServiceRenamingSpanFilter implements SpanFilter {

@Override
public FinishedSpan map(FinishedSpan span) {
String moduleKey = span.getTags().get(ModulithObservations.LowKeys.MODULE_KEY.asString());
if (moduleKey != null) {
// Wait for tracing 1.5.0
span.setLocalServiceName(moduleKey);
}
return span;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import io.micrometer.common.KeyValue;
import io.micrometer.observation.Observation;
Expand All @@ -26,32 +27,34 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.AnnotationUtils;
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<String, ModuleEntryInterceptor> CACHE = new HashMap<>();
private static final String MODULE_KEY = ModuleTracingBeanPostProcessor.MODULE_BAGGAGE_KEY;
private static Map<ApplicationModuleIdentifier, ModuleEntryInterceptor> CACHE = new HashMap<>();

private static final ModulithObservationConvention DEFAULT = new DefaultModulithObservationConvention();

private final ObservedModule module;
private final ObservationRegistry observationRegistry;
@Nullable private final ModulithObservationConvention customModulithObservationConvention;
@Nullable
private final ModulithObservationConvention customModulithObservationConvention;
private final Environment environment;

/**
* Creates a new {@link ModuleEntryInterceptor} for the given {@link ObservedModule} and {@link ObservationRegistry}.
*
* @param module must not be {@literal null}.
* @param observationRegistry must not be {@literal null}.
* @param environment must not be {@literal null}.
*/
private ModuleEntryInterceptor(ObservedModule module, ObservationRegistry observationRegistry) {
this(module, observationRegistry, null);
private ModuleEntryInterceptor(ObservedModule module, ObservationRegistry observationRegistry, Environment environment) {
this(module, observationRegistry, null, environment);
}

/**
Expand All @@ -61,45 +64,49 @@ private ModuleEntryInterceptor(ObservedModule module, ObservationRegistry observ
* @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) {
ModulithObservationConvention custom, Environment environment) {

Assert.notNull(module, "ObservedModule must not be null!");
Assert.notNull(observationRegistry, "Tracer must not be null!");

this.module = module; this.observationRegistry = observationRegistry;
this.customModulithObservationConvention = custom;
this.environment = environment;
}

public static ModuleEntryInterceptor of(ObservedModule module, ObservationRegistry observationRegistry) {
return of(module, observationRegistry, null);
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) {
ModulithObservationConvention custom, Environment environment) {

return CACHE.computeIfAbsent(module.getName(), __ -> {
return new ModuleEntryInterceptor(module, observationRegistry, custom);
return CACHE.computeIfAbsent(module.getIdentifier(), __ -> {
return new ModuleEntryInterceptor(module, observationRegistry, custom, environment);
});
}

/*
* (non-Javadoc)
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
*/
@Override public Object invoke(MethodInvocation invocation) throws Throwable {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {

var moduleName = module.getName();
var moduleIdentifier = module.getIdentifier();
var currentObservation = observationRegistry.getCurrentObservation();
String currentModule = null;

if (currentObservation != null) {
KeyValue moduleKey = currentObservation.getContextView().getLowCardinalityKeyValue(MODULE_KEY);
KeyValue moduleKey = currentObservation.getContextView().getLowCardinalityKeyValue(LowKeys.MODULE_KEY.asString());
currentModule = moduleKey != null ? moduleKey.getValue() : null;
}

if (currentObservation != null && moduleName.equals(currentModule)) {
if (currentObservation != null && Objects.equals(moduleIdentifier.toString(), currentModule)) {
// Same module
return invocation.proceed();
}
Expand All @@ -108,18 +115,18 @@ public static ModuleEntryInterceptor of(ObservedModule module, ObservationRegist

LOGGER.trace("Entering {} via {}.", module.getDisplayName(), invokedMethod);

boolean isEventListener = module.isEventListenerInvocation(invocation);

// TODO: Good name for metrics
ModulithContext modulithContext = new ModulithContext(module, invocation);
ModulithContext modulithContext = new ModulithContext(module, invocation, environment);
var observation = Observation.createNotStarted(customModulithObservationConvention, DEFAULT,
() -> modulithContext, observationRegistry); if (isEventListener) {
observation.lowCardinalityKeyValue(LowKeys.INVOCATION_TYPE.withValue("event-listener"));
} try (Observation.Scope scope = observation.openScope()) {
Object proceed = invocation.proceed(); observation.event(ModulithObservations.Events.EVENT_PUBLICATION_SUCCESS);
() -> 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;
observation.error(ex);
observation.event(ModulithObservations.Events.EVENT_PUBLICATION_FAILURE);
throw ex;
} finally {
LOGGER.trace("Leaving {}", module.getDisplayName()); observation.stop();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.env.Environment;
import org.springframework.modulith.runtime.ApplicationModulesRuntime;
import org.springframework.util.Assert;

Expand All @@ -43,32 +44,34 @@
*
* @author Oliver Drotbohm
*/
public class ModuleTracingBeanPostProcessor extends ModuleTracingSupport implements BeanPostProcessor {
public class ModuleObservabilityBeanPostProcessor extends ModuleObservabilitySupport implements BeanPostProcessor {

public static final String MODULE_BAGGAGE_KEY = "org.springframework.modulith.module";

private final ApplicationModulesRuntime runtime;
private final Supplier<ObservationRegistry> observationRegistry;
private final Map<String, Advisor> advisors;
private final ConfigurableListableBeanFactory factory;
private final Environment environment;

/**
* Creates a new {@link ModuleTracingBeanPostProcessor} for the given {@link ApplicationModulesRuntime} and
* Creates a new {@link ModuleObservabilityBeanPostProcessor} for the given {@link ApplicationModulesRuntime} and
* {@link Tracer}.
*
* @param runtime must not be {@literal null}.
* @param observationRegistry must not be {@literal null}.
*/
public ModuleTracingBeanPostProcessor(ApplicationModulesRuntime runtime, Supplier<ObservationRegistry> observationRegistry,
ConfigurableListableBeanFactory factory) {
public ModuleObservabilityBeanPostProcessor(ApplicationModulesRuntime runtime, Supplier<ObservationRegistry> observationRegistry,
ConfigurableListableBeanFactory factory, Environment environment) {

Assert.notNull(runtime, "ApplicationModulesRuntime must not be null!");
Assert.notNull(observationRegistry, "Tracer must not be null!");
Assert.notNull(observationRegistry, "ObservationRegistry must not be null!");

this.runtime = runtime;
this.observationRegistry = observationRegistry;
this.advisors = new HashMap<>();
this.factory = factory;
this.environment = environment;
}

/*
Expand Down Expand Up @@ -120,8 +123,8 @@ private boolean isInfrastructureBean(String beanName) {

private Advisor getOrBuildAdvisor(ObservedModule module, ObservedModuleType type) {

return advisors.computeIfAbsent(module.getName(), __ -> {
return new ApplicationModuleObservingAdvisor(type, ModuleEntryInterceptor.of(module, observationRegistry.get()));
return advisors.computeIfAbsent(module.getIdentifier().toString(), __ -> {
return new ApplicationModuleObservingAdvisor(type, ModuleEntryInterceptor.of(module, observationRegistry.get(), environment));
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
/**
* @author Oliver Drotbohm
*/
class ModuleTracingSupport implements BeanClassLoaderAware {
class ModuleObservabilitySupport implements BeanClassLoaderAware {

private ClassLoader classLoader;

Expand Down
Original file line number Diff line number Diff line change
@@ -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.3
*/
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;
}
}
Loading

0 comments on commit 52dcb59

Please sign in to comment.