Expose Java application metrics using Micrometer

Learn how to create a Quarkus application that uses Micrometer to expose metrics.

Let’s start by creating a simple Quarkus application exposing some metrics.

Note: We provide a final container image with all of the code, so you can skip the application creation if you are more interested in the observability part.

Go to https://code.quarkus.io/ and generate a new Quarkus application with RESTEasy Reactive, Container Image Jib, and Micrometer Registry Prometheus. Click the link to get a pre-populated page with all dependencies. Figure 1 shows the Quarkus generator page.

A Quarkus generator page with the required extensions.
Figure 1: Quarkus generator page with required extensions.

Now click the Generate your application button, download the zip file locally, and unzip it. With just the Micrometer extension, some JVM-specific metrics are automatically exposed.

Let’s add some custom metrics—for example, the value of a counter that is incremented using a REST endpoint.

Modify the application code

Open the code in your IDE and edit the org.acme.GreetingResource class with the following content:

// This is the main Micrometer class to register custom metrics
    private final MeterRegistry registry;
    // The data to monitor
    private AtomicInteger currentMemory;
    // Injects the Micrometer registry
    GreetingResource(MeterRegistry registry) {
        this.registry = registry;
        // Registers this metric under current.memory name, initializing the counter to 0
        currentMemory = this.registry.gauge("current.memory", Tags.empty(), new AtomicInteger(0));
    }
    // Creates an endpoint to modify the observed variable 
    @GET
    @Path("/consume/{amount}")
    @Produces(MediaType.TEXT_PLAIN)
    public Integer consume(@PathParam("amount") int mem) {
        this.currentMemory.addAndGet(mem);
        return this.currentMemory.get();
    }

Of course, you need to import these classes too:

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import jakarta.ws.rs.PathParam;

Configure Jib

The final step is configuring Jib to push the container image to the registry. In this case, I used my quay.io account, but you should add your own configuration values.

Open the application.properties file and add the following values:

# Change with your values
quarkus.container-image.group=lordofthejars
quarkus.container-image.registry=quay.io
quarkus.container-image.tag=1.0.0

By default, Quarkus maps the extra endpoints, like the metrics one, to the /q subpath, so to get the metrics, you should query the /q/metrics endpoint. Because Prometheus expects the metrics published in /metrics by default, let’s reconfigure Quarkus not to use the subpath:

quarkus.http.non-application-root-path=/

Now you can build, containerize, and publish the application. Again, you can skip this step if you like, as an image is available at https://quay.io/lordofthejars/quarkus-monitor:1.0.0.

Kubernitize

It’s time to create a Kubernetes deployment file for this application. Create a deployment.yamlfile with the following content:

---
apiVersion: v1
kind: Service
metadata:
  annotations:
    app.quarkus.io/build-timestamp: 2023-06-30 - 12:45:38 +0000
    prometheus.io/scrape: "true"
    prometheus.io/path: /metrics
    prometheus.io/port: "8080"
    prometheus.io/scheme: http
  labels:
    app: monitor
    app.kubernetes.io/name: quarkus-monitor
  name: quarkus-monitor
spec:
  ports:
    - name: http
      port: 8080
      protocol: TCP
      targetPort: 8080
  selector:
    app: monitor
    app.kubernetes.io/name: quarkus-monitor
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    app.quarkus.io/build-timestamp: 2023-06-30 - 12:45:38 +0000
    prometheus.io/scrape: "true"
    prometheus.io/path: /metrics
    prometheus.io/port: "8080"
    prometheus.io/scheme: http
  labels:
    app.kubernetes.io/name: quarkus-monitor
    app: monitor
  name: quarkus-monitor
spec:
  replicas: 1
  selector:
    matchLabels:
      app: monitor
      app.kubernetes.io/name: quarkus-monitor
  template:
    metadata:
      annotations:
        app.quarkus.io/build-timestamp: 2023-06-30 - 12:45:38 +0000
        prometheus.io/scrape: "true"
        prometheus.io/path: /metrics
        prometheus.io/port: "8080"
        prometheus.io/scheme: http
      labels:
        app.kubernetes.io/name: quarkus-monitor
        app: monitor
    spec:
      containers:
        - env:
            - name: KUBERNETES_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          image: quay.io/lordofthejars/quarkus-monitor:1.0.0
          imagePullPolicy: Always
          name: quarkus-monitor
          ports:
            - containerPort: 8080
              name: http
              protocol: TCP

It has nothing special except for the labels. As you’ll see later, we use labels to configure Prometheus, specify which services it should target, and expose the metrics in the /metrics endpoint. In this case, the app=monitor label is essential because every service containing this label will be targeted.

Congratulations. You have created your Quarkus application and configured it. Now it's time to work in the Developer Sandbox and deploy a Prometheus instance.

Previous resource
Overview: Expose Java application metrics using Micrometer
Next resource
Deploy the Prometheus instance in the Developer Sandbox