Micrometer registry Prometheus
View MarkdownStatus: Stable Package:
dev.vertique.micrometer.prometheusArtifact:vertique-micrometer-registry-prometheusDepends on: micrometer-core, management, vertx-web, micrometer-registry-prometheus, prometheus-metrics-tracer-common API reference: javadoc.io
Provides a Prometheus backend for the Micrometer metrics layer and mounts a scrape endpoint on the
management HTTP server. Installing this module registers a PrometheusMeterRegistry as a backend
inside vertique-micrometer-core’s composite, then exposes that registry at a configurable path on
the management port so Prometheus can scrape it.
The module does not produce a Dagger PrometheusMeterRegistry binding — there is deliberately
exactly one registry instance, created pre-DI by the ServiceLoader provider and held in a
package-private static holder. Application and framework code always injects the generic
MeterRegistry from MicrometerModule.
When To Use It
Section titled “When To Use It”Add vertique-micrometer-registry-prometheus to any application that should expose metrics in
Prometheus format. Install MicrometerPrometheusModule in the Dagger @Component alongside
MicrometerModule and ManagementModule. The module is inert when metrics.enabled=false, when
VertiqueApplication is not used to launch the app, or when no management verticle is deployed.
Core Concepts
Section titled “Core Concepts”Single-registry identity. Exactly one PrometheusMeterRegistry exists per process. It is
created pre-DI by PrometheusMeterRegistryProvider (discovered via ServiceLoader), published to
the package-private PrometheusBackend static holder, and added to vertique-micrometer-core’s
CompositeMeterRegistry as a backend child. The handle’s close() clears the holder — this
makes rollback and test isolation correct without a second construction path.
Scrape endpoint on the management port only. PrometheusScrapeEndpoint implements
ManagementEndpointContributor and mounts the scrape route on the management router at
#contribute(Router) time, never on the application HTTP router. The management port is protected
by network policy (NFR-TEL-005) — metric names, label values, and (when enabled) exemplar trace
and span IDs are visible to anyone with access to that port. Never expose the management port on a
public network interface.
Worker-thread rendering. Prometheus gauges execute user-provided lambdas at render time. To
prevent those lambdas from blocking the management event loop, PrometheusScrapeEndpoint offloads
PrometheusMeterRegistry.scrape() to a Vert.x worker thread via
Vertx.executeBlocking(..., ordered=false).
Exemplars are opt-in. Exemplars attach trace and span IDs to histogram and summary samples. They
are disabled by default (metrics.prometheus.exemplars.enabled=false) because they expose
trace IDs on the management port. When enabled, exemplars render only in OpenMetrics format and
only when an io.prometheus.metrics.tracer.common.SpanContext Dagger binding is present — provided
by vertique-opentelemetry-prometheus when installed alongside vertique-opentelemetry-core.
Disabled-state matrix. The scrape endpoint mounts nothing (INFO log) when any of the following
is true: metrics.enabled=false; no MeterRegistryProvider on the classpath; the application is
not launched via VertiqueApplication (contributor never ran). Health endpoints are unaffected in
all cases.
Cardinality guarding happens upstream. vertique-micrometer-core’s composite-level cardinality
guard (metrics.cardinality.*) is installed on the composite registry before this module’s backend
is added as a child. A vertique.* meter denied by that guard is never created on any child
registry, including this module’s PrometheusMeterRegistry — the Prometheus scrape output already
reflects the bounded cardinality.
Key Classes
Section titled “Key Classes”PrometheusMeterRegistryProvider
Section titled “PrometheusMeterRegistryProvider”MeterRegistryProvider implementation registered via
META-INF/services/dev.vertique.micrometer.MeterRegistryProvider. Discovered and invoked by
MicrometerMetricsContributor before Vertx is built.
On create(JsonObject):
- Creates a
DeferredSpanContextthat can be wired to a live tracing integration later. - Creates a
PrometheusMeterRegistrywithPrometheusConfig.DEFAULT, a freshPrometheusRegistry, and the deferred span context. - Publishes both to
PrometheusBackend. - Returns a
MeterRegistryBackendhandle whoseclose()is idempotent and clears the holder.
The backendConfig argument (the metrics.backends.prometheus.* subtree) is accepted but
currently unused. metrics.backends.prometheus.* keys are reserved for future backend options.
Invariants and Gotchas
Section titled “Invariants and Gotchas”close()clearsPrometheusBackendatomically viaAtomicBoolean. This is called on both normal shutdown (reverse-order byMicrometerMetricsContributor.onShutdown()) and bootstrap rollback when a later provider fails. Test suites must callMeterRegistryHolder.resetForTests()from@AfterEachto clear both the composite holder and thePrometheusBackendstate.- There is no Dagger
PrometheusMeterRegistrybinding. Code that needs the concrete type for testing may access it viaPrometheusBackend.registry(), which is package-private todev.vertique.micrometer.prometheus.
PrometheusScrapeEndpoint
Section titled “PrometheusScrapeEndpoint”@Singleton ManagementEndpointContributor. Mounted into the management router multibinding set
by MicrometerPrometheusModule.
contribute(Router) behavior:
- Reads
PrometheusBackend.registry(). If empty, logs at INFO and returns without mounting any route. - If exemplars are enabled (
metrics.prometheus.exemplars.enabled=true) and aSpanContextis present in the Dagger graph, wires theDeferredSpanContextto the real implementation. - Mounts
GET {metrics.scrape.path}on the management router.
Per-request handling:
- Inspects the
Acceptheader. If it containsapplication/openmetrics-text, renders in OpenMetrics 1.0.0 format (application/openmetrics-text; version=1.0.0; charset=utf-8). Otherwise renders in Prometheus text 0.0.4 format (text/plain; version=0.0.4; charset=utf-8). - Offloads rendering to a worker thread via
executeBlocking(..., ordered=false). - On render success: responds 200 with the appropriate
Content-Type. - On render failure: logs the exception class name at WARN and responds 500 with the constant body
"metrics unavailable". No exception detail is included in the response.
DeferredSpanContext
Section titled “DeferredSpanContext”Package-private SpanContext implementation that forwards all calls to a delegate once one is
attached via delegate(SpanContext). Before attachment, getCurrentTraceId() and
getCurrentSpanId() return null, isCurrentSpanSampled() returns false, and
markCurrentSpanAsExemplar() is a no-op.
The registry is constructed with this deferred instance so that exemplar support can be enabled
later, at contribute() time, without requiring the tracing integration to be present at backend
creation time. The delegate field is volatile for safe cross-thread publication.
PrometheusBackend
Section titled “PrometheusBackend”Package-private static holder. Stores the PrometheusMeterRegistry and DeferredSpanContext in
AtomicReferences so the scrape endpoint and backend provider can access them safely across
threads.
| Method | Description |
|---|---|
registry() |
Optional<PrometheusMeterRegistry> — empty until publish(), empty again after clear() |
spanContext() |
Optional<DeferredSpanContext> — aligned with registry() |
publish(registry, spanContext) |
Called by PrometheusMeterRegistryProvider.create() |
clear() |
Called by the backend handle’s close() on shutdown or rollback |
MicrometerPrometheusModule
Section titled “MicrometerPrometheusModule”Abstract Dagger @Module. Install alongside MicrometerModule and ManagementModule:
@Singleton@Component(modules = { VertxModule.class, MicrometerModule.class, ManagementModule.class, MicrometerPrometheusModule.class, AppModule.class})interface AppComponent { ManagementVerticle managementVerticle(); HttpVerticle httpVerticle();}Bindings provided:
| Type | Qualifier | Description |
|---|---|---|
PrometheusScrapeConfig |
@Singleton |
Deserialized and validated from metrics config section |
ManagementEndpointContributor |
@IntoSet |
PrometheusScrapeEndpoint contributed to the multibinding set |
SpanContext |
@BindsOptionalOf |
Optional tracing integration; provided by vertique-opentelemetry-prometheus when installed |
Configuration
Section titled “Configuration”Keys live under the metrics section, alongside vertique-micrometer-core keys.
| Key | Type | Default | Description |
|---|---|---|---|
metrics.scrape.path |
string | "/metrics" |
Path on the management router where Prometheus scrapes. Must match ^/[A-Za-z0-9._/-]*$, must not contain * or :, must not equal or start with /health. |
metrics.prometheus.exemplars.enabled |
boolean | false |
Opt-in exemplar support. When true AND a SpanContext binding is present, trace and span IDs are attached to histogram and summary samples. Exemplars render only in OpenMetrics format. |
metrics.backends.prometheus.* |
object | {} |
Reserved for future Prometheus backend options. Currently unused — PrometheusConfig.DEFAULT is always applied. |
Example config:
{ "metrics": { "enabled": true, "scrape": { "path": "/metrics" }, "prometheus": { "exemplars": { "enabled": false } } }}Exposure Guidance (NFR-TEL-005)
Section titled “Exposure Guidance (NFR-TEL-005)”The /metrics endpoint exists only on the management port. The management port is protected by
network policy, not authentication. Metric names, label values, and — when exemplars are enabled —
trace and span IDs are visible to anyone with access to the port. Never expose the management port
to public networks or untrusted network segments.
Extension Points
Section titled “Extension Points”Exemplar SpanContext
Section titled “Exemplar SpanContext”MicrometerPrometheusModule declares SpanContext as @BindsOptionalOf. When
vertique-opentelemetry-prometheus is installed, its OpenTelemetryPrometheusExemplarModule
contributes a SpanContext binding backed by OpenTelemetrySpanContext (from
io.prometheus:prometheus-metrics-tracer-otel), which reads Span.current() lazily at
exemplar-sample time. When that module is absent, the optional is empty and exemplars remain
inactive regardless of metrics.prometheus.exemplars.enabled.
See dev.vertique:vertique-opentelemetry-prometheus for installation details.
To wire a custom span context instead:
@Modulepublic abstract class MyTracingModule {
@Provides @Singleton static SpanContext spanContext() { return MyCustomSpanContext.INSTANCE; }}Install MyTracingModule alongside MicrometerPrometheusModule in the @Component.
Wiring
Section titled “Wiring”PrometheusMeterRegistryProvider is auto-discovered via ServiceLoader — no explicit wiring is
needed for the registry creation step. The Dagger module wires the scrape endpoint into the
management router.
Launch via VertiqueApplication is required. The provider runs inside
MicrometerMetricsContributor, which is a VertxBuilderContributor discovered by
VertiqueApplication before Vertx is built. If the app is launched directly (e.g., with
Vertx.vertx() in a unit test), the contributor never runs, PrometheusBackend stays empty,
and the scrape endpoint silently mounts nothing.
Minimal component for a metrics-enabled application:
@Singleton@Component(modules = { VertxModule.class, MicrometerModule.class, ManagementModule.class, MicrometerPrometheusModule.class, RestModule.class, AppModule.class})interface AppComponent { ManagementVerticle managementVerticle(); HttpVerticle httpVerticle();}The adapter’s simple SPI contribution is declared on its injectable implementation with
@RegisterIntoSet. During the provider build, vertique-codegen-dagger emits
GeneratedRegistrationsModule, which this module includes explicitly. The generated module contains
only this type adaptation; configuration, registry, and optional bindings remain hand-written.
Dependencies
Section titled “Dependencies”dev.vertique:vertique-micrometer-core—MeterRegistryProvider,MeterRegistryBackend,MicrometerModule,MeterRegistryHolderdev.vertique:vertique-management—ManagementEndpointContributor,ManagementModule,ManagementVerticleio.micrometer:micrometer-registry-prometheus—PrometheusMeterRegistry,PrometheusConfigio.prometheus:prometheus-metrics-tracer-common—SpanContext(exemplar bridge interface)io.vertx:vertx-web—Router(management endpoint mounting)com.google.dagger:dagger,jakarta.inject:jakarta.inject-apiorg.slf4j:slf4j-api,org.projectlombok:lombok(provided)