Skip to content

Commit dfa49e2

Browse files
committed
Introduces OpenTelemetry instrumentation for the ActiveJ framework, enabling distributed tracing and context propagation for ActiveJ-based HTTP servers
1 parent d52a3b5 commit dfa49e2

12 files changed

+613
-1
lines changed

.fossa.yml

+3
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ targets:
4949
- type: gradle
5050
path: ./
5151
target: ':testing:agent-for-testing'
52+
- type: gradle
53+
path: ./
54+
target: ':instrumentation:activej-http:javaagent'
5255
- type: gradle
5356
path: ./
5457
target: ':instrumentation:alibaba-druid-1.0:javaagent'

buildscripts/checkstyle.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@
278278
-->
279279
<module name="AbbreviationAsWordInName">
280280
<property name="ignoreFinal" value="false"/>
281-
<property name="allowedAbbreviationLength" value="0"/>
281+
<property name="allowedAbbreviationLength" value="2"/>
282282
<property name="tokens"
283283
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, ANNOTATION_DEF, ANNOTATION_FIELD_DEF,
284284
PARAMETER_DEF, VARIABLE_DEF, METHOD_DEF, PATTERN_VARIABLE_DEF, RECORD_DEF,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# OpenTelemetry Java Agent for ActiveJ Framework
2+
3+
This repository provides an OpenTelemetry Java agent specifically designed to instrument applications built on the [ActiveJ framework](https://activej.io/). The agent enables distributed tracing, context propagation, and telemetry data collection for ActiveJ-based HTTP servers, making it easier to monitor and debug applications in distributed systems.
4+
5+
## Table of Contents
6+
7+
1. [Overview](#overview)
8+
2. [Features](#features)
9+
3. [Prerequisites](#prerequisites)
10+
4. [Installation & Usage](#installation)
11+
12+
---
13+
14+
## Overview
15+
16+
The OpenTelemetry Java agent for ActiveJ integrates with the OpenTelemetry API to provide automatic instrumentation for ActiveJ HTTP servers. It captures trace context from incoming HTTP requests, propagates it through responses, and enriches telemetry data with HTTP-specific attributes such as request methods, headers, status codes, and URL components.
17+
18+
This agent is particularly useful for applications that rely on ActiveJ's high-performance, event-driven architecture and need observability in distributed systems.
19+
20+
---
21+
22+
## Features
23+
24+
- **Distributed Tracing**: Automatically propagates trace context across service boundaries using the `traceparent` header.
25+
- **HTTP Attribute Extraction**: Captures detailed HTTP attributes (e.g., method, path, query, headers) for enriched telemetry data.
26+
- **Error Handling**: Handles exceptions and maps them to appropriate HTTP status codes for better error visibility.
27+
- **Compatibility**: Works seamlessly with OpenTelemetry's Java instrumentation framework and exporters (e.g., Jaeger, Zipkin, HyperDX).
28+
29+
---
30+
31+
## Prerequisites
32+
33+
Before using this agent, ensure you have the following:
34+
35+
- Java 8 or higher
36+
- ActiveJ framework
37+
- An OpenTelemetry collector or backend (e.g., Jaeger, Zipkin, HyperDX) for visualizing traces
38+
39+
---
40+
41+
## Installation
42+
43+
### Using the Java Agent JAR
44+
45+
1. Download the latest release of the OpenTelemetry Java agent JAR file.
46+
2. Add the agent to your application's JVM arguments:
47+
48+
```bash
49+
java -javaagent:/path/to/opentelemetry-java-agent.jar -jar your-application.jar
50+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
group.set("io.activej:activej-http")
8+
module.set("activej-http")
9+
10+
versions.set("[1.0,)")
11+
}
12+
}
13+
14+
dependencies {
15+
library("io.activej:activej-http:6.0-beta2")
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.activejhttp;
7+
8+
import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
9+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType;
10+
import static io.opentelemetry.javaagent.instrumentation.activejhttp.ActiveJHttpServerConnectionSingletons.instrumenter;
11+
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
12+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
13+
import static net.bytebuddy.matcher.ElementMatchers.named;
14+
import static net.bytebuddy.matcher.ElementMatchers.not;
15+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
16+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
17+
18+
import io.activej.http.AsyncServlet;
19+
import io.activej.http.HttpHeaders;
20+
import io.activej.http.HttpRequest;
21+
import io.activej.http.HttpResponse;
22+
import io.activej.promise.Promise;
23+
import io.opentelemetry.api.trace.Span;
24+
import io.opentelemetry.context.Context;
25+
import io.opentelemetry.context.Scope;
26+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
27+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
28+
import net.bytebuddy.asm.Advice;
29+
import net.bytebuddy.description.type.TypeDescription;
30+
import net.bytebuddy.matcher.ElementMatcher;
31+
32+
/**
33+
* This class provides instrumentation for ActiveJ HTTP server connections by applying advice to the
34+
* {@code serve} method of classes that extend {@code io.activej.http.AsyncServlet}. The
35+
* instrumentation is designed to integrate with OpenTelemetry for distributed tracing, capturing
36+
* and propagating trace context through HTTP requests and responses.
37+
*
38+
* @author Krishna Chaitanya Surapaneni
39+
*/
40+
public class ActiveJHttpServerConnectionInstrumentation implements TypeInstrumentation {
41+
42+
/**
43+
* Matches classes that extend {@code io.activej.http.AsyncServlet} but are not interfaces.
44+
*
45+
* @return An {@code ElementMatcher} that identifies target classes for instrumentation.
46+
*/
47+
@Override
48+
public ElementMatcher<TypeDescription> typeMatcher() {
49+
return hasSuperType(named("io.activej.http.AsyncServlet")).and(not(isInterface()));
50+
}
51+
52+
/**
53+
* Applies advice to the {@code serve} method of the matched classes. The advice captures trace
54+
* context at the start of the method and propagates it through the response.
55+
*
56+
* @param transformer The {@code TypeTransformer} used to apply the advice.
57+
*/
58+
@Override
59+
public void transform(TypeTransformer transformer) {
60+
transformer.applyAdviceToMethod(
61+
isMethod()
62+
.and(named("serve"))
63+
.and(takesArguments(1).and(takesArgument(0, named("io.activej.http.HttpRequest")))),
64+
this.getClass().getName() + "$ServeAdvice");
65+
}
66+
67+
/**
68+
* Inner class containing the advice logic for the {@code serve} method. This class defines two
69+
* methods:
70+
*
71+
* <ul>
72+
* <li>{@code methodEnter}: Captures the trace context at the start of the method.
73+
* <li>{@code methodExit}: Propagates the trace context to the response and ends the span.
74+
* </ul>
75+
*/
76+
@SuppressWarnings("unused")
77+
public static class ServeAdvice {
78+
79+
/**
80+
* Advice executed at the start of the {@code serve} method. Captures the current trace context
81+
* and starts a new span if tracing is enabled for the request.
82+
*
83+
* @param asyncServlet The {@code AsyncServlet} instance handling the request.
84+
* @param request The incoming HTTP request.
85+
* @param context Local variable to store the OpenTelemetry context.
86+
* @param scope Local variable to store the OpenTelemetry scope.
87+
* @param httpRequest Local variable to store the HTTP request.
88+
*/
89+
@Advice.OnMethodEnter(suppress = Throwable.class)
90+
public static void methodEnter(
91+
@Advice.This AsyncServlet asyncServlet,
92+
@Advice.Argument(0) HttpRequest request,
93+
@Advice.Local("otelContext") Context context,
94+
@Advice.Local("otelScope") Scope scope,
95+
@Advice.Local("httpRequest") HttpRequest httpRequest) {
96+
Context parentContext = currentContext();
97+
httpRequest = request;
98+
if (!instrumenter().shouldStart(parentContext, request)) {
99+
return;
100+
}
101+
context = instrumenter().start(parentContext, request);
102+
scope = context.makeCurrent();
103+
}
104+
105+
/**
106+
* Advice executed at the end of the {@code serve} method. Propagates the trace context to the
107+
* response, handles exceptions, and ends the span.
108+
*
109+
* @param asyncServlet The {@code AsyncServlet} instance handling the request.
110+
* @param responsePromise The promise representing the HTTP response.
111+
* @param throwable Any exception thrown during the execution of the method.
112+
* @param context Local variable storing the OpenTelemetry context.
113+
* @param scope Local variable storing the OpenTelemetry scope.
114+
* @param httpRequest Local variable storing the HTTP request.
115+
*/
116+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
117+
public static void methodExit(
118+
@Advice.This AsyncServlet asyncServlet,
119+
@Advice.Return(readOnly = false) Promise<HttpResponse> responsePromise,
120+
@Advice.Thrown(readOnly = false) Throwable throwable,
121+
@Advice.Local("otelContext") Context context,
122+
@Advice.Local("otelScope") Scope scope,
123+
@Advice.Local("httpRequest") HttpRequest httpRequest) {
124+
if (context == null || scope == null || httpRequest == null) {
125+
return;
126+
}
127+
String traceId = Span.fromContext(context).getSpanContext().getTraceId();
128+
String spanId = Span.fromContext(context).getSpanContext().getSpanId();
129+
String traceFlags =
130+
Span.fromContext(context).getSpanContext().getTraceFlags().asHex().substring(0, 2);
131+
String traceparent = String.format("00-%s-%s-%s", traceId, spanId, traceFlags);
132+
133+
scope.close();
134+
String traceparentHeader = "traceparent";
135+
if (responsePromise != null) {
136+
HttpResponse httpResponse = responsePromise.getResult();
137+
Throwable error = throwable;
138+
if (responsePromise.isException()) {
139+
error = responsePromise.getException();
140+
}
141+
httpResponse = ActiveJHttpServerHelper.createResponse(error, traceparent, httpResponse);
142+
instrumenter().end(context, httpRequest, httpResponse, error);
143+
responsePromise = Promise.of(httpResponse);
144+
} else if (throwable != null) {
145+
HttpResponse httpResponse =
146+
HttpResponse.builder()
147+
.withCode(500)
148+
.withPlainText(throwable.getMessage())
149+
.withHeader(HttpHeaders.of(traceparentHeader), traceparent)
150+
.build();
151+
instrumenter().end(context, httpRequest, httpResponse, throwable);
152+
responsePromise = Promise.of(httpResponse);
153+
throwable = null;
154+
} else {
155+
HttpResponse httpResponse =
156+
HttpResponse.notFound404()
157+
.withHeader(HttpHeaders.of(traceparentHeader), traceparent)
158+
.build();
159+
instrumenter().end(context, httpRequest, httpResponse, throwable);
160+
responsePromise = Promise.of(httpResponse);
161+
}
162+
}
163+
}
164+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.activejhttp;
7+
8+
import static java.util.Collections.singletonList;
9+
10+
import com.google.auto.service.AutoService;
11+
import io.opentelemetry.javaagent.extension.instrumentation.HelperResourceBuilder;
12+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
13+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
14+
import java.util.List;
15+
16+
/**
17+
* This class is an instrumentation module for ActiveJ HTTP server connections. It integrates with
18+
* OpenTelemetry to provide distributed tracing capabilities for applications using the ActiveJ HTTP
19+
* server.
20+
*
21+
* <p>The module is annotated with {@code @AutoService(InstrumentationModule.class)}, which
22+
* automatically registers it as a service provider for the OpenTelemetry instrumentation framework.
23+
* This allows the module to be discovered and loaded dynamically during runtime.
24+
*
25+
* @author Krishna Chaitanya Surapaneni
26+
*/
27+
@AutoService(InstrumentationModule.class)
28+
public class ActiveJHttpServerConnectionInstrumentationModule extends InstrumentationModule {
29+
30+
/**
31+
* Constructs the instrumentation module with the specified instrumentation names. These names are
32+
* used to identify the instrumentation in the OpenTelemetry framework.
33+
*
34+
* <p>In this case, the module is identified by the names "activej-http" and
35+
* "activej-http-server".
36+
*/
37+
public ActiveJHttpServerConnectionInstrumentationModule() {
38+
super("activej-http", "activej-http-server");
39+
}
40+
41+
/**
42+
* Returns a list of type instrumentation's provided by this module. Each type instrumentation
43+
* applies advice to specific methods or classes to capture trace context and propagate it through
44+
* HTTP requests and responses.
45+
*
46+
* @return A list containing the {@code ActiveJHttpServerConnectionInstrumentation} instance.
47+
*/
48+
@Override
49+
public List<TypeInstrumentation> typeInstrumentations() {
50+
return singletonList(new ActiveJHttpServerConnectionInstrumentation());
51+
}
52+
53+
/**
54+
* Registers helper resources required for the instrumentation. Helper resources are typically
55+
* utility classes or configurations that support the instrumentation logic.
56+
*
57+
* <p>In this case, the {@code ActiveJHttpServerHelper} class is registered as a helper resource.
58+
* This class provides utilities for creating HTTP responses with trace context.
59+
*
60+
* @param helperResourceBuilder The builder used to register helper resources.
61+
*/
62+
@Override
63+
public void registerHelperResources(HelperResourceBuilder helperResourceBuilder) {
64+
helperResourceBuilder.register(ActiveJHttpServerHelper.class.getName());
65+
}
66+
}
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.activejhttp;
7+
8+
import io.activej.http.HttpRequest;
9+
import io.activej.http.HttpResponse;
10+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
11+
import io.opentelemetry.javaagent.bootstrap.internal.JavaagentHttpServerInstrumenters;
12+
13+
/**
14+
* This class provides singleton instances and utilities for ActiveJ HTTP server instrumentation. It
15+
* is designed to centralize the creation and access of OpenTelemetry-related components, such as
16+
* the {@code Instrumenter} used for tracing HTTP requests and responses.
17+
*
18+
* @author Krishna Chaitanya Surapaneni
19+
*/
20+
public class ActiveJHttpServerConnectionSingletons {
21+
22+
/**
23+
* The name of the instrumentation, used to identify this module in the OpenTelemetry framework.
24+
*/
25+
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.activej-http";
26+
27+
private static final Instrumenter<HttpRequest, HttpResponse> INSTRUMENTER;
28+
29+
static {
30+
INSTRUMENTER =
31+
JavaagentHttpServerInstrumenters.create(
32+
INSTRUMENTATION_NAME,
33+
new ActiveJHttpServerHttpAttributesGetter(),
34+
ActiveJHttpServerHeaders.INSTANCE);
35+
}
36+
37+
public static Instrumenter<HttpRequest, HttpResponse> instrumenter() {
38+
return INSTRUMENTER;
39+
}
40+
41+
private ActiveJHttpServerConnectionSingletons() {}
42+
}

0 commit comments

Comments
 (0)