Anotheria
Howtos

Adding MoSKito to a Spring Boot application

How to add MoSKito to a Spring Boot application

Adding MoSKito to a Spring Boot application

Every time we start a new Spring Boot project, the same question comes up sooner or later — usually right after the first production incident: "What is the application actually doing?" Metrics endpoints are nice, but what we really want is to see every service and every endpoint, with requests, errors and average execution times, historical charts, thresholds that turn red before the customer calls, and a single dashboard that shows the state of all our services at once. In other words: we want MoSKito.

In this post we'll walk through the complete integration of MoSKito into a Spring Boot application, using one of our own services as the example. There are three parts to it:

  1. Integrating MoSKito into the application itself (dependencies, moskito.json, @Monitor).
  2. Making the application visible to the outside world (MoSKito Control agent via Spring Actuator, remote port for MoSKito Inspect).
  3. Running MoSKito Inspect and MoSKito Control in Docker next to the application, connected over a shared Docker network.

At the end you'll have a single-app deep-dive console (Inspect) and a multi-instance monitoring dashboard (Control), all running locally in Docker.

Step 1 — Add the dependencies

Our example application runs on Spring Boot 4 with Java 21, but the setup is the same for older versions. Four dependencies go into the pom.xml:

<dependency>
    <groupId>net.anotheria</groupId>
    <artifactId>moskito-springboot2</artifactId>
    <version>4.0.9</version>
</dependency>
<dependency>
    <groupId>net.anotheria</groupId>
    <artifactId>moskito-aop</artifactId>
    <version>4.0.9</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>org.moskito</groupId>
    <artifactId>moskito-control-agent-spring-boot-starter</artifactId>
    <version>4.1.0</version>
</dependency>

What each one does:

  • moskito-springboot2 is the MoSKito Spring Boot starter. It bootstraps MoSKito inside the application and opens a remote connection port so MoSKito Inspect can talk to the app.
  • moskito-aop provides the @Monitor annotation and the AOP magic that turns your classes into producers.
  • spring-boot-starter-actuator is the standard Spring Actuator — we need it as the carrier for the next dependency.
  • moskito-control-agent-spring-boot-starter adds a moskitocontrol actuator endpoint, which MoSKito Control will poll to determine the health status of the application.

Step 2 — Add a moskito.json

MoSKito is configured with a single file, moskito.json, which goes into src/main/resources. It configures everything: accumulators (which values are collected into charts), thresholds (when to turn yellow, orange, red or — heaven forbid — purple), gauges, dashboards, tracing, error catching and so on.

The full file is rather long, so here are the parts you'll most likely want to adjust. The nicest trick in it are auto accumulators: instead of declaring an accumulator for every single class, we declare a pattern. Every producer whose name ends in ServiceImpl (or Endpoint) automatically gets accumulators for requests, errors, total time and average time, in 1m, 5m and 1h intervals:

"@accumulatorsConfig": {
  "accumulationAmount": 250,
  "@autoAccumulators": [
    {
      "namePattern": "$PRODUCERNAME.REQ.1m",
      "producerNamePattern": "(.*)ServiceImpl",
      "statName": "cumulated",
      "valueName": "req",
      "intervalName": "1m"
    },
    {
      "namePattern": "$PRODUCERNAME.AVG.1m",
      "producerNamePattern": "(.*)ServiceImpl",
      "statName": "cumulated",
      "valueName": "avg",
      "intervalName": "1m"
    }
    // ... same for err and time, and for (.*)Endpoint, in 1m/5m/1h
  ]
}

Thresholds define when your application changes its status. A classic example is CPU load:

{
  "name": "SystemCPULoad",
  "producerName": "OS",
  "statName": "OS",
  "valueName": "SystemCPULoad",
  "intervalName": "default",
  "@guards": [
    { "value": "50.0", "direction": "DOWN", "status": "GREEN"  },
    { "value": "50.0", "direction": "UP",   "status": "YELLOW" },
    { "value": "75.0", "direction": "UP",   "status": "ORANGE" },
    { "value": "90.0", "direction": "UP",   "status": "RED"    },
    { "value": "99.0", "direction": "UP",   "status": "PURPLE" }
  ]
}

These threshold statuses are exactly what MoSKito Control will aggregate across all your applications later — so the effort you put in here pays off twice.

And dashboards tie it all together. Again, patterns save us from listing every class by hand — this dashboard automatically picks up charts for every *ServiceImpl in the system:

{
  "name": "Services",
  "@chartPatterns": [
    { "caption": "REQ 1m", "accumulatorPatterns": [ "(.*)ServiceImpl.REQ.1m" ] },
    { "caption": "AVG 1m", "accumulatorPatterns": [ "(.*)ServiceImpl.AVG.1m" ] }
  ],
  "@producerNamePatterns": [ "(.*)ServiceImpl" ]
}

A complete moskito.json example is available in the MoSKito documentation; the one from our project also configures gauges, tracing, error catchers and tagging.

Step 3 — Annotate the classes you want to monitor

Now to the fun part. To turn a class into a MoSKito producer, annotate it with @Monitor:

import net.anotheria.moskito.aop.annotation.Monitor;

@Service
@Monitor(category = "service", subsystem = "todo")
public class TodoServiceImpl implements TodoService {
    ...
}

And the same for the REST endpoints:

@RestController
@Monitor(category = "endpoint", subsystem = "todo")
public class TodoEndpoint {
    ...
}

That's it. Every public method of the class is now monitored — requests, errors, execution times, min/max/average, in all intervals. The category and subsystem attributes are used for grouping in the Inspect UI, so a consistent naming convention (we use service and endpoint) makes navigation much more pleasant. And since our class names end in ServiceImpl and Endpoint, the auto accumulators and dashboard patterns from Step 2 pick them up automatically — annotate a new class and it appears on the dashboard, no further configuration needed.

Step 4 — Open the doors: application.yml

Now the application is monitoring itself, but nobody can look inside yet. Two doors need to be opened: one for MoSKito Inspect and one for MoSKito Control. Both are configured in the application.yml:

server:
  port: 8080

management:
  server:
    port: 8081
  endpoints:
    web:
      exposure:
        include: health,moskitocontrol

moskito:
  remote-port: 9250

What's happening here:

  • moskito.remote-port: 9250 — the MoSKito starter opens a remote connection port on 9250. This is the port MoSKito Inspect will connect to in order to browse producers, journeys, threads and everything else.
  • management.server.port: 8081 — we run the actuator endpoints on a separate management port. This way the monitoring endpoints are never exposed through the same port as your public API — in production, 8080 goes through the load balancer and 8081 stays internal.
  • management.endpoints.web.exposure.include: health,moskitocontrol — this exposes the moskitocontrol actuator endpoint provided by the control agent starter. MoSKito Control will poll it at http://<host>:8081/actuator/moskitocontrol and receive the aggregated threshold status of the application.

Step 5 — Set up the Docker network

Our applications run in Docker, and so will Inspect and Control. For them to talk to each other, they need a shared Docker network — that way Inspect can reach the application's port 9250 and Control can reach the management port 8081 without publishing either of them to the host.

docker network create nordwind

Then start your application on this network, with a well-known name:

docker run -d --name dealflow-api --network nordwind -p 8080:8080 nordwind/dealflow-api

Note that we only publish port 8080 (the actual API) to the host. Ports 9250 and 8081 remain reachable inside the nordwind network only, under the hostname dealflow-api — which is exactly how the Inspect and Control configurations will refer to the application. Monitoring ports never leave the Docker network; this is intended.

Step 6 — Run MoSKito Inspect

MoSKito Inspect is the console for deep-diving into a single application: producers, accumulator charts, thresholds, journeys, now-running requests. Since our application only embeds the MoSKito agent (no embedded UI), we run Inspect as its own Docker container and connect it to the applications remotely.

Inspect is configured with a moskito-inspect.json. The interesting part is the @remotes section, which lists all the applications this Inspect instance can connect to — using the Docker container names and the remote port from Step 4:

{
  "connectivityMode": "LOCAL",
  "@remotes": [
    { "name": "Dealflow",     "host": "dealflow-api", "port": 9250 },
    { "name": "Fisher",       "host": "fisher-api",   "port": 9250 },
    { "name": "Affretrieval", "host": "affr-api",     "port": 9250 }
  ]
}

Put this file into a local configuration directory (say nordwind/moskito-inspect-configuration/) and start the container:

docker run -d -p8088:8000 \
  -v `pwd`/nordwind/moskito-inspect-configuration:/app/moskito-inspect/moskito-inspect-configuration \
  --name inspect --network nordwind \
  anotheria/moskito-inspect:latest

The container joins the same nordwind network as the applications, and the UI is published to the host on port 8088. Open http://localhost:8088, pick a remote from the list, and enjoy browsing your producers.

Step 7 — Run MoSKito Control

Inspect is great for looking into one application, but once you have more than one service you want a single traffic-light view over all of them: which components are green, which are red, and what the important business charts look like. That's MoSKito Control.

Control is configured with a moskitocontrol.json. Its heart is the @components section — one entry per monitored application, this time using the HTTP connector against the actuator endpoint from Step 4:

{
  "@components": [
    {
      "name": "Dealflow-API",
      "category": "api",
      "connectorType": "HTTP",
      "location": "dealflow-api:8081",
      "agentPath": "actuator/moskitocontrol"
    },
    {
      "name": "Fisher-API",
      "category": "api",
      "connectorType": "HTTP",
      "location": "fisher-api:8081",
      "agentPath": "actuator/moskitocontrol"
    }
  ],
  "@views": [
    { "name": "All", "@componentCategories": ["*"], "@charts": ["*"], "@widgets": ["*"] }
  ],
  "@charts": [
    {
      "name": "CPU",
      "limit": 100,
      "@lines": [ { "component": "*", "accumulator": "CPU Time 1m" } ]
    }
  ]
}

Every component points to the management port (8081) of the respective application inside the Docker network, and agentPath tells Control where the actuator endpoint lives. The status you see per component is the aggregated worst-case of all the thresholds we configured in Step 2 — one red threshold and the component turns red. Charts can pull any accumulator from any component, so you can put the CPU time of all your services on one chart, or build business dashboards from your own accumulators.

Start it the same way as Inspect, on the same network:

docker run -d -p8888:8999 \
  -v `pwd`/nordwind/moskito-control-configuration:/app/moskito-control/moskito-control-configuration \
  -v `pwd`/nordwind/moskito-control-logs:/app/moskito-control/logs \
  --name control --network nordwind \
  anotheria/moskito-control:latest

Open http://localhost:8888 and you'll see all your components with their current status, updated every 10 seconds.

Wrapping up

Let's recap what we did:

  1. Added moskito-springboot2, moskito-aop, the Spring actuator and the moskito-control-agent-spring-boot-starter to the application.
  2. Dropped a moskito.json into src/main/resources with auto accumulators, thresholds and pattern-based dashboards.
  3. Annotated services and endpoints with @Monitor.
  4. Opened port 9250 for Inspect and exposed the moskitocontrol actuator endpoint on the management port 8081.
  5. Created a shared Docker network so the monitoring containers can reach the application ports without exposing them to the world.
  6. Started anotheria/moskito-inspect for the deep dive into a single app.
  7. Started anotheria/moskito-control for the traffic-light overview over all apps.

The beauty of this setup is that it scales with you: adding a new service to the monitoring is just the four dependencies, a moskito.json, a couple of @Monitor annotations and two new lines in the Inspect and Control configurations. Everything else — accumulators, dashboards, status aggregation — happens by convention.

MoSKito is open source and available from www.moskito.org. If you are new to MoSKito, also check out the Integration Guide and our older Step by Step Integration series. Happy monitoring!