Skip to content

Commit

Permalink
Add Jodd-Http instrumentation (#7868)
Browse files Browse the repository at this point in the history
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 <mrzeszutek@splunk.com>
  • Loading branch information
PhilKes and Mateusz Rzeszutek committed Feb 23, 2023
1 parent 6cb00d3 commit fad7b24
Show file tree
Hide file tree
Showing 11 changed files with 467 additions and 1 deletion.
3 changes: 2 additions & 1 deletion docs/supported-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ These are the supported libraries and frameworks:
| [JDBC](https://docs.oracle.com/javase/8/docs/api/java/sql/package-summary.html) | Java 8+ | [opentelemetry-jdbc](../instrumentation/jdbc/library) | [Database Client Spans] |
| [Jedis](https://github.com/xetorthio/jedis) | 1.4+ | N/A | [Database Client Spans] |
| [JMS](https://javaee.github.io/javaee-spec/javadocs/javax/jms/package-summary.html) | 1.1+ | N/A | [Messaging Spans] |
| [Jodd Http](https://javadoc.io/doc/org.jodd/jodd-http/latest/index.html) | 4.2+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] |
| [JSP](https://javaee.github.io/javaee-spec/javadocs/javax/servlet/jsp/package-summary.html) | 2.3+ | N/A | none |
| [Kotlin Coroutines](https://kotlinlang.org/docs/coroutines-overview.html) | 1.0+ | N/A | Context propagation |
| [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] |
Expand All @@ -101,7 +102,7 @@ These are the supported libraries and frameworks:
| [Rediscala](https://github.com/etaty/rediscala) | 1.8+ | N/A | [Database Client Spans] |
| [Redisson](https://github.com/redisson/redisson) | 3.0+ | N/A | [Database Client Spans] |
| [RESTEasy](https://resteasy.github.io/) | 3.0+ | N/A | Provides `http.route` [2], Controller Spans [3] |
| [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] |
| [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] |
| [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] |
| [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 |
| [Scala ForkJoinPool](https://www.scala-lang.org/api/2.12.0/scala/concurrent/forkjoin/package$$ForkJoinPool$.html) | 2.8+ | N/A | Context propagation |
Expand Down
19 changes: 19 additions & 0 deletions instrumentation/jodd-http-4.2/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("org.jodd")
module.set("jodd-http")
versions.set("[4.2.0,)")
}
}

dependencies {
// 4.2 is the first version with java 8, follow-redirects and HttpRequest#headerOverwrite method
library("org.jodd:jodd-http:4.2.0")

testImplementation(project(":instrumentation:jodd-http-4.2:javaagent"))
testImplementation(project(":instrumentation-api-semconv"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2;

import io.opentelemetry.context.propagation.TextMapSetter;
import javax.annotation.Nullable;
import jodd.http.HttpRequest;

enum HttpHeaderSetter implements TextMapSetter<HttpRequest> {
INSTANCE;

@Override
public void set(@Nullable HttpRequest carrier, String key, String value) {
if (carrier == null) {
return;
}
carrier.headerOverwrite(key, value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2;

import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HttpFlavorValues.HTTP_1_0;
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HttpFlavorValues.HTTP_1_1;
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HttpFlavorValues.HTTP_2_0;
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HttpFlavorValues.HTTP_3_0;

import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesGetter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import jodd.http.HttpRequest;
import jodd.http.HttpResponse;

final class JoddHttpHttpAttributesGetter
implements HttpClientAttributesGetter<HttpRequest, HttpResponse> {
private static final Logger logger =
Logger.getLogger(JoddHttpHttpAttributesGetter.class.getName());
private static final Set<String> ALLOWED_HTTP_FLAVORS =
new HashSet<>(Arrays.asList(HTTP_1_0, HTTP_1_1, HTTP_2_0, HTTP_3_0));

@Override
public String getMethod(HttpRequest request) {
return request.method();
}

@Override
public String getUrl(HttpRequest request) {
return request.url();
}

@Override
public List<String> getRequestHeader(HttpRequest request, String name) {
return request.headers(name);
}

@Override
public Integer getStatusCode(
HttpRequest request, HttpResponse response, @Nullable Throwable error) {
return response.statusCode();
}

@Override
@Nullable
public String getFlavor(HttpRequest request, @Nullable HttpResponse response) {
String httpVersion = request.httpVersion();
if (httpVersion == null && response != null) {
httpVersion = response.httpVersion();
}
if (httpVersion != null) {
if (httpVersion.contains("/")) {
httpVersion = httpVersion.substring(httpVersion.lastIndexOf("/") + 1);
}

if (ALLOWED_HTTP_FLAVORS.contains(httpVersion)) {
return httpVersion;
}
}
logger.log(Level.FINE, "unexpected http protocol version: {0}", httpVersion);
return null;
}

@Override
public List<String> getResponseHeader(HttpRequest request, HttpResponse response, String name) {
return response.headers(name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2;

import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2.JoddHttpSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import jodd.http.HttpRequest;
import jodd.http.HttpResponse;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

class JoddHttpInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("jodd.http.HttpRequest");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod().and(named("send")).and(takesArguments(0)),
this.getClass().getName() + "$RequestAdvice");
}

@SuppressWarnings("unused")
public static class RequestAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void methodEnter(
@Advice.This HttpRequest request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
Context parentContext = currentContext();
if (!instrumenter().shouldStart(parentContext, request)) {
return;
}
context = instrumenter().start(parentContext, request);
scope = context.makeCurrent();
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void methodExit(
@Advice.This HttpRequest request,
@Advice.Return HttpResponse response,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
if (scope == null) {
return;
}
scope.close();
instrumenter().end(context, request, response, throwable);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.Collections;
import java.util.List;

@AutoService(InstrumentationModule.class)
public class JoddHttpInstrumentationModule extends InstrumentationModule {

public JoddHttpInstrumentationModule() {
super("jodd-http", "jodd-http-4.2");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Collections.singletonList(new JoddHttpInstrumentation());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2;

import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesGetter;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import javax.annotation.Nullable;
import jodd.http.HttpRequest;
import jodd.http.HttpResponse;

final class JoddHttpNetAttributesGetter
implements NetClientAttributesGetter<HttpRequest, HttpResponse> {

@Override
public String getTransport(HttpRequest request, @Nullable HttpResponse response) {
return SemanticAttributes.NetTransportValues.IP_TCP;
}

@Override
@Nullable
public String getPeerName(HttpRequest request) {
return request.host();
}

@Override
public Integer getPeerPort(HttpRequest request) {
return request.port();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.net.PeerServiceAttributesExtractor;
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
import jodd.http.HttpRequest;
import jodd.http.HttpResponse;

public final class JoddHttpSingletons {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jodd-http-4.2";

private static final Instrumenter<HttpRequest, HttpResponse> INSTRUMENTER;

static {
JoddHttpHttpAttributesGetter httpAttributesGetter = new JoddHttpHttpAttributesGetter();
JoddHttpNetAttributesGetter netAttributesGetter = new JoddHttpNetAttributesGetter();

INSTRUMENTER =
Instrumenter.<HttpRequest, HttpResponse>builder(
GlobalOpenTelemetry.get(),
INSTRUMENTATION_NAME,
HttpSpanNameExtractor.create(httpAttributesGetter))
.setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter))
.addAttributesExtractor(
HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter)
.setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders())
.setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders())
.build())
.addAttributesExtractor(
PeerServiceAttributesExtractor.create(
netAttributesGetter, CommonConfig.get().getPeerServiceMapping()))
.addOperationMetrics(HttpClientMetrics.get())
.buildClientInstrumenter(HttpHeaderSetter.INSTANCE);
}

public static Instrumenter<HttpRequest, HttpResponse> instrumenter() {
return INSTRUMENTER;
}

private JoddHttpSingletons() {}
}
Loading

0 comments on commit fad7b24

Please sign in to comment.