Skip to content
17 changes: 9 additions & 8 deletions maven-extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,15 @@ Without this setting, the traces won't be exported and the OpenTelemetry Maven E

The Maven OpenTelemetry Extension supports a subset of the [OpenTelemetry autoconfiguration environment variables and JVM system properties](https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk-extensions/autoconfigure).

| System property <br /> Environment variable | Default value | Description |
|--------------------------------------------------------------------------------------------|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
| `otel.traces.exporter` <br /> `OTEL_TRACES_EXPORTER` | `none` | Select the OpenTelemetry exporter for tracing, the currently only supported values are `none` and `otlp`. `none` makes the instrumentation NoOp |
| `otel.exporter.otlp.endpoint` <br /> `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4317` | The OTLP traces and metrics endpoint to connect to. Must be a URL with a scheme of either `http` or `https` based on the use of TLS. |
| `otel.exporter.otlp.headers` <br /> `OTEL_EXPORTER_OTLP_HEADERS` | | Key-value pairs separated by commas to pass as request headers on OTLP trace and metrics requests. |
| `otel.exporter.otlp.timeout` <br /> `OTEL_EXPORTER_OTLP_TIMEOUT` | `10000` | The maximum waiting time, in milliseconds, allowed to send each OTLP trace and metric batch. |
| `otel.resource.attributes` <br /> `OTEL_RESOURCE_ATTRIBUTES` | | Specify resource attributes in the following format: key1=val1,key2=val2,key3=val3 |
| `otel.instrumentation.maven.mojo.enabled` <br /> `OTEL_INSTRUMENTATION_MAVEN_MOJO_ENABLED` | `true` | Whether to create spans for mojo goal executions, `true` or `false`. Can be configured to reduce the number of spans created for large builds. |
| System property <br /> Environment variable | Default value | Description |
|----------------------------------------------------------------------------------------------------|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
| `otel.traces.exporter` <br /> `OTEL_TRACES_EXPORTER` | `none` | Select the OpenTelemetry exporter for tracing, the currently only supported values are `none` and `otlp`. `none` makes the instrumentation NoOp |
| `otel.exporter.otlp.endpoint` <br /> `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4317` | The OTLP traces and metrics endpoint to connect to. Must be a URL with a scheme of either `http` or `https` based on the use of TLS. |
| `otel.exporter.otlp.headers` <br /> `OTEL_EXPORTER_OTLP_HEADERS` | | Key-value pairs separated by commas to pass as request headers on OTLP trace and metrics requests. |
| `otel.exporter.otlp.timeout` <br /> `OTEL_EXPORTER_OTLP_TIMEOUT` | `10000` | The maximum waiting time, in milliseconds, allowed to send each OTLP trace and metric batch. |
| `otel.resource.attributes` <br /> `OTEL_RESOURCE_ATTRIBUTES` | | Specify resource attributes in the following format: key1=val1,key2=val2,key3=val3 |
| `otel.instrumentation.maven.mojo.enabled` <br /> `OTEL_INSTRUMENTATION_MAVEN_MOJO_ENABLED` | `true` | Whether to create spans for mojo goal executions, `true` or `false`. Can be configured to reduce the number of spans created for large builds. |
| `otel.instrumentation.maven.transfer.enabled` <br /> `OTEL_INSTRUMENTATION_MAVEN_TRANSFER_ENABLED` | `false` | Whether to create spans for artifact transfers, `true` or `false`. Can be activated to understand impact of artifact transfers on performances. |

ℹ️ The `service.name` is set to `maven` and the `service.version` to the version of the Maven runtime in use.

Expand Down
4 changes: 2 additions & 2 deletions maven-extension/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {
}

// NOTE
// `META-INF/sis/javax.inject.Named` is manually handled under src/main/resources because there is
// `META-INF/sisu/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"
Expand All @@ -24,7 +24,7 @@ dependencies {
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
implementation("io.opentelemetry:opentelemetry-exporter-otlp")
implementation("io.opentelemetry.semconv:opentelemetry-semconv")
testImplementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating")
implementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating")

annotationProcessor("com.google.auto.value:auto-value")
compileOnly("com.google.auto.value:auto-value-annotations")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.maven;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.aether.transfer.TransferCancelledException;
import org.eclipse.aether.transfer.TransferEvent;
import org.eclipse.aether.transfer.TransferListener;

/**
* Util class to chain multiple {@link TransferListener} as Maven APIs don't offer this capability.
*/
final class ChainedTransferListener implements TransferListener {

private final List<TransferListener> listeners;

/**
* @param listeners {@code null} values are filtered
*/
ChainedTransferListener(TransferListener... listeners) {
this.listeners = Arrays.stream(listeners).filter(e -> e != null).collect(Collectors.toList());
}

@Override
public void transferInitiated(TransferEvent event) throws TransferCancelledException {
for (TransferListener listener : this.listeners) {
listener.transferInitiated(event);
}
}

@Override
public void transferStarted(TransferEvent event) throws TransferCancelledException {
for (TransferListener listener : this.listeners) {
listener.transferStarted(event);
}
}

@Override
public void transferProgressed(TransferEvent event) throws TransferCancelledException {
for (TransferListener listener : this.listeners) {
listener.transferProgressed(event);
}
}

@Override
public void transferCorrupted(TransferEvent event) throws TransferCancelledException {
for (TransferListener listener : this.listeners) {
listener.transferCorrupted(event);
}
}

@Override
public void transferSucceeded(TransferEvent event) {
for (TransferListener listener : this.listeners) {
listener.transferSucceeded(event);
}
}

@Override
public void transferFailed(TransferEvent event) {
for (TransferListener listener : this.listeners) {
listener.transferFailed(event);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@ public final class OpenTelemetrySdkService implements Closeable {

private final boolean mojosInstrumentationEnabled;

private final boolean transferInstrumentationEnabled;

private boolean disposed;

public OpenTelemetrySdkService() {
logger.debug(
logger.info(
"OpenTelemetry: Initialize OpenTelemetrySdkService v{}...",
MavenOtelSemanticAttributes.TELEMETRY_DISTRO_VERSION_VALUE);

Expand All @@ -76,6 +78,8 @@ public OpenTelemetrySdkService() {

this.mojosInstrumentationEnabled =
configProperties.getBoolean("otel.instrumentation.maven.mojo.enabled", true);
this.transferInstrumentationEnabled =
configProperties.getBoolean("otel.instrumentation.maven.transfer.enabled", false);

this.tracer = openTelemetrySdk.getTracer("io.opentelemetry.contrib.maven", VERSION);
}
Expand Down Expand Up @@ -154,4 +158,8 @@ public ContextPropagators getPropagators() {
public boolean isMojosInstrumentationEnabled() {
return mojosInstrumentationEnabled;
}

public boolean isTransferInstrumentationEnabled() {
return transferInstrumentationEnabled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,9 @@
import io.opentelemetry.maven.handler.MojoGoalExecutionHandler;
import io.opentelemetry.maven.handler.MojoGoalExecutionHandlerConfiguration;
import io.opentelemetry.maven.semconv.MavenOtelSemanticAttributes;
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;
import org.apache.maven.execution.ExecutionListener;
Expand Down Expand Up @@ -338,19 +335,4 @@ public void sessionEnded(ExecutionEvent event) {
logger.debug("OpenTelemetry: Maven session ended, end root span");
spanRegistry.removeRootSpan().end();
}

private static class ToUpperCaseTextMapGetter implements TextMapGetter<Map<String, String>> {
@Override
public Iterable<String> keys(Map<String, String> environmentVariables) {
return environmentVariables.keySet();
}

@Override
@Nullable
public String get(@Nullable Map<String, String> environmentVariables, @Nonnull String key) {
return environmentVariables == null
? null
: environmentVariables.get(key.toUpperCase(Locale.ROOT));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
import org.apache.maven.AbstractMavenLifecycleParticipant;
import org.apache.maven.execution.ExecutionListener;
import org.apache.maven.execution.MavenSession;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.transfer.TransferListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -25,6 +28,8 @@ public final class OtelLifecycleParticipant extends AbstractMavenLifecyclePartic

private final OtelExecutionListener otelExecutionListener;

private final OtelTransferListener otelTransferListener;

/**
* Manually instantiate {@link OtelExecutionListener} and hook it in the Maven build lifecycle
* because Maven Sisu doesn't load it when Maven Plexus did.
Expand All @@ -34,6 +39,14 @@ public final class OtelLifecycleParticipant extends AbstractMavenLifecyclePartic
OpenTelemetrySdkService openTelemetrySdkService, SpanRegistry spanRegistry) {
this.openTelemetrySdkService = openTelemetrySdkService;
this.otelExecutionListener = new OtelExecutionListener(spanRegistry, openTelemetrySdkService);
this.otelTransferListener = new OtelTransferListener(spanRegistry, openTelemetrySdkService);
}

@Override
public void afterSessionStart(MavenSession session) {
if (openTelemetrySdkService.isTransferInstrumentationEnabled()) {
registerTransferListener(session);
}
}

/**
Expand All @@ -43,6 +56,10 @@ public final class OtelLifecycleParticipant extends AbstractMavenLifecyclePartic
*/
@Override
public void afterProjectsRead(MavenSession session) {
registerExecutionListener(session);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we change something here? I didn't expect this PR to touch the execution listener. Did I miss something? I'll look at the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just moved this into a smaller method for symmetry reasons (register<XXX>Listener()) - I left a TODO, because I am not sure if some quirky Maven behavior is responsible for the need to register the exection listener in afterProjectsRead() vs afterSessionStart() - normally, I'd expect both listeners to be registered at the same place, but it appears actual Maven behavior necessitates the current code.

}

void registerExecutionListener(MavenSession session) {
ExecutionListener initialExecutionListener = session.getRequest().getExecutionListener();
if (initialExecutionListener instanceof ChainedExecutionListener
|| initialExecutionListener instanceof OtelExecutionListener) {
Expand All @@ -64,6 +81,40 @@ public void afterProjectsRead(MavenSession session) {
}
}

void registerTransferListener(MavenSession session) {
RepositorySystemSession repositorySession = session.getRepositorySession();
TransferListener initialTransferListener = repositorySession.getTransferListener();
if (initialTransferListener instanceof ChainedTransferListener
|| initialTransferListener instanceof OtelTransferListener) {
// already initialized
logger.debug(
"OpenTelemetry: OpenTelemetry extension already registered as transfer listener, skip.");
} else if (initialTransferListener == null) {
setTransferListener(this.otelTransferListener, repositorySession, session);
logger.debug(
"OpenTelemetry: OpenTelemetry extension registered as transfer listener. No transfer listener initially defined");
} else {
setTransferListener(
new ChainedTransferListener(this.otelTransferListener, initialTransferListener),
repositorySession,
session);
logger.debug(
"OpenTelemetry: OpenTelemetry extension registered as transfer listener. InitialTransferListener: {}",
initialTransferListener);
}
}

void setTransferListener(
TransferListener transferListener,
RepositorySystemSession repositorySession,
MavenSession session) {
if (repositorySession instanceof DefaultRepositorySystemSession) {
((DefaultRepositorySystemSession) repositorySession).setTransferListener(transferListener);
} else {
logger.warn("OpenTelemetry: Cannot set transfer listener");
}
}

@Override
public void afterSessionEnd(MavenSession session) {
// Workaround https://issues.apache.org/jira/browse/MNG-8217
Expand Down
Loading
Loading