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

This commit is contained in:
Cyrille Le Clerc 2024-05-28 17:43:08 +02:00 committed by GitHub
parent 14c385e374
commit 516aed9fdf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 137 additions and 191 deletions

View File

@ -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")

View File

@ -8,66 +8,44 @@ package io.opentelemetry.maven;
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
@ -80,36 +58,48 @@ public final class OpenTelemetrySdkService implements Initializable, Disposable
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);
}
@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;
}
}
public Tracer getTracer() {
Tracer tracer = this.tracer;
if (tracer == null) {
throw new IllegalStateException("Not initialized");
}
return tracer;
return this.tracer;
}
/** Returns the {@link ContextPropagators} for this {@link OpenTelemetry}. */
public ContextPropagators getPropagators() {
return openTelemetry.getPropagators();
return this.openTelemetrySdk.getPropagators();
}
public boolean isMojosInstrumentationEnabled() {

View File

@ -21,6 +21,7 @@ import java.util.Locale;
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;
@ -29,20 +30,15 @@ import org.apache.maven.execution.MavenSession;
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);
@ -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());
@ -102,36 +97,6 @@ public final class OtelExecutionListener extends AbstractExecutionListener {
}
}
/**
* 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();
@ -370,7 +335,7 @@ public final class OtelExecutionListener extends AbstractExecutionListener {
@Override
public void sessionEnded(ExecutionEvent event) {
logger.debug("OpenTelemetry: Maven session ended");
logger.debug("OpenTelemetry: Maven session ended, end root span");
spanRegistry.removeRootSpan().end();
}
@ -382,7 +347,7 @@ public final class OtelExecutionListener extends AbstractExecutionListener {
@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));

View File

@ -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;
/**
* Manually instantiate {@link OtelExecutionListener} and hook it in the Maven build lifecycle
* because Maven Sisu doesn't load it when Maven Plexus did.
*/
@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
* declared as an extension in pom.xml but {@link #afterSessionStart(MavenSession)} is not
* invoked.
*/
@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");
}
@Override
public void afterSessionEnd(MavenSession session) {
logger.debug("OpenTelemetry: afterSessionEnd");
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);
}
}
}

View File

@ -12,10 +12,11 @@ import java.util.concurrent.ConcurrentHashMap;
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;
@ -26,7 +27,8 @@ import org.slf4j.LoggerFactory;
* 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);

View File

@ -17,7 +17,6 @@ import java.net.URL;
import java.util.Collections;
import java.util.List;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.execution.ExecutionEvent;
import org.apache.maven.project.MavenProject;
import org.slf4j.Logger;
@ -36,7 +35,9 @@ final class MavenDeployHandler implements MojoGoalExecutionHandler {
spanBuilder.setSpanKind(SpanKind.CLIENT);
MavenProject project = execution.getProject();
ArtifactRepository optRepository = project.getDistributionManagementArtifactRepository();
@SuppressWarnings("deprecation") // there is no alternative to o.a.m.a.r.ArtifactRepository
org.apache.maven.artifact.repository.ArtifactRepository optRepository =
project.getDistributionManagementArtifactRepository();
if (optRepository == null) {
return;

View File

@ -0,0 +1,4 @@
@ParametersAreNonnullByDefault
package io.opentelemetry.maven.handler;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@ -3,11 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
@DefaultQualifier(value = NonNull.class, locations = TypeUseLocation.FIELD)
@DefaultQualifier(value = NonNull.class, locations = TypeUseLocation.PARAMETER)
@DefaultQualifier(value = NonNull.class, locations = TypeUseLocation.RETURN)
@ParametersAreNonnullByDefault
package io.opentelemetry.maven;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.checkerframework.framework.qual.TypeUseLocation;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@ -10,6 +10,7 @@ import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.ServiceAttributes;
import io.opentelemetry.semconv.incubating.TelemetryIncubatingAttributes;
import org.apache.maven.rtinfo.RuntimeInformation;
import org.apache.maven.rtinfo.internal.DefaultRuntimeInformation;
@ -22,6 +23,12 @@ public class MavenResourceProvider implements ResourceProvider {
return Resource.builder()
.put(ServiceAttributes.SERVICE_NAME, MavenOtelSemanticAttributes.SERVICE_NAME_VALUE)
.put(ServiceAttributes.SERVICE_VERSION, runtimeInformation.getMavenVersion())
.put(
TelemetryIncubatingAttributes.TELEMETRY_DISTRO_NAME,
MavenOtelSemanticAttributes.TELEMETRY_DISTRO_NAME_VALUE)
.put(
TelemetryIncubatingAttributes.TELEMETRY_DISTRO_VERSION,
MavenOtelSemanticAttributes.TELEMETRY_DISTRO_VERSION_VALUE)
.build();
}
}

View File

@ -0,0 +1,9 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
@ParametersAreNonnullByDefault
package io.opentelemetry.maven.resources;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@ -9,6 +9,7 @@ import static io.opentelemetry.api.common.AttributeKey.stringArrayKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.maven.OpenTelemetrySdkService;
import io.opentelemetry.semconv.incubating.ContainerIncubatingAttributes;
import java.util.List;
@ -55,5 +56,10 @@ public class MavenOtelSemanticAttributes {
public static final String SERVICE_NAME_VALUE = "maven";
public static final String TELEMETRY_DISTRO_NAME_VALUE = "opentelemetry-maven-extension";
public static final String TELEMETRY_DISTRO_VERSION_VALUE =
OpenTelemetrySdkService.class.getPackage().getImplementationVersion();
private MavenOtelSemanticAttributes() {}
}

View File

@ -10,5 +10,6 @@
<exportedPackage>io.opentelemetry.api.metrics</exportedPackage>
<exportedPackage>io.opentelemetry.api.trace</exportedPackage>
<exportedPackage>io.opentelemetry.context</exportedPackage>
<exportedPackage>io.opentelemetry.context.propagation</exportedPackage>
</exportedPackages>
</extension>

View File

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<component-set>
<components>
<component>
<role>io.opentelemetry.maven.OpenTelemetrySdkService</role>
<role-hint>opentelemetry-service</role-hint>
<implementation>io.opentelemetry.maven.OpenTelemetrySdkService</implementation>
<description />
<isolated-realm>false</isolated-realm>
<requirements>
<requirement>
<role>org.apache.maven.rtinfo.RuntimeInformation</role>
<field-name>runtimeInformation</field-name>
</requirement>
</requirements>
</component>
<component>
<role>io.opentelemetry.maven.SpanRegistry</role>
<role-hint>default</role-hint>
<implementation>io.opentelemetry.maven.SpanRegistry</implementation>
<description />
<isolated-realm>false</isolated-realm>
</component>
<component>
<role>org.apache.maven.AbstractMavenLifecycleParticipant</role>
<role-hint>default</role-hint>
<implementation>io.opentelemetry.maven.OtelLifecycleParticipant</implementation>
<description />
<isolated-realm>false</isolated-realm>
<requirements>
<requirement>
<role>org.apache.maven.execution.ExecutionListener</role>
<role-hint>otel-execution-listener</role-hint>
<field-name>otelExecutionListener</field-name>
</requirement>
</requirements>
</component>
<component>
<role>org.apache.maven.execution.ExecutionListener</role>
<role-hint>otel-execution-listener</role-hint>
<implementation>io.opentelemetry.maven.OtelExecutionListener</implementation>
<description />
<isolated-realm>false</isolated-realm>
<requirements>
<requirement>
<role>io.opentelemetry.maven.SpanRegistry</role>
<field-name>spanRegistry</field-name>
</requirement>
<requirement>
<role>io.opentelemetry.maven.OpenTelemetrySdkService</role>
<field-name>openTelemetrySdkService</field-name>
</requirement>
</requirements>
</component>
</components>
</component-set>

View File

@ -0,0 +1,3 @@
io.opentelemetry.maven.OpenTelemetrySdkService
io.opentelemetry.maven.OtelLifecycleParticipant
io.opentelemetry.maven.SpanRegistry