Skip to content

Add Helidon Instrumentation #13776

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

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -457,6 +457,12 @@ targets:
- type: gradle
path: ./
target: ':instrumentation:graphql-java:graphql-java-common:library'
- type: gradle
path: ./
target: ':instrumentation:helidon:helidon-4.3.0:javaagent'
- type: gradle
path: ./
target: ':instrumentation:helidon:helidon-4.3.0:library'
- type: gradle
path: ./
target: ':instrumentation:hibernate:hibernate-3.3: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 @@ -74,6 +74,7 @@ These are the supported libraries and frameworks:
| [gRPC](https://github.com/grpc/grpc-java) | 1.6+ | [opentelemetry-grpc-1.6](../instrumentation/grpc-1.6/library) | [RPC Client Spans], [RPC Client Metrics], [RPC Server Spans], [RPC Server Metrics] |
| [Guava ListenableFuture](https://guava.dev/releases/snapshot/api/docs/com/google/common/util/concurrent/ListenableFuture.html) | 10.0+ | [opentelemetry-guava-10.0](../instrumentation/guava-10.0/library) | Context propagation |
| [GWT](http://www.gwtproject.org/) | 2.0+ | N/A | [RPC Server Spans] |
| [Helidon](https://helidon.io/)| Java 21+ | [opentelemetry-helidon](../instrumentation/helidon-4.3.0/library) | [HTTP Server Spans], [HTTP Server Metrics] |
| [Hibernate](https://github.com/hibernate/hibernate-orm) | 3.3+ | N/A | none |
| [Hibernate Reactive](https://hibernate.org/reactive) | 1.0+ | N/A | none |
| [HikariCP](https://github.com/brettwooldridge/HikariCP) | 3.0+ | [opentelemetry-hikaricp-3.0](../instrumentation/hikaricp-3.0/library) | [Database Pool Metrics] |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("io.helidon.webserver")
module.set("helidon-webserver")
versions.set("[4.3.0,)")
assertInverse.set(true)
}
}

otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_21)
}

dependencies {
library("io.helidon.webserver:helidon-webserver:4.3.0")
implementation(project(":instrumentation:helidon:helidon-4.3.0:library"))
testImplementation(project(":instrumentation:helidon:helidon-4.3.0: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.helidon;

import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
import static net.bytebuddy.matcher.ElementMatchers.named;

import io.helidon.webserver.http.HttpRouting;
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 HelidonInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("io.helidon.webserver.http.HttpRouting");
}

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

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

@Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(@Advice.Return HttpRouting.Builder httpContext) {
HelidonSingletons.FILTERS.forEach(httpContext::addFilter);
}
}
}
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.helidon;

import static java.util.Collections.singletonList;

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

@AutoService(InstrumentationModule.class)
public class HelidonInstrumentationModule extends InstrumentationModule {
public HelidonInstrumentationModule() {
super("helidon", "helidon-4.3.0");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return singletonList(new HelidonInstrumentation());
}
}
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.helidon;

import io.helidon.webserver.http.ServerResponse;
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseMutator;

enum HelidonServerResponseMutator implements HttpServerResponseMutator<ServerResponse> {
INSTANCE;

@Override
public void appendHeader(ServerResponse res, String name, String value) {
res.header(name, value);
}
}
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.helidon;

import io.helidon.webserver.http.Filter;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.helidon.HelidonTelemetry;
import io.opentelemetry.instrumentation.helidon.internal.HelidonInstrumenterBuilderUtil;
import io.opentelemetry.javaagent.bootstrap.internal.AgentCommonConfig;
import java.util.List;

public final class HelidonSingletons {

public static final List<Filter> FILTERS;

static {
var config = AgentCommonConfig.get();

var serverBuilder = HelidonTelemetry.builder(GlobalOpenTelemetry.get());
HelidonInstrumenterBuilderUtil.getServerBuilderExtractor()
.apply(serverBuilder)
.configure(config);
var serverTelemetry = serverBuilder.build();

FILTERS = List.of(serverTelemetry.createFilter(), new ResponseCustomizingFilter());
}

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

package io.opentelemetry.javaagent.instrumentation.helidon;

import io.helidon.webserver.http.Filter;
import io.helidon.webserver.http.FilterChain;
import io.helidon.webserver.http.RoutingRequest;
import io.helidon.webserver.http.RoutingResponse;
import io.opentelemetry.context.Context;
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder;

final class ResponseCustomizingFilter implements Filter {

ResponseCustomizingFilter() {}

@Override
public void filter(FilterChain chain, RoutingRequest req, RoutingResponse res) {

var context = Context.current();
HttpServerResponseCustomizerHolder.getCustomizer()
.customize(context, res, HelidonServerResponseMutator.INSTANCE);
chain.proceed();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.helidon;

import io.opentelemetry.instrumentation.helidon.AbstractHelidonTest;
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 HelidonServerTest extends AbstractHelidonTest {

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

@Override
protected void configure(HttpServerTestOptions options) {
super.configure(options);
options.setHasResponseCustomizer(serverEndpoint -> true);

options.setTestNotFound(false);
}
}
55 changes: 55 additions & 0 deletions instrumentation/helidon/helidon-4.3.0/library/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Library Instrumentation for Helidon

Provides OpenTelemetry instrumentation for [Helidon](https://helidon.io/).

## 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-helidon-4.3.0).

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

```xml
<dependencies>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-helidon-4.3.0</artifactId>
<version>OPENTELEMETRY_VERSION</version>
</dependency>
</dependencies>
```

For Gradle, add to your dependencies:

```groovy
implementation("io.opentelemetry.instrumentation:opentelemetry-helidon-4.3.0:OPENTELEMETRY_VERSION")
```

### Usage

The instrumentation library contains an `HttpFeature` that provides OpenTelemetry-based spans
and context propagation.

```java
import java.io.IOException;

import io.helidon.webserver.WebServer;
import io.helidon.webserver.http.HttpRouting;
import io.opentelemetry.api.OpenTelemetry;

public class Application {

static void main(String args) throws IOException {

OpenTelemetry openTelemetry = // ...
WebServer.builder()
.addRouting(
HttpRouting.builder()
.addFeature(HelidonTelemetry.create(openTelemetry))
.get("/greet", (req, res) -> res.send("Hello World!")))
.build();
}
}
```
12 changes: 12 additions & 0 deletions instrumentation/helidon/helidon-4.3.0/library/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
plugins {
id("otel.library-instrumentation")
}

otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_21)
}

dependencies {
implementation("io.helidon.webserver:helidon-webserver:4.3.0")
testImplementation(project(":instrumentation:helidon:helidon-4.3.0:testing"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.helidon;

import java.net.InetSocketAddress;
import java.util.List;
import java.util.Objects;

import javax.annotation.Nullable;

import io.helidon.http.HeaderNames;
import io.helidon.webserver.http.ServerRequest;
import io.helidon.webserver.http.ServerResponse;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesGetter;

enum HelidonAttributesGetter implements HttpServerAttributesGetter<ServerRequest, ServerResponse> {
INSTANCE;

@Override
public String getHttpRequestMethod(ServerRequest req) {
return req.prologue().method().text();
}

@Override
public String getUrlScheme(ServerRequest req) {
return req.requestedUri().scheme();
}

@Override
public String getUrlPath(ServerRequest req) {
return req.path().rawPath();
}

@Nullable
@Override
public String getUrlQuery(ServerRequest req) {
return req.query().rawValue();
}

@Override
public List<String> getHttpRequestHeader(ServerRequest req, String name) {
return req.headers().values(HeaderNames.create(name));
}

@Nullable
@Override
public Integer getHttpResponseStatusCode(
ServerRequest req, @Nullable ServerResponse res, @Nullable Throwable error) {

return Objects.requireNonNull(res).status().code();
}

@Override
public List<String> getHttpResponseHeader(
ServerRequest req, @Nullable ServerResponse res, String name) {
return Objects.requireNonNull(res).headers().values(HeaderNames.create(name));
}

@Nullable
@Override
public String getHttpRoute(ServerRequest req) {
return null;
}

@Override
public String getNetworkProtocolName(ServerRequest req, @Nullable ServerResponse res) {
return req.prologue().protocol();
}

@Override
public String getNetworkProtocolVersion(ServerRequest req, @Nullable ServerResponse res) {
return req.prologue().protocolVersion();
}

@Override
public InetSocketAddress getNetworkPeerInetSocketAddress(
ServerRequest req, @Nullable ServerResponse res) {
var address = req.remotePeer().address();
return address instanceof InetSocketAddress s ? s : null;
}

@Override
public InetSocketAddress getNetworkLocalInetSocketAddress(
ServerRequest req, @Nullable ServerResponse res) {
var address = req.localPeer().address();
return address instanceof InetSocketAddress s ? s : null;
}
}
Loading
Loading