# Vert.x

> Zero-code OpenTelemetry instrumentation for Vert.x 3/4 + RxJava 2/3 applications — traces, logs, and JVM metrics to Last9

Source: https://last9.io/docs/integrations/frameworks/java/vertx/

This guide shows how to instrument a Vert.x application with OpenTelemetry using the
[`vertx-opentelemetry`](https://github.com/last9/vertx-opentelemetry) library and send traces, logs, and JVM metrics to Last9.

The library supports both **Vert.x 4 + RxJava 3** and **Vert.x 3 + RxJava 2**. It works entirely through
environment variables — no code changes required beyond a one-line main class switch and adding a router call.

## Prerequisites

- Java 11 or later
- Vert.x 4.x (or Vert.x 3.9.x for the legacy module)
- Logback as the SLF4J implementation (for MDC trace injection)
- Last9 account with OTLP endpoint configured

## Step 1: Add the dependency

**Vert.x 4 + RxJava 3**

**Maven**

```xml
<dependency>
    <groupId>io.last9</groupId>
    <artifactId>vertx4-rxjava3-otel-autoconfigure</artifactId>
    <version>1.5.0</version>
</dependency>
```

**Gradle**

```groovy
implementation 'io.last9:vertx4-rxjava3-otel-autoconfigure:1.5.0'
```

**Vert.x 3 + RxJava 2**

**Maven**

```xml
<dependency>
    <groupId>io.last9</groupId>
    <artifactId>vertx3-rxjava2-otel-autoconfigure</artifactId>
    <version>1.5.0</version>
</dependency>
```

**Gradle**

```groovy
implementation 'io.last9:vertx3-rxjava2-otel-autoconfigure:1.5.0'
```

The library ships as a fat JAR with all OpenTelemetry SDK dependencies shaded internally — you don't need to add OTel SDK dependencies separately.

## Step 2: Change the main class

1.  **Switch your launcher to `OtelLauncher`**

**Vert.x 4 + RxJava 3**

    In your Maven or Gradle build, change the main class from `io.vertx.core.Launcher` to the OTel launcher:

    ```xml
    <!-- Maven: pom.xml -->
    <properties>
        <mainClass>io.last9.tracing.otel.v4.OtelLauncher</mainClass>
    </properties>
    ```

    ```groovy
    // Gradle: build.gradle
    mainClassName = 'io.last9.tracing.otel.v4.OtelLauncher'
    ```

**Vert.x 3 + RxJava 2**

    ```xml
    <!-- Maven: pom.xml -->
    <properties>
        <mainClass>io.last9.tracing.otel.v3.OtelLauncher</mainClass>
    </properties>
    ```

    ```groovy
    // Gradle: build.gradle
    mainClassName = 'io.last9.tracing.otel.v3.OtelLauncher'
    ```

    `OtelLauncher` extends the standard Vert.x `Launcher`. It auto-configures the OpenTelemetry SDK,
    installs the RxJava context propagation hooks, and enables Vert.x tracing — all before your verticles deploy.

2.  **Use `TracedRouter` instead of `Router`**

    Replace the standard Vert.x `Router` factory with `TracedRouter` to get HTTP SERVER spans with proper route pattern names:

**Vert.x 4 + RxJava 3**

    ```java
    import io.last9.tracing.otel.v4.TracedRouter;

    // Before:
    // Router router = Router.router(vertx);

    // After:
    Router router = TracedRouter.create(vertx);
    ```

**Vert.x 3 + RxJava 2**

    ```java
    import io.last9.tracing.otel.v3.TracedRouter;

    // Before:
    // Router router = Router.router(vertx);

    // After:
    Router router = TracedRouter.create(vertx);
    ```

    Without `TracedRouter`, HTTP spans are named with just the HTTP method (e.g., `GET`). `TracedRouter` updates
    span names to include the matched route pattern (e.g., `GET /v1/users/:id`) after route matching completes.

## Step 3: Configure Logback for trace injection (optional)

To include `trace_id` and `span_id` in log lines, add the `MdcTraceTurboFilter` to your `logback.xml`:

```xml
<configuration>
    <turboFilter class="io.last9.tracing.otel.MdcTraceTurboFilter"/>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} trace_id=%X{trace_id} span_id=%X{span_id} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>
```

This injects trace context directly from `Span.current()` before each log event, ensuring logs are correlated with
traces even across RxJava thread boundaries.

## Step 4: Set environment variables

```bash
export OTEL_SERVICE_NAME="your-service-name"
export OTEL_EXPORTER_OTLP_ENDPOINT="$last9_otlp_endpoint"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=$last9_otlp_auth_header"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_LOGS_EXPORTER="otlp"
export OTEL_TRACES_SAMPLER="parentbased_always_on"
```

Find your OTLP endpoint and auth header in the Last9 console under **Integrations → OpenTelemetry**.

### Cloud resource detection

Enable resource providers for your cloud environment so resource attributes (`cloud.provider`, `host.name`, etc.)
are automatically attached to all telemetry:

```bash
# AWS (EC2, ECS, EKS, Lambda)
export OTEL_RESOURCE_PROVIDERS_AWS_ENABLED=true

# GCP (GCE, GKE, Cloud Run, App Engine)
export OTEL_RESOURCE_PROVIDERS_GCP_ENABLED=true
```

### Sampling for high-traffic services

For services handling more than 10k requests per second, use ratio-based sampling to control costs:

```bash
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1   # Sample 10% of new traces
```

`parentbased_traceidratio` preserves the sampling decision from upstream services — if an upstream
service sampled the trace, all downstream spans continue to be recorded.

## What gets traced automatically

| Signal                  | Coverage                                                          |
| ----------------------- | ----------------------------------------------------------------- |
| HTTP Server             | All routes via `TracedRouter` — spans named `METHOD /path/:param` |
| HTTP Client             | Outgoing requests via `WebClient` (Vert.x 4)                      |
| EventBus                | Publish/request/reply messages                                    |
| SQL (PostgreSQL, MySQL) | Via Vert.x SQL client (Vert.x 4)                                  |
| Redis                   | Via `vertx-redis-client` (Vert.x 4)                               |
| Kafka                   | Via `vertx-kafka-client` (Vert.x 4)                               |
| RxJava operators        | Context preserved across `subscribeOn`, `observeOn`, `flatMap`    |
| JVM metrics             | Memory, GC, threads, CPU, class loading                           |
| Logs                    | Correlated with traces via MDC + OTLP export                      |

:::note
Vert.x 3 does not have the `VertxTracer` SPI, so SQL/Redis/Kafka/EventBus are not automatically traced.
HTTP SERVER spans are traced via `TracedRouter`. For outgoing HTTP calls in Vert.x 3, inject the
`traceparent` header manually using `openTelemetry.getPropagators().getTextMapPropagator().inject(...)`.
:::

## Verify the integration

Start your application and make a few HTTP requests. You should see:

1. **Traces** in Last9 with spans for HTTP requests named by route pattern
2. **Logs** with `trace_id` and `span_id` fields linking to the corresponding trace
3. **JVM metrics** (memory, GC, CPU) in Last9 under the service name

For troubleshooting, set `OTEL_LOG_LEVEL=debug` to see detailed SDK initialization logs.
