Skip to content

Observability

View Markdown

A Vertique application answers three operational questions: is it alive, what is it doing, and which request caused this. Each answer comes from a module you compose, with safe defaults:

  • A separate management plane. Liveness and readiness probes are served by a dedicated management server (port 9090 by default), separate from the application port. Probes and metrics can therefore sit behind different network policy than application traffic.
  • Any module can contribute a health check. The health-check SPI lives in the core module, so a capability contributes its own readiness without depending on the management server.
  • Backend-agnostic metrics. One injected meter registry is bootstrapped before the runtime and wired into Vert.x’s own metrics. Backends activate by classpath presence. With none present, or with metrics disabled, recording is a safe no-op.
  • Per-layer metrics as modules. REST and services metrics ship as their own modules, which you add when you want them. The built-in @Timed aspect times any bean method (see AOP and events).
  • Opt-in distributed tracing. OpenTelemetry bootstraps its own SDK or reuses a javaagent’s, installs the Vert.x tracer, and bridges trace ids into the correlation context so that logs and traces share ids.
  • One correlation id across the request path. A request id at the REST edge, an x-correlation-id header on Kafka records, and the durable context captured by workflow instances are mirrored into the logging MDC, so a log line names the request it belongs to.
  • Completion callbacks at every boundary. REST requests, MCP requests, Kafka records, and outbox publish attempts each report their final outcome to a listener the application contributes.

The REST and services starters include the management plane. Metrics and tracing are modules your component names explicitly.

Deployment covers the platform side: where probes are pointed, who scrapes metrics, and where logs ship. This page covers what the application itself emits.

ManagementVerticle serves Kubernetes-style probes on the management port. Each probe is backed by a qualified health-check set:

Endpoint Backing check set UP response DOWN response
GET /health/live @Liveness-qualified checks 200 503
GET /health/ready @Readiness-qualified checks 200 503

Framework capabilities contribute their own checks. The run on the Services page shows a managed service deployment reporting through readiness. An application contributes its own HealthCheck the same way, through a Dagger multibinding. The management server also hosts operational endpoints contributed by other modules, such as a Prometheus scrape route when that backend is installed. Applications on vertique-starter-rest or vertique-starter-services already have ManagementModule in their component. Every other application names it explicitly.

vertique-micrometer-core bootstraps a composite meter registry before the runtime is built, wires it into Vert.x’s metrics subsystem, and exposes it as an injectable MeterRegistry. Code records metrics without knowing which backends exist.

Backends are classpath-activated. vertique-micrometer-registry-prometheus contributes the Prometheus registry and its scrape endpoint on the management plane. Add vertique-micrometer-rest or vertique-micrometer-services for per-request and per-dispatch metrics at those layers, and use the @Timed aspect for any bean method.

Add the optional vertique-micrometer-resilience adapter alongside vertique-resilience when resilience events should become timers and counters in the shared registry. It contributes one isolated, synchronous observer and follows metrics.enabled. See the vertique-micrometer-resilience module reference for the fixed meter names, tags, and cardinality exclusions.

When metrics.enabled is false or no backend is present, the composite is empty and every recording is a no-op.

vertique-opentelemetry-core provides opt-in distributed tracing. It bootstraps an OpenTelemetry SDK before the runtime, or reuses an existing global one installed by a javaagent. It then installs the Vert.x tracing subsystem and emits security lifecycle events as span events.

Trace ids are bridged into the framework’s correlation context, so once tracing is on, logs and spans share identifiers without further wiring. vertique-opentelemetry-rest and vertique-opentelemetry-services add the per-layer spans. Tracing owns no metrics and no log output. Those belong to Micrometer and the logging module, so the three concerns do not overlap.

vertique-correlation owns the correlation context that the rest of the framework reads and writes: generated or inherited correlation ids, selective mirroring into the logging MDC, and the adapter that snapshots and restores the context across async boundaries. Because of it, the REST edge returns an X-Request-Id header, a consumed Kafka record’s x-correlation-id seeds its dispatch, and a workflow instance’s captured durable context can fill correlation back in when the instance resumes days later.

Each transport reports a request once, when it is finished, to listeners the application contributes through a Dagger set multibinding. Use them for your own metrics, audit records, or access logs. The framework’s REST metrics module is itself one of these listeners.

Request Listener
A JAX-RS operation, including one rejected with 401, 403, 415, or a validation 400 RestRequestCompletedListener
A request no transport claimed: 404, 405, a root-level rejection, a failed WebSocket upgrade HttpRequestCompletedListener
A request the MCP mount handled McpRequestCompletedListener

A request produces one event, from the transport that handled it. An MCP request does not also produce a REST event, and a successful WebSocket upgrade produces none.

From the vertique-rest-core module reference, a listener that counts requests by route:

@Provides
@IntoSet
static RestRequestCompletedListener requestMetrics(MeterRegistry registry) {
return event -> registry.counter(
"http.server.requests",
"method", event.method(),
"route", event.operation().routeTemplate(),
"status", Integer.toString(event.statusCode()))
.increment();
}

A REST event always carries the matched operation, so event.operation() is never null. Both REST and HTTP events also carry the timing, the status, a safe failure code, the security and correlation snapshots, and the request origin.

Listeners are unordered and must not block. An exception a listener throws is logged and does not stop the remaining listeners or change the response. A listener that needs the live request overrides the two-argument onCompleted(event, routingContext) overload.

The messaging modules follow the same pattern. See Observe every record’s outcome for Kafka consumers and Observe publish attempts for the outbox relay.