Skip to content

Commit

Permalink
GH-928 - Move to observations API.
Browse files Browse the repository at this point in the history
Reworked observability integration to create Observations rather than traces directly. Additional metrics and counters and an Observation.Context to potentially customize the metrics exposed.
  • Loading branch information
marcingrzejszczak authored and odrotbohm committed Nov 22, 2024
1 parent 163673a commit 738ad3f
Show file tree
Hide file tree
Showing 20 changed files with 566 additions and 151 deletions.
10 changes: 10 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
<spring-cloud-aws-bom.version>3.1.1</spring-cloud-aws-bom.version>
<testcontainers.version>1.17.6</testcontainers.version>
<structurizr.version>3.1.0</structurizr.version>
<!-- TODO: For snapshots -->
<micrometer-tracing.version>1.5.0-SNAPSHOT</micrometer-tracing.version>

</properties>

Expand Down Expand Up @@ -97,6 +99,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
53 changes: 52 additions & 1 deletion spring-modulith-observability/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@

<properties>
<module.name>org.springframework.modulith.observability</module.name>
<spring-cloud.version>2021.0.0</spring-cloud.version>

<micrometer-docs-generator.version>1.0.4</micrometer-docs-generator.version>
<micrometer-docs-generator.inputPath>${project.basedir}/src/main/java/</micrometer-docs-generator.inputPath>
<micrometer-docs-generator.inclusionPattern>.*</micrometer-docs-generator.inclusionPattern>
<micrometer-docs-generator.outputPath>${project.build.directory}/observation-documentation/</micrometer-docs-generator.outputPath>
</properties>

<dependencies>
Expand Down Expand Up @@ -73,12 +77,24 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
<optional>true</optional>
</dependency>

<!-- Testing -->

<dependency>
Expand Down Expand Up @@ -107,4 +123,39 @@

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>generate-docs</id>
<phase>prepare-package</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>io.micrometer.docs.DocsGeneratorCommand</mainClass>
<includePluginDependencies>true</includePluginDependencies>
<arguments>
<argument>${micrometer-docs-generator.inputPath}</argument>
<argument>${micrometer-docs-generator.inclusionPattern}</argument>
<argument>${micrometer-docs-generator.outputPath}</argument>
</arguments>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-docs-generator</artifactId>
<version>${micrometer-docs-generator.version}</version>
<type>jar</type>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<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 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 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,
Environment environment) {
this(module, observationRegistry, null, environment);
}

/**
* Creates a new {@link ModuleEntryInterceptor} for the given {@link ObservedModule} and {@link Tracer}.
* Creates a new {@link ModuleEntryInterceptor} for the given {@link ObservedModule}, {@link ObservationRegistry} and
* {@link ModulithObservationConvention}.
*
* @param module must not be {@literal null}.
* @param tracer 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, Tracer tracer) {
private ModuleEntryInterceptor(ObservedModule module, ObservationRegistry observationRegistry,
ModulithObservationConvention custom, Environment environment) {

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

this.module = module;
this.tracer = tracer;
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, Tracer tracer) {
public static ModuleEntryInterceptor of(ObservedModule module, ObservationRegistry observationRegistry,
ModulithObservationConvention custom, Environment environment) {

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

Expand All @@ -66,34 +98,38 @@ public static ModuleEntryInterceptor of(ObservedModule module, Tracer tracer) {
@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 (currentObservation != null) {
KeyValue moduleKey = currentObservation.getContextView().getLowCardinalityKeyValue(LowKeys.MODULE_KEY.asString());
currentModule = moduleKey != null ? moduleKey.getValue() : null;
}

if (currentSpan != null && moduleName.equals(currentModule)) {
if (currentObservation != null && Objects.equals(moduleIdentifier.toString(), currentModule)) {
// Same module
return invocation.proceed();
}

var invokedMethod = module.getInvokedMethod(invocation);

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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -31,21 +35,25 @@
public class ModuleEventListener implements ApplicationListener<ApplicationEvent> {

private final ApplicationModulesRuntime runtime;
private final Supplier<Tracer> tracer;
private final Supplier<ObservationRegistry> observationRegistry;
private final Supplier<MeterRegistry> 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> tracer) {
public ModuleEventListener(ApplicationModulesRuntime runtime, Supplier<ObservationRegistry> observationRegistrySupplier,
Supplier<MeterRegistry> 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;
}

/*
Expand Down Expand Up @@ -74,12 +82,20 @@ public void onApplicationEvent(ApplicationEvent event) {
return;
}

var span = tracer.get().currentSpan();
MeterRegistry registry = meterRegistry.get();
if (registry != null) {
Counter.builder(ModulithMetrics.EVENTS.getName()) //
.tags(ModulithMetrics.LowKeys.EVENT_TYPE.name().toLowerCase(), event.getClass().getSimpleName()) //
.tags(ModulithMetrics.LowKeys.MODULE_NAME.name().toLowerCase(), moduleByType.getDisplayName()) //
.register(registry).increment();
}

var observation = observationRegistry.get().getCurrentObservation();

if (span == null) {
if (observation == null) {
return;
}

span.event("Published " + payloadType.getName());
observation.event(Event.of(Events.EVENT_PUBLICATION_SUCCESS.getName(), "Published " + payloadType.getName()));
}
}
Loading

0 comments on commit 738ad3f

Please sign in to comment.