_ _____ ____ ____ _____ _ _
| |/ / _ \| _ \|_ _|| ____|| \/ |
| ' / | | | |_) | || | | _| > <
| . \ |_| | _ < || | | |___/ /\ \
|_|\_\___/|_| \_\|___||_____/_/ \_\
A G E N T
Zero-overhead distributed tracing for the JVM
Kortex Agent is a Java instrumentation agent written in Kotlin that automatically instruments your JVM application for distributed tracing — with no code changes required. Attach it via -javaagent and it instantly begins capturing SQL queries, incoming HTTP requests, and outgoing HTTP calls, reporting structured spans to your Kortex Core backend via gRPC.
Built on ByteBuddy, it injects tracing logic at the bytecode level using the Advice pattern, keeping runtime overhead minimal and your application code pristine.
| Capability | Details |
|---|---|
| 🗄️ JDBC Instrumentation | Traces all PreparedStatement executions — queries, updates, and batch operations |
| 🌐 HTTP Server Tracing | Captures incoming requests via Jakarta & Javax Servlet APIs |
| 🔗 HTTP Client Tracing | Instruments outgoing calls through java.net.http.HttpClient |
| 📡 W3C Trace Context | Full traceparent header propagation for end-to-end distributed traces |
| ⚡ Async Reporting | Non-blocking span collection via LinkedBlockingQueue with batched gRPC delivery |
| 🪶 Zero Footprint | ByteBuddy Advice (compile-time code templates) — no proxies, no reflection overhead |
./gradlew clean buildThe agent JAR will be produced at:
build/libs/kortex-agent-1.0.0.jar
Prepend the -javaagent flag to your application's startup command:
java -javaagent:path/to/kortex-agent-1.0.0.jar=host=localhost,port=9090 \
-jar your-application.jarPass configuration as comma-separated key=value pairs in the agent argument string:
| Argument | Default | Description |
|---|---|---|
host |
localhost |
Hostname of the Kortex Core gRPC server |
port |
9090 |
Port of the Kortex Core gRPC server |
Example — custom backend:
java -javaagent:kortex-agent-1.0.0.jar=host=trace-server,port=8080 \
-jar app.jarEvery captured operation is reported as a structured span:
┌─────────────────────────────────────────────────────────┐
│ Span │
├─────────────────────┬───────────────────────────────────┤
│ trace_id │ 32-char hex (W3C compliant) │
│ span_id │ 16-char hex │
│ parent_span_id │ 16-char hex (for linking) │
│ name │ "jdbc.executeQuery", "HTTP GET /…" │
│ kind │ DB | SERVER | CLIENT │
│ start_time_unix_nano│ nanosecond precision timestamp │
│ end_time_unix_nano │ nanosecond precision timestamp │
│ attributes │ SQL query, HTTP method, status… │
│ status │ OK | ERROR │
└─────────────────────┴───────────────────────────────────┘
Your Application
│
▼
┌─────────────────────────────────────────────────────────┐
│ JVM + Kortex Agent │
│ │
│ ┌─────────────┐ ┌───────────────┐ │
│ │ KortexAgent │───▶│ ByteBuddy │ Transforms target │
│ │ (premain) │ │ Transformers │ classes at load │
│ └─────────────┘ └───────┬───────┘ │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌──────────┐ ┌─────────────┐ │
│ │ JDBC │ │ HTTP │ │ HTTP Client │ │
│ │ Advice │ │ Server │ │ Advice │ │
│ └──────┬─────┘ │ Advice │ └──────┬──────┘ │
│ │ └────┬─────┘ │ │
│ └────────────┼──────────────┘ │
│ ▼ │
│ ┌────────────────┐ │
│ │ ContextManager │ Thread-local trace │
│ │ (W3C context) │ propagation │
│ └───────┬────────┘ │
│ ▼ │
│ ┌────────────────┐ │
│ │ SpanReporter │ LinkedBlockingQueue │
│ │ (async batch) │ + daemon thread │
│ └───────┬────────┘ │
└──────────────────────────│──────────────────────────────┘
│ gRPC
▼
┌────────────────┐
│ Kortex Core │
└────────────────┘
| Layer | Intercepted Methods |
|---|---|
| JDBC | PreparedStatement.execute, .executeQuery, .executeUpdate |
| HTTP Server | HttpServlet.service, .doGet, .doPost, .doPut, .doDelete |
| HTTP Client | HttpClient.send, .sendAsync |
- The JVM invokes
premainbefore your application'smainmethod - ByteBuddy installs transformers that rewrite matching classes at load time
- Advice code surrounds each instrumented method, capturing start/end timestamps and context
- Completed spans are enqueued into a
LinkedBlockingQueue(non-blocking for your threads) - A background daemon thread drains the queue and sends batches to Kortex Core over gRPC
kortex-agent/
├── src/main/
│ ├── java/io/kortex/agent/internal/ # Java helpers for ByteBuddy interop
│ ├── kotlin/io/kortex/agent/
│ │ ├── advice/ # Instrumentation advice classes
│ │ │ ├── JdbcAdvice.kt
│ │ │ ├── HttpServerAdvice.kt
│ │ │ └── HttpClientAdvice.kt
│ │ ├── ContextManager.kt # Thread-local W3C trace context
│ │ ├── KortexAgent.kt # Agent entry point (premain)
│ │ └── SpanReporter.kt # Async batching + gRPC client
│ └── proto/ # Protobuf definitions
├── build.gradle.kts
└── settings.gradle.kts
Create a minimal test application that exercises both JDBC and HTTP:
import java.sql.*;
import java.net.http.*;
import java.net.URI;
public class TestApp {
public static void main(String[] args) throws Exception {
// JDBC — will produce a DB span
Connection conn = DriverManager.getConnection("jdbc:h2:mem:test");
PreparedStatement stmt = conn.prepareStatement("SELECT 1");
stmt.executeQuery();
// HTTP Client — will produce a CLIENT span
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://example.com"))
.build();
client.send(request, HttpResponse.BodyHandlers.ofString());
}
}Run with the agent attached:
java -javaagent:kortex-agent-1.0.0.jar TestApp- JDK 11 or higher
- Gradle 8.5+
Copyright © 2024 Kortex · All rights reserved