https://github.com/last9/vertx-opentelemetry
Zero-code OpenTelemetry auto-instrumentation for Vert.x 4 + RxJava 3 and Vert.x 3 + RxJava 2. Distributed tracing, log-to-trace correlation, and RxJava context propagation.
https://github.com/last9/vertx-opentelemetry
distributed-tracing instrumentation java log-correlation observability opentelemetry otel rxjava tracing vertx
Last synced: about 2 months ago
JSON representation
Zero-code OpenTelemetry auto-instrumentation for Vert.x 4 + RxJava 3 and Vert.x 3 + RxJava 2. Distributed tracing, log-to-trace correlation, and RxJava context propagation.
- Host: GitHub
- URL: https://github.com/last9/vertx-opentelemetry
- Owner: last9
- License: mit
- Created: 2026-02-06T05:34:21.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-04-02T13:45:47.000Z (2 months ago)
- Last Synced: 2026-04-03T02:33:53.386Z (2 months ago)
- Topics: distributed-tracing, instrumentation, java, log-correlation, observability, opentelemetry, otel, rxjava, tracing, vertx
- Language: Java
- Homepage: https://last9.io
- Size: 646 KB
- Stars: 3
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Vert.x OpenTelemetry
Drop a JAR on the JVM. Get traces, metrics, and logs. No code changes.
| Stack | Agent |
|-------|-------|
| Vert.x 3.9+ / RxJava 2 | `vertx3-otel-agent.jar` |
| Vert.x 4.5+ / RxJava 3 | `vertx4-otel-agent.jar` |
## Get started
```bash
export OTEL_SERVICE_NAME=my-service
export OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.last9.io
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic "
java -javaagent:vertx3-otel-agent.jar -jar my-app.jar
```
That's it. Every HTTP endpoint, database query, Kafka message, cache operation, and outbound HTTP call is traced automatically.
Download the latest agent from [Releases](https://github.com/last9/vertx-opentelemetry/releases):
```bash
# Vert.x 3
curl -L -o vertx3-otel-agent.jar \
https://github.com/last9/vertx-opentelemetry/releases/download/v2.3.4/vertx3-otel-agent-2.3.4.jar
# Vert.x 4
curl -L -o vertx4-otel-agent.jar \
https://github.com/last9/vertx-opentelemetry/releases/download/v2.3.4/vertx4-otel-agent-2.3.4.jar
```
## What gets instrumented
### Vert.x 3
ByteBuddy instruments these at class-load time — no wrappers, no annotations, no XML:
| Component | Span Kind | Key Attributes |
|-----------|-----------|---------------|
| Netty HTTP server | SERVER | `http.request.method`, `url.path`, `http.response.status_code` |
| Router (RxJava2 + core) | SERVER | `http.route` (pattern, not literal path) |
| WebClient | CLIENT | `url.full`, `server.address`, `http.response.status_code` |
| JDBCClient | CLIENT | `db.system`, `db.name`, `db.statement` |
| KafkaProducer | PRODUCER | `messaging.destination.name` |
| KafkaConsumer | CONSUMER | `messaging.batch.message_count` |
| AerospikeClient | CLIENT | `db.system=aerospike`, `net.peer.name` |
| MySQLPool / PgPool | CLIENT | `db.system`, `db.statement` |
| Jedis (Pool, Cluster, Pipeline) | CLIENT | `db.system=redis`, `db.statement` |
| Lettuce (sync/async/reactive) | CLIENT | `db.system=redis`, `db.statement` |
| Raw JDBC (`Statement.execute*`) | CLIENT | `db.system` (auto-detected), `db.statement` |
| Netty HTTP client | CLIENT | `http.method`, `net.peer.name` |
| RESTEasy (JAX-RS) | — | `@Path` templates → `http.route` |
| AWS SQS (SDK v1 + v2) | CONSUMER | `messaging.system=AmazonSQS` |
### Vert.x 4
The agent injects `TracingOptions` at startup, activating the native `VertxTracer` SPI. No ByteBuddy needed for the core stack:
| Component | How | What you get |
|-----------|-----|-------------|
| HTTP server | VertxTracer SPI | SERVER spans for every request |
| HTTP client | VertxTracer SPI | CLIENT spans + `traceparent` injection |
| EventBus | VertxTracer SPI | INTERNAL spans for send/publish |
| SQL client (PgPool, MySQLPool) | VertxTracer SPI | CLIENT spans with SQL |
| Redis client | VertxTracer SPI | CLIENT spans |
| Kafka | VertxTracer SPI | PRODUCER/CONSUMER spans |
| Router | ByteBuddy | Route patterns: `GET /v1/users/:id` |
| Vert.x metrics | Micrometer → OTel bridge | HTTP pools, event bus, event loop lag |
| Jedis, Lettuce, JDBC, Aerospike, RESTEasy, SQS | ByteBuddy | Same as v3 |
### Across all agents
- **RxJava context propagation** — trace context flows across `subscribeOn`, `observeOn`, `flatMap`
- **W3C `traceparent`** — injected on every outgoing HTTP and Kafka call
- **Log-trace correlation** — `trace_id` and `span_id` auto-injected into every Logback line. No `logback.xml` changes.
- **JVM metrics** — memory, GC, threads, CPU
- **Cloud resource detection** — AWS (EC2, ECS, EKS) and GCP (GCE, GKE, Cloud Run)
## Why not the OpenTelemetry Java Agent?
The OTel Java Agent assumes `ThreadLocal` context propagation. Vert.x doesn't use `ThreadLocal`. The result: broken spans on async HTTP calls ([#11860](https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/11860)), lost context across RxJava operators, and broken virtual thread support on Java 21 ([#10526](https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/10526)).
This library works with Vert.x's event-loop model — handler-based instrumentation for v3, native `VertxTracer` SPI for v4, RxJava assembly hooks for context propagation across all operators.
## Java compatibility
**Java 8+.** The agent uses the OkHttp-based OTLP sender (shaded as `io.last9.internal.okhttp3`) instead of `java.net.http.HttpClient`. Same JAR on Java 8, 11, 17, 21.
## Deployment
### EC2 / Bare metal
```bash
curl -L -o /opt/otel/vertx3-otel-agent.jar \
https://github.com/last9/vertx-opentelemetry/releases/download/v2.3.4/vertx3-otel-agent-2.3.4.jar
java -javaagent:/opt/otel/vertx3-otel-agent.jar -jar /opt/app/my-app.jar
```
### Docker / Kubernetes
```dockerfile
FROM eclipse-temurin:11-jre-alpine
COPY target/my-app.jar /app/my-app.jar
COPY vertx3-otel-agent.jar /app/vertx3-otel-agent.jar
CMD ["java", "-javaagent:/app/vertx3-otel-agent.jar", "-jar", "/app/my-app.jar"]
```
## How it works
1. A 2-class shim (`AgentBootstrap`) loads on the system classloader — compiled to Java 8 bytecode
2. The embedded library JAR (`agent-impl.jar`) is injected onto the system classloader
3. All OTel SDK, ByteBuddy, and OkHttp classes are shaded under `io.last9.internal.*` — no classpath conflicts even if your app bundles its own OTel SDK
4. ByteBuddy transformers intercept Vert.x APIs at load time
5. RxJava context propagation hooks are installed automatically
6. Log-trace correlation is auto-installed into Logback
**No Maven dependency required.** Drop the JAR and go.
> **Do not add `io.last9:vertx3-rxjava2-otel-autoconfigure` (or the v4 equivalent) as a Maven dependency when using `-javaagent`.** The app's unshaded classes will shadow the agent's shaded ones — you'll get a no-op tracer and zero spans.
## Configuration
All standard [OpenTelemetry environment variables](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/) work:
| Variable | Default |
|----------|---------|
| `OTEL_SERVICE_NAME` | `unknown-service` |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4318` |
| `OTEL_EXPORTER_OTLP_HEADERS` | — |
| `OTEL_EXPORTER_OTLP_TIMEOUT` | `10000` |
| `OTEL_RESOURCE_ATTRIBUTES` | — |
| `OTEL_TRACES_SAMPLER` | `parentbased_always_on` |
| `OTEL_LOGS_EXPORTER` | `otlp` |
| `OTEL_METRICS_EXPORTER` | `otlp` |
| `OTEL_METRIC_EXPORT_INTERVAL` | `60000` |
| `OTEL_BSP_SCHEDULE_DELAY` | `5000` |
Set `OTEL_EXPORTER_OTLP_TIMEOUT=30000` when exporting to remote backends.
## Troubleshooting
**Disconnected traces (outgoing calls show as separate root spans)**
1. Look for `bytecode instrumentation installed` in logs — confirms the agent loaded
2. Look for `RxJava context propagation installed` — confirms async context flows
3. Verify the downstream service is also OTel-instrumented and reads `traceparent`
**No spans exported**
1. Check `OTEL_EXPORTER_OTLP_ENDPOINT` is reachable
2. Check `OTEL_EXPORTER_OTLP_HEADERS` auth is correct
3. Look for `Connection refused` in stderr
4. Set `OTEL_LOG_LEVEL=debug` for verbose export logs
**CLIENT spans present but no SERVER spans**
1. Look for `transformed io.vertx.reactivex.ext.web.Router (loaded=false)` and `HttpServerAdviceHelper: wrapping requestHandler` in logs
2. Look for `Tracer OK (io.last9.internal.otel.sdk.trace.SdkTracer)` on stderr — if you see `Tracer is NO-OP`, SDK init failed
3. Look for `WARNING: HttpServerAdviceHelper is missing version marker` — this means the app bundles a conflicting `io.last9` library dependency. Remove it.
## Requirements
| Agent | Java | Vert.x | RxJava |
|-------|------|--------|--------|
| `vertx3-otel-agent` | 8+ | 3.9+ | 2.x |
| `vertx4-otel-agent` | 11+ | 4.5+ | 3.x |
---
Library Mode
> Use the agent. Library mode exists for environments where `-javaagent` is not feasible.
### Vert.x 4
```xml
io.last9
vertx4-rxjava3-otel-autoconfigure
2.3.4
```
```java
// Use OtelLauncher as main class
// io.last9.tracing.otel.v4.OtelLauncher
Router router = TracedRouter.create(vertx);
TracedDBPool traced = TracedDBPool.wrap(PgPool.pool(vertx, opts, poolOpts), "postgresql", "mydb");
consumer.batchHandler(KafkaTracing.tracedBatchHandler(topicName, this::handleBatch));
```
### Vert.x 3
```xml
io.last9
vertx3-rxjava2-otel-autoconfigure
2.3.4
```
```java
// io.last9.tracing.otel.v3.OtelLauncher
Router router = TracedRouter.create(vertx);
WebClient client = TracedWebClient.create(vertx);
SQLClient sql = TracedSQLClient.wrap(JDBCClient.createShared(vertx, config), "mysql", "mydb");
TracedAerospikeClient aero = TracedAerospikeClient.wrap(new AerospikeClient("localhost", 3000), "ns");
TracedKafkaProducer producer = TracedKafkaProducer.wrap(KafkaProducer.create(vertx, config));
TracedKafkaConsumer.create(vertx, config, "topic", "group", records -> { ... });
```
### Manual SDK init (custom main class)
```java
import io.last9.tracing.otel.OtelSdkSetup;
import io.last9.tracing.otel.v3.RxJava2ContextPropagation; // or v4.RxJava3ContextPropagation
OtelSdkSetup.initialize();
RxJava2ContextPropagation.install();
```
## License
MIT
## Support
- [GitHub Issues](https://github.com/last9/vertx-opentelemetry/issues)
- [Last9 Documentation](https://last9.io/docs)