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

feat: Add OpenTelemetry instrumentation for ActiveJ HTTP server #13335

Merged
merged 57 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
b52c49d
Introduces OpenTelemetry instrumentation for the ActiveJ framework, e…
kcsurapaneni Feb 18, 2025
1cc6c5f
Resolved PR comments
kcsurapaneni Feb 18, 2025
b4e8e29
instrumentation name is updated with version
kcsurapaneni Feb 18, 2025
ce368c1
instrumentation name is updated with version
kcsurapaneni Feb 18, 2025
b287f78
instrumentation name is updated with version
kcsurapaneni Feb 18, 2025
e1a29dc
checkstyle config is reverted and added @SuppressWarnings(Abbreviatio…
kcsurapaneni Feb 18, 2025
583e063
renames class files to avoid AbbreviationAsWordInName checkstyle rule
kcsurapaneni Feb 18, 2025
b7aff9d
Test cases are added
kcsurapaneni Feb 19, 2025
b241b45
build script dependencies is updated
kcsurapaneni Feb 19, 2025
1662b23
throwable is now read-only parameter
kcsurapaneni Feb 19, 2025
4fd797f
base version is set to Java 17
kcsurapaneni Feb 19, 2025
7bf1776
PR comments addressed
kcsurapaneni Feb 20, 2025
a07317e
PR comments addressed
kcsurapaneni Feb 20, 2025
d5bb920
spotlessCheck applied
kcsurapaneni Feb 20, 2025
429ad65
PR comments addressed
kcsurapaneni Feb 20, 2025
ac95594
explicitly added test dependencies
kcsurapaneni Feb 20, 2025
5a4d2dd
Resolving common/check-latest-dep-test-overrides issue
kcsurapaneni Feb 20, 2025
c142d7f
fix double instrumentation (#13337)
zeitlinger Feb 18, 2025
c098084
Add comment to workflow file (#13343)
trask Feb 18, 2025
c0b67a3
Better qualify Java HttpClient instrumentation package name (#13296)
trask Feb 18, 2025
4c149bd
Update apidiff baseline to released version 2.13.1 (#13348)
otelbot[bot] Feb 18, 2025
fea36de
Merge change log updates from release/v2.13.x (#13347)
otelbot[bot] Feb 18, 2025
2c31d8f
fix(deps): update gradle develocity packages to v3.19.2 (patch) (#13349)
renovate[bot] Feb 18, 2025
583801e
Fix automated PR body text (#13350)
trask Feb 18, 2025
f6ef942
fix(deps): update dependency io.opentelemetry.semconv:opentelemetry-s…
renovate[bot] Feb 19, 2025
bb75dab
fix(deps): update testcontainers-java monorepo to v1.20.5 (patch) (#1…
renovate[bot] Feb 19, 2025
aba23a3
fix(deps): update dependency com.google.apis:google-api-services-shee…
renovate[bot] Feb 20, 2025
5a92938
Fix flaky test (#13358)
laurit Feb 20, 2025
1420a18
Fix testLatestDeps (#13364)
trask Feb 20, 2025
20d00a0
Fix TODO (#13363)
trask Feb 20, 2025
f1cfe0e
integration test case has been added which extends AbstractHttpServer…
kcsurapaneni Feb 21, 2025
0748348
fix(deps): update dependency org.springframework.boot:spring-boot-sta…
renovate[bot] Feb 21, 2025
6ec9373
fix(deps): update dependency org.testcontainers:testcontainers to v1.…
renovate[bot] Feb 21, 2025
37032d2
Merge branch 'main' into activej
laurit Feb 21, 2025
65e6d57
Test cases are updated to use JUnit 5
kcsurapaneni Feb 21, 2025
ad7692a
fix(deps): update dependency org.awaitility:awaitility to v4.3.0 (#13…
renovate[bot] Feb 21, 2025
6f01a1c
Promise response method exit handling logic updated
kcsurapaneni Feb 21, 2025
dc94afa
addressed PR comments
kcsurapaneni Feb 26, 2025
2332b12
fix(deps): update opentelemetry-java-contrib monorepo to v1.44.0-alph…
renovate[bot] Feb 22, 2025
fe1151c
[Spring Scheduling] Support Virtual Threads (#13370)
jakobjoachim Feb 22, 2025
18b3cde
chore(deps): update weekly update (#13381)
renovate[bot] Feb 24, 2025
dfb73d1
fix(deps): update dependency checkstyle to v10.21.3 (#13380)
renovate[bot] Feb 24, 2025
f167d99
fix(deps): update junit5 monorepo to v5.12.0 (minor) (#13372)
renovate[bot] Feb 24, 2025
bf3fe1f
fix(deps): update dependency com.google.auth:google-auth-library-oaut…
renovate[bot] Feb 25, 2025
e802708
fix semconv naming for 'jvm.buffer.memory.used' metric (#13374)
SylvainJuge Feb 25, 2025
f6ee0dc
fix(deps): update dependency com.google.auth:google-auth-library-oaut…
renovate[bot] Feb 25, 2025
036ae0b
chore(deps): update dependency gradle to v8.13 (#13394)
renovate[bot] Feb 25, 2025
148a91a
Ensure tilde$1 onExit is run in correct order (#13360)
masonedmison Feb 26, 2025
2f40b7f
Add instrumentation of AWS Bedrock to use gen_ai conventions (#13355)
anuraaga Feb 26, 2025
483ec98
fix(deps): update dependency ch.qos.logback:logback-classic to v1.5.1…
renovate[bot] Feb 26, 2025
ef5ceec
Merge remote-tracking branch 'origin' into activej
kcsurapaneni Feb 26, 2025
418a259
@laurit PR comments are addressed
kcsurapaneni Feb 26, 2025
81827bc
@laurit PR comments are addressed
kcsurapaneni Feb 26, 2025
87345f0
polish
laurit Feb 26, 2025
9023ad8
reformat table
laurit Feb 26, 2025
99a4c21
only consider 6.0+ classes
kcsurapaneni Feb 26, 2025
7269a0e
supports minimum 6.0 version
kcsurapaneni Feb 26, 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
3 changes: 3 additions & 0 deletions .fossa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ targets:
- type: gradle
path: ./
target: ':testing:agent-for-testing'
- type: gradle
path: ./
target: ':instrumentation:activej-http-6.0:javaagent'
- type: gradle
path: ./
target: ':instrumentation:alibaba-druid-1.0:javaagent'
Expand Down
2 changes: 1 addition & 1 deletion dependencyManagement/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ val DEPENDENCIES = listOf(
"io.opentelemetry.proto:opentelemetry-proto:1.5.0-alpha",
"io.opentelemetry:opentelemetry-extension-annotations:1.18.0", // deprecated, no longer part of bom
"org.assertj:assertj-core:3.27.3",
"org.awaitility:awaitility:4.2.2",
"org.awaitility:awaitility:4.3.0",
"com.google.code.findbugs:annotations:3.0.1u2",
"com.google.code.findbugs:jsr305:3.0.2",
"org.apache.groovy:groovy:${groovyVersion}",
Expand Down
1 change: 1 addition & 0 deletions docs/supported-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ These are the supported libraries and frameworks:

| Library/Framework | Auto-instrumented versions | Standalone Library Instrumentation [1] | Semantic Conventions |
|---------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
| [ActiveJ](https://activej.io/) | 6.0+ | N/A | [HTTP Server Spans] |
| [Akka Actors](https://doc.akka.io/docs/akka/current/typed/index.html) | 2.3+ | N/A | Context propagation |
| [Akka HTTP](https://doc.akka.io/docs/akka-http/current/index.html) | 10.0+ | N/A | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics], Provides `http.route` [2] |
| [Alibaba Druid](https://github.com/alibaba/druid) | 1.0+ | [opentelemetry-alibaba-druid-1.0](../instrumentation/alibaba-druid-1.0/library) | [Database Pool Metrics] |
Expand Down
21 changes: 21 additions & 0 deletions instrumentation/activej-http-6.0/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("io.activej:activej-http")
module.set("activej-http")
versions.set("[6.0,)")
assertInverse.set(true)
}
}

dependencies {
library("io.activej:activej-http:6.0-rc2")
latestDepTestLibrary("io.activej:activej-http:6.0-rc2") // documented limitation
}

otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_17)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.activejhttp;

import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType;
import static io.opentelemetry.javaagent.instrumentation.activejhttp.ActivejHttpServerConnectionSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import io.activej.http.AsyncServlet;
import io.activej.http.HttpRequest;
import io.activej.http.HttpResponse;
import io.activej.promise.Promise;
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 net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

public class ActivejHttpServerConnectionInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return hasSuperType(named("io.activej.http.AsyncServlet")).and(not(isInterface()));
}

@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
return hasClassesNamed("io.activej.http.AsyncServlet");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod()
.and(named("serve"))
.and(takesArguments(1).and(takesArgument(0, named("io.activej.http.HttpRequest")))),
this.getClass().getName() + "$ServeAdvice");
}

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

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void methodEnter(
@Advice.This AsyncServlet asyncServlet,
@Advice.Argument(0) HttpRequest request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope,
@Advice.Local("httpRequest") HttpRequest httpRequest) {
Context parentContext = currentContext();
httpRequest = request;
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 AsyncServlet asyncServlet,
@Advice.Return(readOnly = false) Promise<HttpResponse> responsePromise,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope,
@Advice.Local("httpRequest") HttpRequest httpRequest) {
if (scope == null) {
return;
}
scope.close();
if (throwable != null) {
instrumenter().end(context, httpRequest, null, throwable);
} else {
responsePromise = PromiseWrapper.wrap(responsePromise, httpRequest, context);
}
}
}
}
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.activejhttp;

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 ActivejHttpServerConnectionInstrumentationModule extends InstrumentationModule {

public ActivejHttpServerConnectionInstrumentationModule() {
super("activej-http", "activej-http-6.0");
}

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

import io.activej.http.HttpRequest;
import io.activej.http.HttpResponse;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.javaagent.bootstrap.internal.JavaagentHttpServerInstrumenters;

public final class ActivejHttpServerConnectionSingletons {

private static final String INSTRUMENTATION_NAME = "io.opentelemetry.activej-http-6.0";

private static final Instrumenter<HttpRequest, HttpResponse> INSTRUMENTER;

static {
INSTRUMENTER =
JavaagentHttpServerInstrumenters.create(
INSTRUMENTATION_NAME,
new ActivejHttpServerHttpAttributesGetter(),
ActivejHttpServerHeaders.INSTANCE);
}

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

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

package io.opentelemetry.javaagent.instrumentation.activejhttp;

import io.activej.http.HttpHeader;
import io.activej.http.HttpHeaderValue;
import io.activej.http.HttpHeaders;
import io.activej.http.HttpRequest;
import io.opentelemetry.context.propagation.internal.ExtendedTextMapGetter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

enum ActivejHttpServerHeaders implements ExtendedTextMapGetter<HttpRequest> {
INSTANCE;

@Override
public Iterable<String> keys(HttpRequest httpRequest) {
return httpRequest.getHeaders().stream().map(h -> h.getKey().toString()).toList();
}

@Override
public String get(HttpRequest carrier, String key) {
if (carrier == null) {
return null;
}
return carrier.getHeader(HttpHeaders.of(key));
}

@Override
public Iterator<String> getAll(HttpRequest carrier, String key) {
List<String> values = new ArrayList<>();
if (carrier != null) {
for (Map.Entry<HttpHeader, HttpHeaderValue> entry : carrier.getHeaders()) {
if (entry.getKey().toString().equalsIgnoreCase(key)) {
values.add(entry.getValue().toString());
}
}
}
return values.iterator();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.activejhttp;

import static io.opentelemetry.testing.internal.armeria.internal.shaded.guava.base.Ascii.toLowerCase;

import io.activej.http.HttpRequest;
import io.activej.http.HttpResponse;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesGetter;
import java.util.List;
import javax.annotation.Nullable;

final class ActivejHttpServerHttpAttributesGetter
implements HttpServerAttributesGetter<HttpRequest, HttpResponse> {

@Override
public String getHttpRequestMethod(HttpRequest request) {
return ActivejHttpServerUtil.getHttpRequestMethod(request);
}

@Override
public List<String> getHttpRequestHeader(HttpRequest request, String name) {
return ActivejHttpServerUtil.requestHeader(request, name);
}

@Override
public Integer getHttpResponseStatusCode(
HttpRequest request, HttpResponse httpResponse, @Nullable Throwable error) {
return ActivejHttpServerUtil.getHttpResponseStatusCode(request, httpResponse, error);
}

@Override
public List<String> getHttpResponseHeader(
HttpRequest request, HttpResponse httpResponse, String name) {
return ActivejHttpServerUtil.getHttpResponseHeader(request, httpResponse, name);
}

@Override
public String getUrlScheme(HttpRequest request) {
return toLowerCase(request.getProtocol().name());
}

@Override
public String getUrlPath(HttpRequest request) {
return request.getPath();
}

@Override
public String getUrlQuery(HttpRequest request) {
return request.getQuery();
}

@Override
public String getNetworkProtocolName(HttpRequest request, @Nullable HttpResponse httpResponse) {
return ActivejHttpServerUtil.getNetworkProtocolName(request);
}

@Override
public String getNetworkProtocolVersion(
HttpRequest request, @Nullable HttpResponse httpResponse) {
return ActivejHttpServerUtil.getNetworkProtocolVersion(request.getVersion());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.activejhttp;

import static io.activej.http.HttpError.internalServerError500;
import static java.util.Collections.emptyList;

import io.activej.http.HttpHeaders;
import io.activej.http.HttpRequest;
import io.activej.http.HttpResponse;
import io.activej.http.HttpVersion;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Nullable;

public final class ActivejHttpServerUtil {

private ActivejHttpServerUtil() {
throw new UnsupportedOperationException();
}

static String getHttpRequestMethod(HttpRequest request) {
return request.getMethod().name();
}

static List<String> requestHeader(HttpRequest request, String name) {
String headerValue = request.getHeader(HttpHeaders.of(name));
if (headerValue == null) {
return emptyList();
}
return Arrays.stream(headerValue.split(",")).collect(Collectors.toList());
}

static Integer getHttpResponseStatusCode(
HttpRequest request, HttpResponse httpResponse, @Nullable Throwable error) {
if (error != null && httpResponse.getCode() <= 0) {
return internalServerError500().getCode();
}
return httpResponse.getCode();
}

static List<String> getHttpResponseHeader(
HttpRequest request, HttpResponse httpResponse, String name) {
String headerValue = httpResponse.getHeader(HttpHeaders.of(name));
if (headerValue == null) {
return emptyList();
}
return Arrays.stream(headerValue.split(",")).toList();
}

static String getNetworkProtocolVersion(HttpVersion version) {
switch (version) {
case HTTP_0_9:
return "0.9";
case HTTP_1_0:
return "1.0";
case HTTP_1_1:
return "1.1";
case HTTP_2_0:
return "2.0";
}
return "unknown";
}

static String getNetworkProtocolName(HttpRequest request) {
if (request.getVersion() == HttpVersion.HTTP_0_9
|| request.getVersion() == HttpVersion.HTTP_1_0
|| request.getVersion() == HttpVersion.HTTP_1_1
|| request.getVersion() == HttpVersion.HTTP_2_0) {
return "http";
}
return null;
}
}
Loading
Loading