Skip to content

Commit

Permalink
[Maven Extension] Migrate from Plexus to JSR 330 dependency injection…
Browse files Browse the repository at this point in the history
… APIs (#1320)
  • Loading branch information
cyrille-leclerc authored May 28, 2024
1 parent 14c385e commit 516aed9
Show file tree
Hide file tree
Showing 14 changed files with 137 additions and 191 deletions.
13 changes: 7 additions & 6 deletions maven-extension/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,31 @@ plugins {
}

// NOTE
// `META-INF/plexus/components.xml` is manually handled under src/main/resources because there is no Gradle
// equivalent to the Maven plugin `plexus-component-metadata:generate-metadata`
// `META-INF/sis/javax.inject.Named` is manually handled under src/main/resources because there is
// no Gradle equivalent to the Maven plugin `org.eclipse.sisu:sisu-maven-plugin`

description = "Maven extension to observe Maven builds with distributed traces using OpenTelemetry SDK"
otelJava.moduleName.set("io.opentelemetry.maven")

dependencies {
implementation("org.codehaus.plexus:plexus-component-annotations:2.1.1")
compileOnly("javax.inject:javax.inject:1")

implementation("io.opentelemetry:opentelemetry-api")
implementation("io.opentelemetry:opentelemetry-sdk")
implementation("io.opentelemetry:opentelemetry-sdk-trace")
implementation("io.opentelemetry:opentelemetry-sdk-metrics")
implementation("io.opentelemetry:opentelemetry-sdk-logs")
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
implementation("io.opentelemetry:opentelemetry-exporter-otlp")
implementation("io.opentelemetry.semconv:opentelemetry-semconv")
implementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating:1.25.0-alpha")
implementation("io.opentelemetry:opentelemetry-exporter-otlp")

annotationProcessor("com.google.auto.value:auto-value")
compileOnly("com.google.auto.value:auto-value-annotations")

compileOnly("org.apache.maven:maven-core:3.5.0") // do not auto-update this version
compileOnly("org.apache.maven:maven-core:3.5.0") // do not auto-update, support older mvn versions
compileOnly("org.slf4j:slf4j-api")
compileOnly("org.sonatype.aether:aether-api:1.13.1")

testImplementation("org.apache.maven:maven-core:3.5.0")
testImplementation("org.slf4j:slf4j-simple")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,66 +8,44 @@
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.maven.semconv.MavenOtelSemanticAttributes;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.common.CompletableResultCode;
import java.io.Closeable;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Disposable;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
import javax.annotation.PreDestroy;
import javax.inject.Named;
import javax.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Service to configure the {@link OpenTelemetry} instance. */
@Component(role = OpenTelemetrySdkService.class, hint = "opentelemetry-service")
public final class OpenTelemetrySdkService implements Initializable, Disposable {
@Named
@Singleton
public final class OpenTelemetrySdkService implements Closeable {

static final String VERSION =
OpenTelemetrySdkService.class.getPackage().getImplementationVersion();

private static final Logger logger = LoggerFactory.getLogger(OpenTelemetrySdkService.class);

private OpenTelemetry openTelemetry = OpenTelemetry.noop();
@Nullable private OpenTelemetrySdk openTelemetrySdk;
private final OpenTelemetrySdk openTelemetrySdk;

@Nullable private Tracer tracer;
private final Tracer tracer;

private boolean mojosInstrumentationEnabled;
private final boolean mojosInstrumentationEnabled;

/** Visible for testing */
@Nullable AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk;
private boolean disposed;

@Override
public synchronized void dispose() {
logger.debug("OpenTelemetry: dispose OpenTelemetrySdkService...");
OpenTelemetrySdk openTelemetrySdk = this.openTelemetrySdk;
if (openTelemetrySdk != null) {
logger.debug("OpenTelemetry: Shutdown SDK Trace Provider...");
CompletableResultCode sdkProviderShutdown =
openTelemetrySdk.getSdkTracerProvider().shutdown();
sdkProviderShutdown.join(10, TimeUnit.SECONDS);
if (sdkProviderShutdown.isSuccess()) {
logger.debug("OpenTelemetry: SDK Trace Provider shut down");
} else {
logger.warn(
"OpenTelemetry: Failure to shutdown SDK Trace Provider (done: {})",
sdkProviderShutdown.isDone());
}
this.openTelemetrySdk = null;
}
this.openTelemetry = OpenTelemetry.noop();

this.autoConfiguredOpenTelemetrySdk = null;
logger.debug("OpenTelemetry: OpenTelemetrySdkService disposed");
}

@Override
public void initialize() {
logger.debug("OpenTelemetry: Initialize OpenTelemetrySdkService v{}...", VERSION);
public OpenTelemetrySdkService() {
logger.debug(
"OpenTelemetry: Initialize OpenTelemetrySdkService v{}...",
MavenOtelSemanticAttributes.TELEMETRY_DISTRO_VERSION_VALUE);

// Change default of "otel.[traces,metrics,logs].exporter" from "otlp" to "none"
// The impacts are
Expand All @@ -80,36 +58,48 @@ public void initialize() {
properties.put("otel.metrics.exporter", "none");
properties.put("otel.logs.exporter", "none");

this.autoConfiguredOpenTelemetrySdk =
AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk =
AutoConfiguredOpenTelemetrySdk.builder()
.setServiceClassLoader(getClass().getClassLoader())
.addPropertiesSupplier(() -> properties)
.disableShutdownHook()
.build();

if (logger.isDebugEnabled()) {
logger.debug("OpenTelemetry: OpenTelemetry SDK initialized");
}
this.openTelemetrySdk = autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk();
this.openTelemetry = this.openTelemetrySdk;

Boolean mojoSpansEnabled = getBooleanConfig("otel.instrumentation.maven.mojo.enabled");
this.mojosInstrumentationEnabled = mojoSpansEnabled == null ? true : mojoSpansEnabled;
this.mojosInstrumentationEnabled = mojoSpansEnabled == null || mojoSpansEnabled;

this.tracer = openTelemetry.getTracer("io.opentelemetry.contrib.maven", VERSION);
this.tracer = openTelemetrySdk.getTracer("io.opentelemetry.contrib.maven", VERSION);
}

public Tracer getTracer() {
Tracer tracer = this.tracer;
if (tracer == null) {
throw new IllegalStateException("Not initialized");
@PreDestroy
@Override
public synchronized void close() {
if (disposed) {
logger.debug("OpenTelemetry: OpenTelemetry SDK already shut down, ignore");
} else {
logger.debug("OpenTelemetry: Shutdown OpenTelemetry SDK...");
CompletableResultCode openTelemetrySdkShutdownResult =
this.openTelemetrySdk.shutdown().join(10, TimeUnit.SECONDS);
if (openTelemetrySdkShutdownResult.isSuccess()) {
logger.debug("OpenTelemetry: OpenTelemetry SDK successfully shut down");
} else {
logger.warn(
"OpenTelemetry: Failure to shutdown OpenTelemetry SDK (done: {})",
openTelemetrySdkShutdownResult.isDone());
}
this.disposed = true;
}
return tracer;
}

public Tracer getTracer() {
return this.tracer;
}

/** Returns the {@link ContextPropagators} for this {@link OpenTelemetry}. */
public ContextPropagators getPropagators() {
return openTelemetry.getPropagators();
return this.openTelemetrySdk.getPropagators();
}

public boolean isMojosInstrumentationEnabled() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.maven.execution.AbstractExecutionListener;
import org.apache.maven.execution.ExecutionEvent;
Expand All @@ -29,20 +30,15 @@
import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Close the OpenTelemetry SDK (see {@link OpenTelemetrySdkService#dispose()}) on the end of
* execution of the last project ({@link #projectSucceeded(ExecutionEvent)} and {@link
* #projectFailed(ExecutionEvent)}) rather than on the end of the Maven session {@link
* #sessionEnded(ExecutionEvent)} because OpenTelemetry and GRPC classes are unloaded by the Maven
* classloader before {@link #sessionEnded(ExecutionEvent)} causing {@link NoClassDefFoundError}
* messages in the logs.
* Don't mark this class as {@link javax.inject.Named} and {@link javax.inject.Singleton} because
* Maven Sisu doesn't automatically load instance of {@link ExecutionListener} as Maven Extension
* hooks the same way Maven Plexus did so we manually hook this instance of {@link
* ExecutionListener} through the {@link OtelLifecycleParticipant#afterProjectsRead(MavenSession)}.
*/
@Component(role = ExecutionListener.class, hint = "otel-execution-listener")
public final class OtelExecutionListener extends AbstractExecutionListener {

private static final Logger logger = LoggerFactory.getLogger(OtelExecutionListener.class);
Expand All @@ -56,17 +52,16 @@ public final class OtelExecutionListener extends AbstractExecutionListener {
*/
private static final ThreadLocal<Scope> MOJO_EXECUTION_SCOPE = new ThreadLocal<>();

@SuppressWarnings("NullAway") // Automatically initialized by DI
@Requirement
private SpanRegistry spanRegistry;
private final SpanRegistry spanRegistry;

@SuppressWarnings("NullAway") // Automatically initialized by DI
@Requirement
private OpenTelemetrySdkService openTelemetrySdkService;
private final OpenTelemetrySdkService openTelemetrySdkService;

private final Map<MavenGoal, MojoGoalExecutionHandler> mojoGoalExecutionHandlers;

public OtelExecutionListener() {
OtelExecutionListener(
SpanRegistry spanRegistry, OpenTelemetrySdkService openTelemetrySdkService) {
this.spanRegistry = spanRegistry;
this.openTelemetrySdkService = openTelemetrySdkService;
this.mojoGoalExecutionHandlers =
MojoGoalExecutionHandlerConfiguration.loadMojoGoalExecutionHandler(
OtelExecutionListener.class.getClassLoader());
Expand Down Expand Up @@ -102,36 +97,6 @@ public OtelExecutionListener() {
}
}

/**
* Register in given {@link OtelExecutionListener} to the lifecycle of the given {@link
* MavenSession}
*
* @see org.apache.maven.execution.MavenExecutionRequest#setExecutionListener(ExecutionListener)
*/
public static void registerOtelExecutionListener(
MavenSession session, OtelExecutionListener otelExecutionListener) {

ExecutionListener initialExecutionListener = session.getRequest().getExecutionListener();
if (initialExecutionListener instanceof ChainedExecutionListener
|| initialExecutionListener instanceof OtelExecutionListener) {
// already initialized
logger.debug(
"OpenTelemetry: OpenTelemetry extension already registered as execution listener, skip.");
} else if (initialExecutionListener == null) {
session.getRequest().setExecutionListener(otelExecutionListener);
logger.debug(
"OpenTelemetry: OpenTelemetry extension registered as execution listener. No execution listener initially defined");
} else {
session
.getRequest()
.setExecutionListener(
new ChainedExecutionListener(otelExecutionListener, initialExecutionListener));
logger.debug(
"OpenTelemetry: OpenTelemetry extension registered as execution listener. InitialExecutionListener: "
+ initialExecutionListener);
}
}

@Override
public void sessionStarted(ExecutionEvent executionEvent) {
MavenProject project = executionEvent.getSession().getTopLevelProject();
Expand Down Expand Up @@ -370,7 +335,7 @@ public void mojoFailed(ExecutionEvent executionEvent) {

@Override
public void sessionEnded(ExecutionEvent event) {
logger.debug("OpenTelemetry: Maven session ended");
logger.debug("OpenTelemetry: Maven session ended, end root span");
spanRegistry.removeRootSpan().end();
}

Expand All @@ -382,7 +347,7 @@ public Iterable<String> keys(Map<String, String> environmentVariables) {

@Override
@Nullable
public String get(@Nullable Map<String, String> environmentVariables, String key) {
public String get(@Nullable Map<String, String> environmentVariables, @Nonnull String key) {
return environmentVariables == null
? null
: environmentVariables.get(key.toUpperCase(Locale.ROOT));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,59 @@

package io.opentelemetry.maven;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.maven.AbstractMavenLifecycleParticipant;
import org.apache.maven.execution.ExecutionListener;
import org.apache.maven.execution.MavenSession;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Add the {@link OtelExecutionListener} to the lifecycle of the Maven execution */
@Component(role = AbstractMavenLifecycleParticipant.class)
@Named
@Singleton
public final class OtelLifecycleParticipant extends AbstractMavenLifecycleParticipant {

private static final Logger logger = LoggerFactory.getLogger(OtelLifecycleParticipant.class);

@SuppressWarnings("NullAway") // Automatically initialized by DI
@Requirement(role = ExecutionListener.class, hint = "otel-execution-listener")
private OtelExecutionListener otelExecutionListener;
private final OtelExecutionListener otelExecutionListener;

/**
* For an unknown reason, {@link #afterProjectsRead(MavenSession)} is invoked when the module is
* declared as an extension in pom.xml but {@link #afterSessionStart(MavenSession)} is not invoked
* Manually instantiate {@link OtelExecutionListener} and hook it in the Maven build lifecycle
* because Maven Sisu doesn't load it when Maven Plexus did.
*/
@Override
public void afterProjectsRead(MavenSession session) {
OtelExecutionListener.registerOtelExecutionListener(session, this.otelExecutionListener);
logger.debug("OpenTelemetry: afterProjectsRead");
}

@Override
public void afterSessionStart(MavenSession session) {
OtelExecutionListener.registerOtelExecutionListener(session, this.otelExecutionListener);
logger.debug("OpenTelemetry: afterSessionStart");
@Inject
OtelLifecycleParticipant(
OpenTelemetrySdkService openTelemetrySdkService, SpanRegistry spanRegistry) {
this.otelExecutionListener = new OtelExecutionListener(spanRegistry, openTelemetrySdkService);
}

/**
* For an unknown reason, {@link #afterProjectsRead(MavenSession)} is invoked when the module is
* declared as an extension in pom.xml but {@link #afterSessionStart(MavenSession)} is not
* invoked.
*/
@Override
public void afterSessionEnd(MavenSession session) {
logger.debug("OpenTelemetry: afterSessionEnd");
public void afterProjectsRead(MavenSession session) {
ExecutionListener initialExecutionListener = session.getRequest().getExecutionListener();
if (initialExecutionListener instanceof ChainedExecutionListener
|| initialExecutionListener instanceof OtelExecutionListener) {
// already initialized
logger.debug(
"OpenTelemetry: OpenTelemetry extension already registered as execution listener, skip.");
} else if (initialExecutionListener == null) {
session.getRequest().setExecutionListener(this.otelExecutionListener);
logger.debug(
"OpenTelemetry: OpenTelemetry extension registered as execution listener. No execution listener initially defined");
} else {
session
.getRequest()
.setExecutionListener(
new ChainedExecutionListener(this.otelExecutionListener, initialExecutionListener));
logger.debug(
"OpenTelemetry: OpenTelemetry extension registered as execution listener. InitialExecutionListener: "
+ initialExecutionListener);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -26,7 +27,8 @@
* Daemon</a>, can't execute multiple builds concurrently, there is no need to differentiate spans
* per {@link org.apache.maven.execution.MavenSession}.
*/
@Component(role = SpanRegistry.class)
@Singleton
@Named
public final class SpanRegistry {

private static final Logger logger = LoggerFactory.getLogger(SpanRegistry.class);
Expand Down
Loading

0 comments on commit 516aed9

Please sign in to comment.