Micrometer Plugin

This plugin allows reporting metrics using Micrometer.

Getting Started

The Micrometer plugin is available on Maven Central as a separate artifact, but in order to use the plugin an additional library for reporting to a specific monitoring system is required. In the following description, an example is given to report to Prometheus. Thus, add the following dependencies:

<dependency>
    <groupId>io.javalin</groupId>
    <artifactId>javalin-micrometer</artifactId>
    <version>7.2.0</version>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
    <version>${io.micrometer.version}</version>
</dependency>

Create a registry, register the plugin, and provide a route:

public static void main(String[] args) {
    PrometheusMeterRegistry prometheusMeterRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
    String contentType = "text/plain; version=0.0.4; charset=utf-8";

    MicrometerPlugin micrometerPlugin = new MicrometerPlugin(micrometerPluginConfig -> micrometerPluginConfig.registry = prometheusMeterRegistry);
    Javalin.create(config -> {
        config.registerPlugin(micrometerPlugin);
        config.routes.get("/prometheus", ctx -> ctx.contentType(contentType).result(prometheusMeterRegistry.scrape()));
    }).start(8080);
}

With this setup, Javalin will report several Jetty-related metrics (for example accessed endpoints and returned status codes) at the specified endpoint, suitable for being ingested by Prometheus.

Provided Metrics

The Micrometer library comes with a number of useful metrics provider, which can be easily added:

import io.micrometer.core.instrument.binder.jvm.*;
import io.micrometer.core.instrument.binder.system.*;


PrometheusMeterRegistry prometheusMeterRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);


// add a tag to all reported values to simplify filtering in large installations:
registry.config().commonTags("application", "My-Application");

new ClassLoaderMetrics().bindTo(registry);
new JvmMemoryMetrics().bindTo(registry);
new JvmGcMetrics().bindTo(registry);
new JvmThreadMetrics().bindTo(registry);
new UptimeMetrics().bindTo(registry);
new ProcessorMetrics().bindTo(registry);
new DiskSpaceMetrics(new File(System.getProperty("user.dir"))).bindTo(registry);

MicrometerPlugin micrometerPlugin = new MicrometerPlugin(micrometerPluginConfig -> micrometerPluginConfig.registry = prometheusMeterRegistry);
String contentType = "text/plain; version=0.0.4; charset=utf-8";

Javalin.create(config -> {
    config.registerPlugin(micrometerPlugin);
    config.routes.get("/prometheus", ctx -> ctx.contentType(contentType).result(prometheusMeterRegistry.scrape()));
}).start(8080);

Custom Meters

It’s also easy to provide custom meters, reporting application-specific values, for example the length of a job-queue, the number of logged in users, or other values. In the following example, just a random number is returned:

import io.micrometer.core.instrument.Gauge;

Gauge
  .builder("myapp_random", () -> (int) (Math.random() * 1000))
  .description("Random number from My-Application.")
  .strongReference(true)
  .register(registry);

WebSocket Exception Tagging

To tag exceptions thrown from WebSocket handlers in your Micrometer metrics, delegate to the static wsExceptionHandler from your own wsException handler. The plugin exposes it as a companion-object field, and its signature in the source is:

@JvmField
val wsExceptionHandler = WsExceptionHandler<Exception> { e, ctx ->
    val simpleName = e.javaClass.simpleName
    ctx.attribute(EXCEPTION_HEADER, simpleName.ifBlank { e.javaClass.name })
}

When invoked, it stores the exception’s simple class name (falling back to the fully qualified name if the simple name is blank) on the WsContext as the __micrometer_exception_name attribute. By default — i.e. if you never delegate to this handler — WebSocket exceptions are not tagged in metrics.

Javalin.create(config -> {
    config.registerPlugin(new MicrometerPlugin(plugin -> plugin.registry = registry));
    // Delegate WebSocket exceptions to the Micrometer handler for tagging
    config.routes.wsException(Exception.class, (e, ctx) -> {
        MicrometerPlugin.wsExceptionHandler.handle(e, ctx);
        ctx.closeSession(WsCloseStatus.SERVER_ERROR, e.getMessage());
    });
});
Javalin.create { config ->
    config.registerPlugin(MicrometerPlugin { it.registry = registry })
    // Delegate WebSocket exceptions to the Micrometer handler for tagging
    config.routes.wsException(Exception::class.java) { e, ctx ->
        MicrometerPlugin.wsExceptionHandler.handle(e, ctx)
        ctx.closeSession(WsCloseStatus.SERVER_ERROR, e.message)
    }
}

Registering a wsException handler suppresses Javalin’s default behavior of closing the socket on uncaught exceptions, so close the session yourself as shown above.

Like Javalin?
Star us 😊