Chapter 7. Develop Micrometer application for JBoss EAP
7.1. Integrating Micrometer metrics in JBoss EAP
Using Micrometer, you can monitor and collect application metrics in JBoss EAP. Micrometer support provides the exposure of application metrics. The export process is PUSH-based, ensuring that metrics are sent to an OpenTelemetry Collector.
Prerequisites
- You have installed JDK 17.
- You have installed the Maven 3.6 or later version. For more information, see Downloading Apache Maven.
- You have installed Docker. For more information, see Get Docker.
- Optional: You have podman installed on your system. Use the latest podman version available on supported RHEL. For more information, see Red Hat JBoss Enterprise Application Platform 8.0 Supported Configurations.
-
The
configure-micrometer.cli
file is available in the application root directory.
The example in this section, including how to use the configure-micrometer.cli
file, is based on the Micrometer Quickstart.
Procedure
- Open a terminal.
Start JBoss EAP as a standalone server by using the following script:
Copy to Clipboard Copied! Toggle word wrap Toggle overflow <EAP_HOME>/bin/standalone.sh -c standalone-microprofile.xml
$ <EAP_HOME>/bin/standalone.sh -c standalone-microprofile.xml
NoteFor Windows server, use the
<EAP_HOME>\bin\standalone.bat
script.- Open a new terminal.
- Navigate to the application root directory.
Run the following command to configure the server:
Copy to Clipboard Copied! Toggle word wrap Toggle overflow <EAP_HOME>/bin/jboss-cli.sh --connect --file=configure-micrometer.cli
$ <EAP_HOME>/bin/jboss-cli.sh --connect --file=configure-micrometer.cli
NoteFor Windows server, use the
<EAP_HOME>\bin\jboss-cli.bat
script.Replace <EAP_HOME> with the path to your server.
Expected output:
Copy to Clipboard Copied! Toggle word wrap Toggle overflow The batch executed successfully process-state: reload-required
The batch executed successfully process-state: reload-required
Reload the server with the following management command:
Copy to Clipboard Copied! Toggle word wrap Toggle overflow <EAP_HOME>/bin/jboss-cli.sh --connect --commands=reload
$ <EAP_HOME>/bin/jboss-cli.sh --connect --commands=reload
Create a configuration file named
docker-compose.yaml
with the following content:Copy to Clipboard Copied! Toggle word wrap Toggle overflow version: "3" services: otel-collector: image: otel/opentelemetry-collector command: [--config=/etc/otel-collector-config.yaml] volumes: - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml:Z ports: - 1888:1888 # pprof extension - 8888:8888 # Prometheus metrics exposed by the collector - 8889:8889 # Prometheus exporter metrics - 13133:13133 # health_check extension - 4317:4317 # OTLP gRPC receiver - 4318:4318 # OTLP http receiver - 55679:55679 # zpages extension - 1234:1234 # /metrics endpoint
version: "3" services: otel-collector: image: otel/opentelemetry-collector command: [--config=/etc/otel-collector-config.yaml] volumes: - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml:Z ports: - 1888:1888 # pprof extension - 8888:8888 # Prometheus metrics exposed by the collector - 8889:8889 # Prometheus exporter metrics - 13133:13133 # health_check extension - 4317:4317 # OTLP gRPC receiver - 4318:4318 # OTLP http receiver - 55679:55679 # zpages extension - 1234:1234 # /metrics endpoint
Create a configuration file named
otel-collector-config.yaml
with the following content:Copy to Clipboard Copied! Toggle word wrap Toggle overflow extensions: health_check: pprof: endpoint: 0.0.0.0:1777 zpages: endpoint: 0.0.0.0:55679 receivers: otlp: protocols: grpc: http: processors: batch: exporters: prometheus: endpoint: "0.0.0.0:1234" service: pipelines: metrics: receivers: [otlp] processors: [batch] exporters: [prometheus] extensions: [health_check, pprof, zpages]
extensions: health_check: pprof: endpoint: 0.0.0.0:1777 zpages: endpoint: 0.0.0.0:55679 receivers: otlp: protocols: grpc: http: processors: batch: exporters: prometheus: endpoint: "0.0.0.0:1234" service: pipelines: metrics: receivers: [otlp] processors: [batch] exporters: [prometheus] extensions: [health_check, pprof, zpages]
Start the collector server instance by running the following command:
Copy to Clipboard Copied! Toggle word wrap Toggle overflow docker-compose up
$ docker-compose up
NoteYou can also use Podman instead of Docker. If you choose Podman, then use the
$ podman-compose up
command instead of$ docker-compose up
. If Docker or Podman is not supported in your environment, then see Otel Collector documentation for guidance on installing and running the OpenTelemetry Collector.In the
RootResource
class, see how theMeterRegistry
is injected into your class to ensure proper setup before registering the meters.Copy to Clipboard Copied! Toggle word wrap Toggle overflow @Path("/") @ApplicationScoped public class RootResource { // ... @Inject private MeterRegistry registry; private Counter performCheckCounter; private Counter originalCounter; private Counter duplicatedCounter; @PostConstruct private void createMeters() { Gauge.builder("prime.highestSoFar", () -> highestPrimeNumberSoFar) .description("Highest prime number so far.") .register(registry); performCheckCounter = Counter .builder("prime.performedChecks") .description("How many prime checks have been performed.") .register(registry); originalCounter = Counter .builder("prime.duplicatedCounter") .tags(List.of(Tag.of("type", "original"))) .register(registry); duplicatedCounter = Counter .builder("prime.duplicatedCounter") .tags(List.of(Tag.of("type", "copy"))) .register(registry); } // ... }
@Path("/") @ApplicationScoped public class RootResource { // ... @Inject private MeterRegistry registry; private Counter performCheckCounter; private Counter originalCounter; private Counter duplicatedCounter; @PostConstruct private void createMeters() { Gauge.builder("prime.highestSoFar", () -> highestPrimeNumberSoFar) .description("Highest prime number so far.") .register(registry); performCheckCounter = Counter .builder("prime.performedChecks") .description("How many prime checks have been performed.") .register(registry); originalCounter = Counter .builder("prime.duplicatedCounter") .tags(List.of(Tag.of("type", "original"))) .register(registry); duplicatedCounter = Counter .builder("prime.duplicatedCounter") .tags(List.of(Tag.of("type", "copy"))) .register(registry); } // ... }
Inspect the
checkIfPrime()
method body to see how to use the registered meters within your application logic. For example:Copy to Clipboard Copied! Toggle word wrap Toggle overflow @GET @Path("/prime/{number}") public String checkIfPrime(@PathParam("number") long number) throws Exception { performCheckCounter.increment(); Timer timer = registry.timer("prime.timer"); return timer.recordCallable(() -> { if (number < 1) { return "Only natural numbers can be prime numbers."; } if (number == 1) { return "1 is not prime."; } if (number == 2) { return "2 is prime."; } if (number % 2 == 0) { return number + " is not prime, it is divisible by 2."; } for (int i = 3; i < Math.floor(Math.sqrt(number)) + 1; i = i + 2) { try { Thread.sleep(10); } catch (InterruptedException e) { // } if (number % i == 0) { return number + " is not prime, is divisible by " + i + "."; } } if (number > highestPrimeNumberSoFar) { highestPrimeNumberSoFar = number; } return number + " is prime."; }); }
@GET @Path("/prime/{number}") public String checkIfPrime(@PathParam("number") long number) throws Exception { performCheckCounter.increment(); Timer timer = registry.timer("prime.timer"); return timer.recordCallable(() -> { if (number < 1) { return "Only natural numbers can be prime numbers."; } if (number == 1) { return "1 is not prime."; } if (number == 2) { return "2 is prime."; } if (number % 2 == 0) { return number + " is not prime, it is divisible by 2."; } for (int i = 3; i < Math.floor(Math.sqrt(number)) + 1; i = i + 2) { try { Thread.sleep(10); } catch (InterruptedException e) { // } if (number % i == 0) { return number + " is not prime, is divisible by " + i + "."; } } if (number > highestPrimeNumberSoFar) { highestPrimeNumberSoFar = number; } return number + " is prime."; }); }
Navigate to the application root directory.
Syntax
Copy to Clipboard Copied! Toggle word wrap Toggle overflow cd <path_to_application_root>/<application_root>
$ cd <path_to_application_root>/<application_root>
Example, in reference to the Micrometer Quickstart:
Copy to Clipboard Copied! Toggle word wrap Toggle overflow cd ~/quickstarts/micrometer
$ cd ~/quickstarts/micrometer
Compile and deploy the application with the following command:
Copy to Clipboard Copied! Toggle word wrap Toggle overflow mvn clean package wildfly:deploy
$ mvn clean package wildfly:deploy
This deploys micrometer/target/micrometer.war
to the running server.
Verification
Access the application by using a web browser or you can run the following command.
Copy to Clipboard Copied! Toggle word wrap Toggle overflow curl http://localhost:8080/micrometer/prime/13
$ curl http://localhost:8080/micrometer/prime/13
Expected output:
Copy to Clipboard Copied! Toggle word wrap Toggle overflow 13 is prime.
13 is prime.