Skip to content

Commit fad7b24

Browse files
CrustackMateusz Rzeszutek
andauthored
Add Jodd-Http instrumentation (#7868)
This PR resolves #7629 This adds javaagent instrumentation for the [jodd-http](https://http.jodd.org/) `HttpRequest`. It creates `Http Client Spans` and `Http Client Metrics`, the lowest supported version is `org.jodd:jodd-http:4.2.0` (most recent: `6.3.0`), since this is the first version of the library supporting java 8, having follow-redirect capability and `HttpRequest#overwriteHeader()` method. The instrumented method's signature and return type `HttpRequest#send()` has not been modified since, and therefore the instrumentation works for all `jodd-http` versions above `4.2.0`. Since this is my first contribution/instrumentation, I orientated myself on the `apache-httpclient-5.0` instrumentation, but obviously I would be glad to get some feedback on this --------- Co-authored-by: Mateusz Rzeszutek <[email protected]>
1 parent 6cb00d3 commit fad7b24

File tree

11 files changed

+467
-1
lines changed

11 files changed

+467
-1
lines changed

docs/supported-libraries.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ These are the supported libraries and frameworks:
7777
| [JDBC](https://docs.oracle.com/javase/8/docs/api/java/sql/package-summary.html) | Java 8+ | [opentelemetry-jdbc](../instrumentation/jdbc/library) | [Database Client Spans] |
7878
| [Jedis](https://github.com/xetorthio/jedis) | 1.4+ | N/A | [Database Client Spans] |
7979
| [JMS](https://javaee.github.io/javaee-spec/javadocs/javax/jms/package-summary.html) | 1.1+ | N/A | [Messaging Spans] |
80+
| [Jodd Http](https://javadoc.io/doc/org.jodd/jodd-http/latest/index.html) | 4.2+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] |
8081
| [JSP](https://javaee.github.io/javaee-spec/javadocs/javax/servlet/jsp/package-summary.html) | 2.3+ | N/A | none |
8182
| [Kotlin Coroutines](https://kotlinlang.org/docs/coroutines-overview.html) | 1.0+ | N/A | Context propagation |
8283
| [Ktor](https://github.com/ktorio/ktor) | 1.0+ | [opentelemetry-ktor-1.0](../instrumentation/ktor/ktor-1.0/library),<br>[opentelemetry-ktor-2.0](../instrumentation/ktor/ktor-2.0/library) | [HTTP Server Spans], [HTTP Server Metrics] |
@@ -101,7 +102,7 @@ These are the supported libraries and frameworks:
101102
| [Rediscala](https://github.com/etaty/rediscala) | 1.8+ | N/A | [Database Client Spans] |
102103
| [Redisson](https://github.com/redisson/redisson) | 3.0+ | N/A | [Database Client Spans] |
103104
| [RESTEasy](https://resteasy.github.io/) | 3.0+ | N/A | Provides `http.route` [2], Controller Spans [3] |
104-
| [Restlet](https://restlet.github.io/) | 1.0+ | [opentelemetry-restlet-1.1](../instrumentation/restlet/restlet-1.1/library),<br>[opentelemetry-restlet-2.0](../instrumentation/restlet/restlet-2.0/library) | [HTTP Server Spans], [HTTP Server Metrics] |
105+
| [Restlet](https://restlet.github.io/) | 1.0+ | [opentelemetry-restlet-1.1](../instrumentation/restlet/restlet-1.1/library),<br>[opentelemetry-restlet-2.0](../instrumentation/restlet/restlet-2.0/library) | [HTTP Server Spans], [HTTP Server Metrics] |
105106
| [RMI](https://docs.oracle.com/en/java/javase/11/docs/api/java.rmi/java/rmi/package-summary.html) | Java 8+ | | [RPC Client Spans], [RPC Server Spans] |
106107
| [RxJava](https://github.com/ReactiveX/RxJava) | 1.0+ | [opentelemetry-rxjava-1.0](../instrumentation/rxjava/rxjava-1.0/library),<br>[opentelemetry-rxjava-2.0](../instrumentation/rxjava/rxjava-2.0/library),<br>[opentelemetry-rxjava-3.0](../instrumentation/rxjava/rxjava-3.0/library),<br>[opentelemetry-rxjava-3.1.1](../instrumentation/rxjava/rxjava-3.1.1/library) | Context propagation |
107108
| [Scala ForkJoinPool](https://www.scala-lang.org/api/2.12.0/scala/concurrent/forkjoin/package$$ForkJoinPool$.html) | 2.8+ | N/A | Context propagation |
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
group.set("org.jodd")
8+
module.set("jodd-http")
9+
versions.set("[4.2.0,)")
10+
}
11+
}
12+
13+
dependencies {
14+
// 4.2 is the first version with java 8, follow-redirects and HttpRequest#headerOverwrite method
15+
library("org.jodd:jodd-http:4.2.0")
16+
17+
testImplementation(project(":instrumentation:jodd-http-4.2:javaagent"))
18+
testImplementation(project(":instrumentation-api-semconv"))
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2;
7+
8+
import io.opentelemetry.context.propagation.TextMapSetter;
9+
import javax.annotation.Nullable;
10+
import jodd.http.HttpRequest;
11+
12+
enum HttpHeaderSetter implements TextMapSetter<HttpRequest> {
13+
INSTANCE;
14+
15+
@Override
16+
public void set(@Nullable HttpRequest carrier, String key, String value) {
17+
if (carrier == null) {
18+
return;
19+
}
20+
carrier.headerOverwrite(key, value);
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2;
7+
8+
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HttpFlavorValues.HTTP_1_0;
9+
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HttpFlavorValues.HTTP_1_1;
10+
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HttpFlavorValues.HTTP_2_0;
11+
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HttpFlavorValues.HTTP_3_0;
12+
13+
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesGetter;
14+
import java.util.Arrays;
15+
import java.util.HashSet;
16+
import java.util.List;
17+
import java.util.Set;
18+
import java.util.logging.Level;
19+
import java.util.logging.Logger;
20+
import javax.annotation.Nullable;
21+
import jodd.http.HttpRequest;
22+
import jodd.http.HttpResponse;
23+
24+
final class JoddHttpHttpAttributesGetter
25+
implements HttpClientAttributesGetter<HttpRequest, HttpResponse> {
26+
private static final Logger logger =
27+
Logger.getLogger(JoddHttpHttpAttributesGetter.class.getName());
28+
private static final Set<String> ALLOWED_HTTP_FLAVORS =
29+
new HashSet<>(Arrays.asList(HTTP_1_0, HTTP_1_1, HTTP_2_0, HTTP_3_0));
30+
31+
@Override
32+
public String getMethod(HttpRequest request) {
33+
return request.method();
34+
}
35+
36+
@Override
37+
public String getUrl(HttpRequest request) {
38+
return request.url();
39+
}
40+
41+
@Override
42+
public List<String> getRequestHeader(HttpRequest request, String name) {
43+
return request.headers(name);
44+
}
45+
46+
@Override
47+
public Integer getStatusCode(
48+
HttpRequest request, HttpResponse response, @Nullable Throwable error) {
49+
return response.statusCode();
50+
}
51+
52+
@Override
53+
@Nullable
54+
public String getFlavor(HttpRequest request, @Nullable HttpResponse response) {
55+
String httpVersion = request.httpVersion();
56+
if (httpVersion == null && response != null) {
57+
httpVersion = response.httpVersion();
58+
}
59+
if (httpVersion != null) {
60+
if (httpVersion.contains("/")) {
61+
httpVersion = httpVersion.substring(httpVersion.lastIndexOf("/") + 1);
62+
}
63+
64+
if (ALLOWED_HTTP_FLAVORS.contains(httpVersion)) {
65+
return httpVersion;
66+
}
67+
}
68+
logger.log(Level.FINE, "unexpected http protocol version: {0}", httpVersion);
69+
return null;
70+
}
71+
72+
@Override
73+
public List<String> getResponseHeader(HttpRequest request, HttpResponse response, String name) {
74+
return response.headers(name);
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2;
7+
8+
import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
9+
import static io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2.JoddHttpSingletons.instrumenter;
10+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
11+
import static net.bytebuddy.matcher.ElementMatchers.named;
12+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
13+
14+
import io.opentelemetry.context.Context;
15+
import io.opentelemetry.context.Scope;
16+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
17+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
18+
import jodd.http.HttpRequest;
19+
import jodd.http.HttpResponse;
20+
import net.bytebuddy.asm.Advice;
21+
import net.bytebuddy.description.type.TypeDescription;
22+
import net.bytebuddy.matcher.ElementMatcher;
23+
24+
class JoddHttpInstrumentation implements TypeInstrumentation {
25+
26+
@Override
27+
public ElementMatcher<TypeDescription> typeMatcher() {
28+
return named("jodd.http.HttpRequest");
29+
}
30+
31+
@Override
32+
public void transform(TypeTransformer transformer) {
33+
transformer.applyAdviceToMethod(
34+
isMethod().and(named("send")).and(takesArguments(0)),
35+
this.getClass().getName() + "$RequestAdvice");
36+
}
37+
38+
@SuppressWarnings("unused")
39+
public static class RequestAdvice {
40+
41+
@Advice.OnMethodEnter(suppress = Throwable.class)
42+
public static void methodEnter(
43+
@Advice.This HttpRequest request,
44+
@Advice.Local("otelContext") Context context,
45+
@Advice.Local("otelScope") Scope scope) {
46+
Context parentContext = currentContext();
47+
if (!instrumenter().shouldStart(parentContext, request)) {
48+
return;
49+
}
50+
context = instrumenter().start(parentContext, request);
51+
scope = context.makeCurrent();
52+
}
53+
54+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
55+
public static void methodExit(
56+
@Advice.This HttpRequest request,
57+
@Advice.Return HttpResponse response,
58+
@Advice.Thrown Throwable throwable,
59+
@Advice.Local("otelContext") Context context,
60+
@Advice.Local("otelScope") Scope scope) {
61+
if (scope == null) {
62+
return;
63+
}
64+
scope.close();
65+
instrumenter().end(context, request, response, throwable);
66+
}
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2;
7+
8+
import com.google.auto.service.AutoService;
9+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
10+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
11+
import java.util.Collections;
12+
import java.util.List;
13+
14+
@AutoService(InstrumentationModule.class)
15+
public class JoddHttpInstrumentationModule extends InstrumentationModule {
16+
17+
public JoddHttpInstrumentationModule() {
18+
super("jodd-http", "jodd-http-4.2");
19+
}
20+
21+
@Override
22+
public List<TypeInstrumentation> typeInstrumentations() {
23+
return Collections.singletonList(new JoddHttpInstrumentation());
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2;
7+
8+
import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesGetter;
9+
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
10+
import javax.annotation.Nullable;
11+
import jodd.http.HttpRequest;
12+
import jodd.http.HttpResponse;
13+
14+
final class JoddHttpNetAttributesGetter
15+
implements NetClientAttributesGetter<HttpRequest, HttpResponse> {
16+
17+
@Override
18+
public String getTransport(HttpRequest request, @Nullable HttpResponse response) {
19+
return SemanticAttributes.NetTransportValues.IP_TCP;
20+
}
21+
22+
@Override
23+
@Nullable
24+
public String getPeerName(HttpRequest request) {
25+
return request.host();
26+
}
27+
28+
@Override
29+
public Integer getPeerPort(HttpRequest request) {
30+
return request.port();
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2;
7+
8+
import io.opentelemetry.api.GlobalOpenTelemetry;
9+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
10+
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor;
11+
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics;
12+
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
13+
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor;
14+
import io.opentelemetry.instrumentation.api.instrumenter.net.PeerServiceAttributesExtractor;
15+
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
16+
import jodd.http.HttpRequest;
17+
import jodd.http.HttpResponse;
18+
19+
public final class JoddHttpSingletons {
20+
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jodd-http-4.2";
21+
22+
private static final Instrumenter<HttpRequest, HttpResponse> INSTRUMENTER;
23+
24+
static {
25+
JoddHttpHttpAttributesGetter httpAttributesGetter = new JoddHttpHttpAttributesGetter();
26+
JoddHttpNetAttributesGetter netAttributesGetter = new JoddHttpNetAttributesGetter();
27+
28+
INSTRUMENTER =
29+
Instrumenter.<HttpRequest, HttpResponse>builder(
30+
GlobalOpenTelemetry.get(),
31+
INSTRUMENTATION_NAME,
32+
HttpSpanNameExtractor.create(httpAttributesGetter))
33+
.setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter))
34+
.addAttributesExtractor(
35+
HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter)
36+
.setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders())
37+
.setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders())
38+
.build())
39+
.addAttributesExtractor(
40+
PeerServiceAttributesExtractor.create(
41+
netAttributesGetter, CommonConfig.get().getPeerServiceMapping()))
42+
.addOperationMetrics(HttpClientMetrics.get())
43+
.buildClientInstrumenter(HttpHeaderSetter.INSTANCE);
44+
}
45+
46+
public static Instrumenter<HttpRequest, HttpResponse> instrumenter() {
47+
return INSTRUMENTER;
48+
}
49+
50+
private JoddHttpSingletons() {}
51+
}

0 commit comments

Comments
 (0)