Skip to content

Commit 8d057a2

Browse files
SentryManlaurit
andauthored
Add jdk.httpserver instrumentation (#13243)
Co-authored-by: Lauri Tulmin <[email protected]>
1 parent f2e51a0 commit 8d057a2

File tree

23 files changed

+936
-1
lines changed

23 files changed

+936
-1
lines changed

.fossa.yml

+6
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,12 @@ targets:
148148
- type: gradle
149149
path: ./
150150
target: ':instrumentation:java-http-client:library'
151+
- type: gradle
152+
path: ./
153+
target: ':instrumentation:java-http-server:javaagent'
154+
- type: gradle
155+
path: ./
156+
target: ':instrumentation:java-http-server:library'
151157
- type: gradle
152158
path: ./
153159
target: ':instrumentation:java-util-logging:javaagent'

docs/supported-libraries.md

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ These are the supported libraries and frameworks:
8080
| [InfluxDB Client](https://github.com/influxdata/influxdb-java) | 2.4+ | N/A | [Database Client Spans], [Database Client Metrics]&nbsp;[6] |
8181
| [Java Executors](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html) | Java 8+ | N/A | Context propagation |
8282
| [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] |
83+
| [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] |
8384
| [java.util.logging](https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html) | Java 8+ | N/A | none |
8485
| [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] |
8586
| [Javalin](https://javalin.io/) | 5.0+ | N/A | Provides `http.route` [2] |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
coreJdk()
8+
}
9+
}
10+
11+
dependencies {
12+
implementation(project(":instrumentation:java-http-server:library"))
13+
testImplementation(project(":instrumentation:java-http-server:testing"))
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.javahttpserver;
7+
8+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.extendsClass;
9+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
10+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
11+
import static net.bytebuddy.matcher.ElementMatchers.named;
12+
13+
import com.sun.net.httpserver.HttpContext;
14+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
15+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
16+
import net.bytebuddy.asm.Advice;
17+
import net.bytebuddy.description.type.TypeDescription;
18+
import net.bytebuddy.matcher.ElementMatcher;
19+
20+
public class HttpServerInstrumentation implements TypeInstrumentation {
21+
22+
@Override
23+
public ElementMatcher<TypeDescription> typeMatcher() {
24+
return extendsClass(named("com.sun.net.httpserver.HttpServer"));
25+
}
26+
27+
@Override
28+
public void transform(TypeTransformer transformer) {
29+
transformer.applyAdviceToMethod(
30+
isMethod().and(isPublic()).and(named("createContext")),
31+
HttpServerInstrumentation.class.getName() + "$BuildAdvice");
32+
}
33+
34+
@SuppressWarnings("unused")
35+
public static class BuildAdvice {
36+
37+
@Advice.OnMethodExit(suppress = Throwable.class)
38+
public static void onExit(@Advice.Return HttpContext httpContext) {
39+
httpContext.getFilters().addAll(JavaHttpServerSingletons.FILTERS);
40+
}
41+
}
42+
}
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.javahttpserver;
7+
8+
import static java.util.Collections.singletonList;
9+
10+
import com.google.auto.service.AutoService;
11+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
12+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
13+
import java.util.List;
14+
15+
@AutoService(InstrumentationModule.class)
16+
public class JavaHttpServerInstrumentationModule extends InstrumentationModule {
17+
public JavaHttpServerInstrumentationModule() {
18+
super("java-http-server");
19+
}
20+
21+
@Override
22+
public List<TypeInstrumentation> typeInstrumentations() {
23+
return singletonList(new HttpServerInstrumentation());
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.javahttpserver;
7+
8+
import com.sun.net.httpserver.Headers;
9+
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseMutator;
10+
11+
enum JavaHttpServerResponseMutator implements HttpServerResponseMutator<Headers> {
12+
INSTANCE;
13+
14+
@Override
15+
public void appendHeader(Headers response, String name, String value) {
16+
response.add(name, value);
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.javahttpserver;
7+
8+
import com.sun.net.httpserver.Filter;
9+
import io.opentelemetry.api.GlobalOpenTelemetry;
10+
import io.opentelemetry.instrumentation.api.incubator.config.internal.CommonConfig;
11+
import io.opentelemetry.instrumentation.javahttpserver.JavaHttpServerTelemetry;
12+
import io.opentelemetry.instrumentation.javahttpserver.JavaHttpServerTelemetryBuilder;
13+
import io.opentelemetry.instrumentation.javahttpserver.internal.JavaHttpServerInstrumenterBuilderUtil;
14+
import io.opentelemetry.javaagent.bootstrap.internal.AgentCommonConfig;
15+
import java.util.Arrays;
16+
import java.util.List;
17+
18+
public final class JavaHttpServerSingletons {
19+
20+
public static final List<Filter> FILTERS;
21+
22+
static {
23+
CommonConfig config = AgentCommonConfig.get();
24+
25+
JavaHttpServerTelemetryBuilder serverBuilder =
26+
JavaHttpServerTelemetry.builder(GlobalOpenTelemetry.get());
27+
JavaHttpServerInstrumenterBuilderUtil.getServerBuilderExtractor()
28+
.apply(serverBuilder)
29+
.configure(config);
30+
JavaHttpServerTelemetry serverTelemetry = serverBuilder.build();
31+
32+
FILTERS = Arrays.asList(serverTelemetry.newFilter(), new ResponseCustomizingFilter());
33+
}
34+
35+
private JavaHttpServerSingletons() {}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.javahttpserver;
7+
8+
import com.sun.net.httpserver.Filter;
9+
import com.sun.net.httpserver.HttpExchange;
10+
import io.opentelemetry.context.Context;
11+
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder;
12+
import java.io.IOException;
13+
14+
final class ResponseCustomizingFilter extends Filter {
15+
16+
ResponseCustomizingFilter() {}
17+
18+
@Override
19+
public void doFilter(HttpExchange exchange, Chain chain) throws IOException {
20+
Context context = Context.current();
21+
HttpServerResponseCustomizerHolder.getCustomizer()
22+
.customize(context, exchange.getResponseHeaders(), JavaHttpServerResponseMutator.INSTANCE);
23+
chain.doFilter(exchange);
24+
}
25+
26+
@Override
27+
public String description() {
28+
return "OpenTelemetry response customizing filter";
29+
}
30+
}
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.javahttpserver;
7+
8+
import io.opentelemetry.instrumentation.javahttpserver.AbstractJavaHttpServerTest;
9+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
10+
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension;
11+
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions;
12+
import org.junit.jupiter.api.extension.RegisterExtension;
13+
14+
class JavaHttpServerTest extends AbstractJavaHttpServerTest {
15+
16+
@RegisterExtension
17+
static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent();
18+
19+
@Override
20+
protected void configure(HttpServerTestOptions options) {
21+
super.configure(options);
22+
23+
options.setHasResponseCustomizer(serverEndpoint -> true);
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Library Instrumentation for Java HTTP Server
2+
3+
Provides OpenTelemetry instrumentation for [Java HTTP Server](https://docs.oracle.com/en/java/javase/21/docs/api/jdk.httpserver/module-summary.html).
4+
5+
## Quickstart
6+
7+
### Add these dependencies to your project
8+
9+
Replace `OPENTELEMETRY_VERSION` with the [latest
10+
release](https://search.maven.org/search?q=g:io.opentelemetry.instrumentation%20AND%20a:opentelemetry-java-http-server).
11+
12+
For Maven, add to your `pom.xml` dependencies:
13+
14+
```xml
15+
<dependencies>
16+
<dependency>
17+
<groupId>io.opentelemetry.instrumentation</groupId>
18+
<artifactId>opentelemetry-java-http-server</artifactId>
19+
<version>OPENTELEMETRY_VERSION</version>
20+
</dependency>
21+
</dependencies>
22+
```
23+
24+
For Gradle, add to your dependencies:
25+
26+
```groovy
27+
implementation("io.opentelemetry.instrumentation:opentelemetry-java-http-server:OPENTELEMETRY_VERSION")
28+
```
29+
30+
### Usage
31+
32+
The instrumentation library contains a `Filter` wrapper that provides OpenTelemetry-based spans
33+
and context propagation.
34+
35+
```java
36+
37+
import java.io.IOException;
38+
import java.net.InetSocketAddress;
39+
40+
import com.sun.net.httpserver.HttpContext;
41+
import com.sun.net.httpserver.HttpServer;
42+
43+
import io.opentelemetry.api.OpenTelemetry;
44+
import io.opentelemetry.sdk.OpenTelemetrySdk;
45+
46+
public class Application {
47+
48+
static void main(String args) throws IOException {
49+
50+
final HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
51+
final HttpContext context =
52+
server.createContext(
53+
"/",
54+
ctx -> {
55+
// http logic
56+
});
57+
58+
OpenTelemetry openTelemetry = //...
59+
60+
JavaHttpServerTelemetry.create(openTelemetry).configure(context);
61+
}
62+
}
63+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
plugins {
2+
id("otel.library-instrumentation")
3+
id("otel.nullaway-conventions")
4+
}
5+
6+
dependencies {
7+
testImplementation(project(":instrumentation:java-http-server:testing"))
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.javahttpserver;
7+
8+
import com.sun.net.httpserver.HttpExchange;
9+
import com.sun.net.httpserver.HttpsExchange;
10+
import io.opentelemetry.instrumentation.api.internal.HttpProtocolUtil;
11+
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesGetter;
12+
import java.net.InetSocketAddress;
13+
import java.util.Collections;
14+
import java.util.List;
15+
import javax.annotation.Nullable;
16+
17+
enum JavaHttpServerAttributesGetter
18+
implements HttpServerAttributesGetter<HttpExchange, HttpExchange> {
19+
INSTANCE;
20+
21+
@Override
22+
public String getHttpRequestMethod(HttpExchange exchange) {
23+
return exchange.getRequestMethod();
24+
}
25+
26+
@Override
27+
public String getUrlScheme(HttpExchange exchange) {
28+
return exchange instanceof HttpsExchange ? "https" : "http";
29+
}
30+
31+
@Override
32+
public String getUrlPath(HttpExchange exchange) {
33+
return exchange.getRequestURI().getPath();
34+
}
35+
36+
@Nullable
37+
@Override
38+
public String getUrlQuery(HttpExchange exchange) {
39+
return exchange.getRequestURI().getQuery();
40+
}
41+
42+
@Override
43+
public List<String> getHttpRequestHeader(HttpExchange exchange, String name) {
44+
return exchange.getRequestHeaders().getOrDefault(name, Collections.emptyList());
45+
}
46+
47+
@Nullable
48+
@Override
49+
public Integer getHttpResponseStatusCode(
50+
HttpExchange exchange, @Nullable HttpExchange res, @Nullable Throwable error) {
51+
int status = exchange.getResponseCode();
52+
return status != -1 ? status : null;
53+
}
54+
55+
@Override
56+
public List<String> getHttpResponseHeader(
57+
HttpExchange exchange, @Nullable HttpExchange res, String name) {
58+
return exchange.getResponseHeaders().getOrDefault(name, Collections.emptyList());
59+
}
60+
61+
@Override
62+
public String getHttpRoute(HttpExchange exchange) {
63+
return exchange.getHttpContext().getPath();
64+
}
65+
66+
@Override
67+
public String getNetworkProtocolName(HttpExchange exchange, @Nullable HttpExchange res) {
68+
return HttpProtocolUtil.getProtocol(exchange.getProtocol());
69+
}
70+
71+
@Override
72+
public String getNetworkProtocolVersion(HttpExchange exchange, @Nullable HttpExchange res) {
73+
return HttpProtocolUtil.getVersion(exchange.getProtocol());
74+
}
75+
76+
@Override
77+
public InetSocketAddress getNetworkPeerInetSocketAddress(
78+
HttpExchange exchange, @Nullable HttpExchange res) {
79+
return exchange.getRemoteAddress();
80+
}
81+
82+
@Override
83+
public InetSocketAddress getNetworkLocalInetSocketAddress(
84+
HttpExchange exchange, @Nullable HttpExchange res) {
85+
return exchange.getLocalAddress();
86+
}
87+
}

0 commit comments

Comments
 (0)