Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add jdk.httpserver instrumentation #13243

Merged
merged 32 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
76dbc9c
start
SentryMan Feb 6, 2025
0c28cba
start
SentryMan Feb 6, 2025
874c553
Update AbstractJdkHttpServerTest.java
SentryMan Feb 6, 2025
8651b06
agent
SentryMan Feb 7, 2025
d0d35eb
spotless
SentryMan Feb 7, 2025
60c3382
Update AbstractJdkHttpServerTest.java
SentryMan Feb 7, 2025
cd1e121
Update AbstractJdkHttpServerTest.java
SentryMan Feb 7, 2025
2f19961
span filter
SentryMan Feb 7, 2025
e386c06
Update AbstractJdkHttpServerTest.java
SentryMan Feb 7, 2025
1433ddc
Update AbstractJdkHttpServerTest.java
SentryMan Feb 7, 2025
60b29fb
Update AbstractJdkHttpServerTest.java
SentryMan Feb 7, 2025
959b667
address comments
SentryMan Feb 7, 2025
578e275
address more comments
SentryMan Feb 7, 2025
43a78c2
http pipeline
SentryMan Feb 7, 2025
8ee019f
add executor
SentryMan Feb 7, 2025
43cc1cf
Update supported-libraries.md
SentryMan Feb 7, 2025
2332175
try future stop
SentryMan Feb 8, 2025
d2698c0
Update AbstractJdkHttpServerTest.java
SentryMan Feb 8, 2025
fd3662a
Update AbstractJdkHttpServerTest.java
SentryMan Feb 8, 2025
15383a8
Update AbstractJdkHttpServerTest.java
SentryMan Feb 8, 2025
10b3679
Create README.md
SentryMan Feb 11, 2025
85c3cf2
Merge branch 'main' into jdk.httpserver
laurit Feb 12, 2025
46cb20f
update fossa configuration
laurit Feb 12, 2025
6487593
rename classes
laurit Feb 13, 2025
ec176d8
fix jdk8
laurit Feb 13, 2025
373abd7
modify tests a bit, add a method that configures tracing for HttpContext
laurit Feb 13, 2025
dd43a41
spotless
laurit Feb 13, 2025
a46b078
reformat table
laurit Feb 13, 2025
f6b556a
enable response customizer test
laurit Feb 13, 2025
a42e07e
package change
SentryMan Feb 14, 2025
f78ec08
static imports
SentryMan Feb 14, 2025
ec93763
Merge branch 'main' into jdk.httpserver
laurit Feb 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .fossa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,12 @@ targets:
- type: gradle
path: ./
target: ':instrumentation:java-http-client:library'
- type: gradle
path: ./
target: ':instrumentation:java-http-server:javaagent'
- type: gradle
path: ./
target: ':instrumentation:java-http-server:library'
- type: gradle
path: ./
target: ':instrumentation:java-util-logging:javaagent'
Expand Down
1 change: 1 addition & 0 deletions docs/supported-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ These are the supported libraries and frameworks:
| [InfluxDB Client](https://github.com/influxdata/influxdb-java) | 2.4+ | N/A | [Database Client Spans], [Database Client Metrics] [6] |
| [Java Executors](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html) | Java 8+ | N/A | Context propagation |
| [Java Http Client](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/package-summary.html) | Java 11+ | [opentelemetry-java-http-client](../instrumentation/java-http-client/library) | [HTTP Client Spans], [HTTP Client Metrics] |
| [Java Http Server](https://docs.oracle.com/en/java/javase/21/docs/api/jdk.httpserver/module-summary.html) | Java 8+ | [opentelemetry-java-http-server](../instrumentation/java-http-server/library) | [HTTP Server Spans], [HTTP Server Metrics] |
| [java.util.logging](https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html) | Java 8+ | N/A | none |
| [Java Platform](https://docs.oracle.com/javase/8/docs/api/java/lang/management/ManagementFactory.html) | Java 8+ | [opentelemetry-runtime-telemetry-java8](../instrumentation/runtime-telemetry/runtime-telemetry-java8/library),<br>[opentelemetry-runtime-telemetry-java17](../instrumentation/runtime-telemetry/runtime-telemetry-java17/library),<br>[opentelemetry-resources](../instrumentation/resources/library) | [JVM Runtime Metrics] |
| [Javalin](https://javalin.io/) | 5.0+ | N/A | Provides `http.route` [2] |
Expand Down
14 changes: 14 additions & 0 deletions instrumentation/java-http-server/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
coreJdk()
}
}

dependencies {
implementation(project(":instrumentation:java-http-server:library"))
testImplementation(project(":instrumentation:java-http-server:testing"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.javahttpserver;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.extendsClass;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;

import com.sun.net.httpserver.HttpContext;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

public class HttpServerInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return extendsClass(named("com.sun.net.httpserver.HttpServer"));
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod().and(isPublic()).and(named("createContext")),
HttpServerInstrumentation.class.getName() + "$BuildAdvice");
}

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

@Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(@Advice.Return HttpContext httpContext) {
httpContext.getFilters().addAll(JavaHttpServerSingletons.FILTERS);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.javahttpserver;

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 JavaHttpServerInstrumentationModule extends InstrumentationModule {
public JavaHttpServerInstrumentationModule() {
super("java-http-server");
}

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

package io.opentelemetry.javaagent.instrumentation.javahttpserver;

import com.sun.net.httpserver.Headers;
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseMutator;

enum JavaHttpServerResponseMutator implements HttpServerResponseMutator<Headers> {
INSTANCE;

@Override
public void appendHeader(Headers response, String name, String value) {
response.add(name, value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.javahttpserver;

import com.sun.net.httpserver.Filter;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.config.internal.CommonConfig;
import io.opentelemetry.instrumentation.javahttpserver.JavaHttpServerTelemetry;
import io.opentelemetry.instrumentation.javahttpserver.JavaHttpServerTelemetryBuilder;
import io.opentelemetry.instrumentation.javahttpserver.internal.JavaHttpServerInstrumenterBuilderUtil;
import io.opentelemetry.javaagent.bootstrap.internal.AgentCommonConfig;
import java.util.Arrays;
import java.util.List;

public final class JavaHttpServerSingletons {

public static final List<Filter> FILTERS;

static {
CommonConfig config = AgentCommonConfig.get();

JavaHttpServerTelemetryBuilder serverBuilder =
JavaHttpServerTelemetry.builder(GlobalOpenTelemetry.get());
JavaHttpServerInstrumenterBuilderUtil.getServerBuilderExtractor()
.apply(serverBuilder)
.configure(config);
JavaHttpServerTelemetry serverTelemetry = serverBuilder.build();

FILTERS = Arrays.asList(serverTelemetry.newFilter(), new ResponseCustomizingFilter());
}

private JavaHttpServerSingletons() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.javahttpserver;

import com.sun.net.httpserver.Filter;
import com.sun.net.httpserver.HttpExchange;
import io.opentelemetry.context.Context;
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder;
import java.io.IOException;

class ResponseCustomizingFilter extends Filter {

ResponseCustomizingFilter() {}

@Override
public void doFilter(HttpExchange exchange, Chain chain) throws IOException {
Context context = Context.current();
HttpServerResponseCustomizerHolder.getCustomizer()
.customize(context, exchange.getResponseHeaders(), JavaHttpServerResponseMutator.INSTANCE);
chain.doFilter(exchange);
}

@Override
public String description() {
return "OpenTelemetry response customizing filter";
}
}
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.javahttpserver;

import io.opentelemetry.instrumentation.javahttpserver.AbstractJavaHttpServerTest;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions;
import org.junit.jupiter.api.extension.RegisterExtension;

class JavaHttpServerTest extends AbstractJavaHttpServerTest {

@RegisterExtension
static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent();

@Override
protected void configure(HttpServerTestOptions options) {
super.configure(options);

options.setHasResponseCustomizer(serverEndpoint -> true);
}
}
63 changes: 63 additions & 0 deletions instrumentation/java-http-server/library/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Library Instrumentation for Java HTTP Server

Provides OpenTelemetry instrumentation for [Java HTTP Server](https://docs.oracle.com/en/java/javase/21/docs/api/jdk.httpserver/module-summary.html).

## Quickstart

### Add these dependencies to your project

Replace `OPENTELEMETRY_VERSION` with the [latest
release](https://search.maven.org/search?q=g:io.opentelemetry.instrumentation%20AND%20a:opentelemetry-java-http-server).

For Maven, add to your `pom.xml` dependencies:

```xml
<dependencies>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-java-http-server</artifactId>
<version>OPENTELEMETRY_VERSION</version>
</dependency>
</dependencies>
```

For Gradle, add to your dependencies:

```groovy
implementation("io.opentelemetry.instrumentation:opentelemetry-java-http-server:OPENTELEMETRY_VERSION")
```

### Usage

The instrumentation library contains a `Filter` wrapper that provides OpenTelemetry-based spans
and context propagation.

```java

import java.io.IOException;
import java.net.InetSocketAddress;

import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpServer;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.sdk.OpenTelemetrySdk;

public class Application {

static void main(String args) throws IOException {

final HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
final HttpContext context =
server.createContext(
"/",
ctx -> {
// http logic
});

OpenTelemetry openTelemetry = //...

JavaHttpServerTelemetry.create(openTelemetry).configure(context);
}
}
```
8 changes: 8 additions & 0 deletions instrumentation/java-http-server/library/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
plugins {
id("otel.library-instrumentation")
id("otel.nullaway-conventions")
}

dependencies {
testImplementation(project(":instrumentation:java-http-server:testing"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.javahttpserver;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpsExchange;
import io.opentelemetry.instrumentation.api.internal.HttpProtocolUtil;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesGetter;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;

enum JavaHttpServerAttributesGetter
implements HttpServerAttributesGetter<HttpExchange, HttpExchange> {
INSTANCE;

@Override
public String getHttpRequestMethod(HttpExchange exchange) {
return exchange.getRequestMethod();
}

@Override
public String getUrlScheme(HttpExchange exchange) {
return exchange instanceof HttpsExchange ? "https" : "http";
}

@Override
public String getUrlPath(HttpExchange exchange) {
return exchange.getRequestURI().getPath();
}

@Nullable
@Override
public String getUrlQuery(HttpExchange exchange) {
return exchange.getRequestURI().getQuery();
}

@Override
public List<String> getHttpRequestHeader(HttpExchange exchange, String name) {
return exchange.getRequestHeaders().getOrDefault(name, Collections.emptyList());
}

@Nullable
@Override
public Integer getHttpResponseStatusCode(
HttpExchange exchange, @Nullable HttpExchange res, @Nullable Throwable error) {
int status = exchange.getResponseCode();
return status != -1 ? status : null;
}

@Override
public List<String> getHttpResponseHeader(
HttpExchange exchange, @Nullable HttpExchange res, String name) {
return exchange.getResponseHeaders().getOrDefault(name, Collections.emptyList());
}

@Override
public String getHttpRoute(HttpExchange exchange) {
return exchange.getHttpContext().getPath();
}

@Override
public String getNetworkProtocolName(HttpExchange exchange, @Nullable HttpExchange res) {
return HttpProtocolUtil.getProtocol(exchange.getProtocol());
}

@Override
public String getNetworkProtocolVersion(HttpExchange exchange, @Nullable HttpExchange res) {
return HttpProtocolUtil.getVersion(exchange.getProtocol());
}

@Override
public InetSocketAddress getNetworkPeerInetSocketAddress(
HttpExchange exchange, @Nullable HttpExchange res) {
return exchange.getRemoteAddress();
}

@Override
public InetSocketAddress getNetworkLocalInetSocketAddress(
HttpExchange exchange, @Nullable HttpExchange res) {
return exchange.getLocalAddress();
}
}
Loading
Loading