Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Maven Extension] Migrate from Plexus to JSR 330 dependency injection APIs #1320

Merged
merged 6 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading