Skip to content

last9/vertx-opentelemetry

Repository files navigation

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

export OTEL_SERVICE_NAME=my-service
export OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.last9.io
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic <token>"

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:

# 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 correlationtrace_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), lost context across RxJava operators, and broken virtual thread support on Java 21 (#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

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

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 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

<dependency>
    <groupId>io.last9</groupId>
    <artifactId>vertx4-rxjava3-otel-autoconfigure</artifactId>
    <version>2.3.4</version>
</dependency>
// Use OtelLauncher as main class
// <mainClass>io.last9.tracing.otel.v4.OtelLauncher</mainClass>

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

<dependency>
    <groupId>io.last9</groupId>
    <artifactId>vertx3-rxjava2-otel-autoconfigure</artifactId>
    <version>2.3.4</version>
</dependency>
// <mainClass>io.last9.tracing.otel.v3.OtelLauncher</mainClass>

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<String, String> producer = TracedKafkaProducer.wrap(KafkaProducer.create(vertx, config));
TracedKafkaConsumer.create(vertx, config, "topic", "group", records -> { ... });

Manual SDK init (custom main class)

import io.last9.tracing.otel.OtelSdkSetup;
import io.last9.tracing.otel.v3.RxJava2ContextPropagation; // or v4.RxJava3ContextPropagation

OtelSdkSetup.initialize();
RxJava2ContextPropagation.install();

License

MIT

Support

About

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.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages