Skip to content

Commit 907cde8

Browse files
committed
initial commit
1 parent 3fc4309 commit 907cde8

File tree

9 files changed

+366
-19
lines changed

9 files changed

+366
-19
lines changed

maven-extension/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ plugins {
66
}
77

88
// NOTE
9-
// `META-INF/sis/javax.inject.Named` is manually handled under src/main/resources because there is
9+
// `META-INF/sisu/javax.inject.Named` is manually handled under src/main/resources because there is
1010
// no Gradle equivalent to the Maven plugin `org.eclipse.sisu:sisu-maven-plugin`
1111

1212
description = "Maven extension to observe Maven builds with distributed traces using OpenTelemetry SDK"
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.maven;
7+
8+
import java.util.Arrays;
9+
import java.util.List;
10+
import java.util.stream.Collectors;
11+
import org.eclipse.aether.transfer.TransferCancelledException;
12+
import org.eclipse.aether.transfer.TransferEvent;
13+
import org.eclipse.aether.transfer.TransferListener;
14+
15+
/**
16+
* Util class to chain multiple {@link TransferListener} as Maven APIs don't offer this capability.
17+
*/
18+
final class ChainedTransferListener implements TransferListener {
19+
20+
private final List<TransferListener> listeners;
21+
22+
/**
23+
* @param listeners {@code null} values are filtered
24+
*/
25+
ChainedTransferListener(TransferListener... listeners) {
26+
this.listeners = Arrays.stream(listeners).filter(e -> e != null).collect(Collectors.toList());
27+
}
28+
29+
@Override
30+
public void transferInitiated(TransferEvent event) throws TransferCancelledException {
31+
for (TransferListener listener : this.listeners) {
32+
listener.transferInitiated(event);
33+
}
34+
}
35+
36+
@Override
37+
public void transferStarted(TransferEvent event) throws TransferCancelledException {
38+
for (TransferListener listener : this.listeners) {
39+
listener.transferStarted(event);
40+
}
41+
}
42+
43+
@Override
44+
public void transferProgressed(TransferEvent event) throws TransferCancelledException {
45+
for (TransferListener listener : this.listeners) {
46+
listener.transferProgressed(event);
47+
}
48+
}
49+
50+
@Override
51+
public void transferCorrupted(TransferEvent event) throws TransferCancelledException {
52+
for (TransferListener listener : this.listeners) {
53+
listener.transferCorrupted(event);
54+
}
55+
}
56+
57+
@Override
58+
public void transferSucceeded(TransferEvent event) {
59+
for (TransferListener listener : this.listeners) {
60+
listener.transferSucceeded(event);
61+
}
62+
}
63+
64+
@Override
65+
public void transferFailed(TransferEvent event) {
66+
for (TransferListener listener : this.listeners) {
67+
listener.transferFailed(event);
68+
}
69+
}
70+
}

maven-extension/src/main/java/io/opentelemetry/maven/OpenTelemetrySdkService.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ public final class OpenTelemetrySdkService implements Closeable {
4949

5050
private final boolean mojosInstrumentationEnabled;
5151

52+
private final boolean transferInstrumentationEnabled;
53+
5254
private boolean disposed;
5355

5456
public OpenTelemetrySdkService() {
@@ -76,6 +78,9 @@ public OpenTelemetrySdkService() {
7678

7779
this.mojosInstrumentationEnabled =
7880
configProperties.getBoolean("otel.instrumentation.maven.mojo.enabled", true);
81+
// TODO maybe default would be false
82+
this.transferInstrumentationEnabled =
83+
configProperties.getBoolean("otel.instrumentation.maven.transfer.enabled", true);
7984

8085
this.tracer = openTelemetrySdk.getTracer("io.opentelemetry.contrib.maven", VERSION);
8186
}
@@ -154,4 +159,8 @@ public ContextPropagators getPropagators() {
154159
public boolean isMojosInstrumentationEnabled() {
155160
return mojosInstrumentationEnabled;
156161
}
162+
163+
public boolean isTransferInstrumentationEnabled() {
164+
return transferInstrumentationEnabled;
165+
}
157166
}

maven-extension/src/main/java/io/opentelemetry/maven/OtelExecutionListener.java

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,9 @@
1717
import io.opentelemetry.maven.handler.MojoGoalExecutionHandler;
1818
import io.opentelemetry.maven.handler.MojoGoalExecutionHandlerConfiguration;
1919
import io.opentelemetry.maven.semconv.MavenOtelSemanticAttributes;
20-
import java.util.Locale;
2120
import java.util.Map;
2221
import java.util.Optional;
2322
import java.util.stream.Collectors;
24-
import javax.annotation.Nonnull;
25-
import javax.annotation.Nullable;
2623
import org.apache.maven.execution.AbstractExecutionListener;
2724
import org.apache.maven.execution.ExecutionEvent;
2825
import org.apache.maven.execution.ExecutionListener;
@@ -338,19 +335,4 @@ public void sessionEnded(ExecutionEvent event) {
338335
logger.debug("OpenTelemetry: Maven session ended, end root span");
339336
spanRegistry.removeRootSpan().end();
340337
}
341-
342-
private static class ToUpperCaseTextMapGetter implements TextMapGetter<Map<String, String>> {
343-
@Override
344-
public Iterable<String> keys(Map<String, String> environmentVariables) {
345-
return environmentVariables.keySet();
346-
}
347-
348-
@Override
349-
@Nullable
350-
public String get(@Nullable Map<String, String> environmentVariables, @Nonnull String key) {
351-
return environmentVariables == null
352-
? null
353-
: environmentVariables.get(key.toUpperCase(Locale.ROOT));
354-
}
355-
}
356338
}

maven-extension/src/main/java/io/opentelemetry/maven/OtelLifecycleParticipant.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
import org.apache.maven.AbstractMavenLifecycleParticipant;
1212
import org.apache.maven.execution.ExecutionListener;
1313
import org.apache.maven.execution.MavenSession;
14+
import org.eclipse.aether.DefaultRepositorySystemSession;
15+
import org.eclipse.aether.RepositorySystemSession;
16+
import org.eclipse.aether.transfer.TransferListener;
1417
import org.slf4j.Logger;
1518
import org.slf4j.LoggerFactory;
1619

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

2629
private final OtelExecutionListener otelExecutionListener;
2730

31+
private final OtelTransferListener otelTransferListener;
32+
2833
/**
2934
* Manually instantiate {@link OtelExecutionListener} and hook it in the Maven build lifecycle
3035
* because Maven Sisu doesn't load it when Maven Plexus did.
@@ -34,6 +39,17 @@ public final class OtelLifecycleParticipant extends AbstractMavenLifecyclePartic
3439
OpenTelemetrySdkService openTelemetrySdkService, SpanRegistry spanRegistry) {
3540
this.openTelemetrySdkService = openTelemetrySdkService;
3641
this.otelExecutionListener = new OtelExecutionListener(spanRegistry, openTelemetrySdkService);
42+
this.otelTransferListener = new OtelTransferListener(spanRegistry, openTelemetrySdkService);
43+
}
44+
45+
@Override
46+
public void afterSessionStart(MavenSession session) {
47+
// TODO transfers happen before afterProjectsRead() - not sure I understand the issue in the
48+
// comment of afterProjectsRead()
49+
if (openTelemetrySdkService.isTransferInstrumentationEnabled()) {
50+
registerTransferListener(session);
51+
otelTransferListener.startTransferRoot();
52+
}
3753
}
3854

3955
/**
@@ -43,6 +59,13 @@ public final class OtelLifecycleParticipant extends AbstractMavenLifecyclePartic
4359
*/
4460
@Override
4561
public void afterProjectsRead(MavenSession session) {
62+
if (openTelemetrySdkService.isTransferInstrumentationEnabled()) {
63+
otelTransferListener.endTransferRoot();
64+
}
65+
registerExecutionListener(session);
66+
}
67+
68+
void registerExecutionListener(MavenSession session) {
4669
ExecutionListener initialExecutionListener = session.getRequest().getExecutionListener();
4770
if (initialExecutionListener instanceof ChainedExecutionListener
4871
|| initialExecutionListener instanceof OtelExecutionListener) {
@@ -64,6 +87,40 @@ public void afterProjectsRead(MavenSession session) {
6487
}
6588
}
6689

90+
void registerTransferListener(MavenSession session) {
91+
RepositorySystemSession repositorySession = session.getRepositorySession();
92+
TransferListener initialTransferListener = repositorySession.getTransferListener();
93+
if (initialTransferListener instanceof ChainedTransferListener
94+
|| initialTransferListener instanceof OtelTransferListener) {
95+
// already initialized
96+
logger.debug(
97+
"OpenTelemetry: OpenTelemetry extension already registered as transfer listener, skip.");
98+
} else if (initialTransferListener == null) {
99+
setTransferListener(this.otelTransferListener, repositorySession, session);
100+
logger.debug(
101+
"OpenTelemetry: OpenTelemetry extension registered as transfer listener. No transfer listener initially defined");
102+
} else {
103+
setTransferListener(
104+
new ChainedTransferListener(this.otelTransferListener, initialTransferListener),
105+
repositorySession,
106+
session);
107+
logger.debug(
108+
"OpenTelemetry: OpenTelemetry extension registered as transfer listener. InitialTransferListener: {}",
109+
initialTransferListener);
110+
}
111+
}
112+
113+
void setTransferListener(
114+
TransferListener transferListener,
115+
RepositorySystemSession repositorySession,
116+
MavenSession session) {
117+
if (repositorySession instanceof DefaultRepositorySystemSession) {
118+
((DefaultRepositorySystemSession) repositorySession).setTransferListener(transferListener);
119+
} else {
120+
logger.warn("OpenTelemetry: Cannot set transfer listener");
121+
}
122+
}
123+
67124
@Override
68125
public void afterSessionEnd(MavenSession session) {
69126
// Workaround https://issues.apache.org/jira/browse/MNG-8217
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.maven;
7+
8+
import io.opentelemetry.api.trace.Span;
9+
import io.opentelemetry.api.trace.SpanBuilder;
10+
import io.opentelemetry.api.trace.SpanKind;
11+
import io.opentelemetry.api.trace.StatusCode;
12+
import io.opentelemetry.context.Context;
13+
import io.opentelemetry.maven.semconv.MavenOtelSemanticAttributes;
14+
import java.util.Locale;
15+
import java.util.Optional;
16+
import org.apache.maven.execution.ExecutionListener;
17+
import org.apache.maven.execution.MavenSession;
18+
import org.eclipse.aether.transfer.AbstractTransferListener;
19+
import org.eclipse.aether.transfer.TransferEvent;
20+
import org.eclipse.aether.transfer.TransferResource;
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
23+
24+
/**
25+
* Don't mark this class as {@link javax.inject.Named} and {@link javax.inject.Singleton} because
26+
* Maven Sisu doesn't automatically load instance of {@link ExecutionListener} as Maven Extension
27+
* hooks the same way Maven Plexus did so we manually hook this instance of {@link
28+
* ExecutionListener} through the {@link OtelLifecycleParticipant#afterProjectsRead(MavenSession)}.
29+
*/
30+
public final class OtelTransferListener extends AbstractTransferListener {
31+
32+
private static final Logger logger = LoggerFactory.getLogger(OtelTransferListener.class);
33+
34+
private final SpanRegistry spanRegistry;
35+
36+
private final OpenTelemetrySdkService openTelemetrySdkService;
37+
38+
OtelTransferListener(SpanRegistry spanRegistry, OpenTelemetrySdkService openTelemetrySdkService) {
39+
this.spanRegistry = spanRegistry;
40+
this.openTelemetrySdkService = openTelemetrySdkService;
41+
}
42+
43+
/**
44+
* Starts a span to collect transfers that occur before regular execution spans can serve as
45+
* parents.
46+
*/
47+
public void startTransferRoot() {
48+
io.opentelemetry.context.Context context =
49+
openTelemetrySdkService
50+
.getPropagators()
51+
.getTextMapPropagator()
52+
.extract(
53+
io.opentelemetry.context.Context.current(),
54+
System.getenv(),
55+
new ToUpperCaseTextMapGetter());
56+
57+
// TODO question: is this the root span name we want?
58+
String spanName = "Transfer: global";
59+
logger.debug("OpenTelemetry: Start span: {}", spanName);
60+
Span transferSpan =
61+
this.openTelemetrySdkService
62+
.getTracer()
63+
.spanBuilder(spanName)
64+
.setParent(context)
65+
.setSpanKind(SpanKind.SERVER)
66+
.startSpan();
67+
spanRegistry.setRootSpan(transferSpan);
68+
}
69+
70+
/** Ends the root span. */
71+
public void endTransferRoot() {
72+
spanRegistry.getRootSpanNotNull().end();
73+
}
74+
75+
@Override
76+
public void transferInitiated(TransferEvent event) {
77+
ResourceInformation info = createResourceInformation(event);
78+
79+
logger.debug("OpenTelemetry: Maven transfer initiated: span {}:{}", info.type, info.url);
80+
81+
SpanBuilder spanBuilder =
82+
this.openTelemetrySdkService
83+
.getTracer()
84+
.spanBuilder(info.type + ":" + info.url)
85+
.setParent(
86+
Context.current()
87+
.with(Span.wrap(spanRegistry.getRootSpanNotNull().getSpanContext())))
88+
.setAttribute(MavenOtelSemanticAttributes.MAVEN_TRANSFER_URL, info.url)
89+
.setAttribute(MavenOtelSemanticAttributes.MAVEN_TRANSFER_TYPE, info.type);
90+
91+
spanRegistry.putSpan(spanBuilder.startSpan(), event);
92+
}
93+
94+
@Override
95+
public void transferSucceeded(TransferEvent event) {
96+
ResourceInformation info = createResourceInformation(event);
97+
98+
logger.debug("OpenTelemetry: Maven transfer succeeded: span {}:{}", info.type, info.url);
99+
100+
Optional.ofNullable(spanRegistry.removeSpan(event))
101+
.ifPresent(
102+
span -> {
103+
span.setStatus(StatusCode.OK);
104+
finish(span, event);
105+
});
106+
}
107+
108+
@Override
109+
public void transferFailed(TransferEvent event) {
110+
ResourceInformation info = createResourceInformation(event);
111+
112+
logger.debug("OpenTelemetry: Maven transfer failed: span {}:{}", info.type, info.url);
113+
114+
Optional.ofNullable(spanRegistry.removeSpan(event)).ifPresent(span -> fail(span, event));
115+
}
116+
117+
@Override
118+
public void transferCorrupted(TransferEvent event) {
119+
ResourceInformation info = createResourceInformation(event);
120+
121+
logger.debug("OpenTelemetry: Maven transfer corrupted: span {}:{}", info.type, info.url);
122+
123+
Optional.ofNullable(spanRegistry.removeSpan(event)).ifPresent(span -> fail(span, event));
124+
}
125+
126+
void finish(Span span, TransferEvent event) {
127+
span.setAttribute(
128+
MavenOtelSemanticAttributes.MAVEN_TRANSFER_SIZE,
129+
Long.toString(event.getTransferredBytes()));
130+
span.end();
131+
}
132+
133+
void fail(Span span, TransferEvent event) {
134+
span.setStatus(
135+
StatusCode.ERROR,
136+
Optional.ofNullable(event.getException()).map(Exception::getMessage).orElse("n/a"));
137+
finish(span, event);
138+
}
139+
140+
ResourceInformation createResourceInformation(TransferEvent event) {
141+
TransferResource resource = event.getResource();
142+
return new ResourceInformation(
143+
resource.getRepositoryUrl() + resource.getResourceName(),
144+
event.getRequestType().toString().toLowerCase(Locale.ROOT));
145+
}
146+
147+
private static class ResourceInformation {
148+
protected final String url;
149+
protected final String type;
150+
151+
ResourceInformation(String url, String type) {
152+
this.url = url;
153+
this.type = type;
154+
}
155+
}
156+
}

0 commit comments

Comments
 (0)