The original post (Japanese) was written on 20 July 2025.
MicronautからAzure Monitorにmetricを送信したい – Logico Inside
This entry is related to the following one. Please take a look for background information.
Send signals from Micronaut native image applications to Azure Monitor | Microsoft Community Hub
Prerequisites
- Maven: 3.9.10
- JDK version 21
- Micronaut: 4.9.0 or later
The following tutorials were used as a reference.
Create a Micronaut Application to Collect Metrics and Monitor Them on Azure Monitor Metrics
Collect Metrics with Micronaut
Create an archetype
We can create an archetype using Micronaut’s CLI (mn
) or Micronaut Launch. In this entry, use application.yml
instead of application.properties
for application configuration. So, we need to specify the feature “yaml” so that we can include dependencies for using yaml.
Micronaut Launch
https://micronaut.io/launch/
$ mn create-app \
--build=maven \
--jdk=21 \
--lang=java \
--test=junit \
--features=validation,graalvm,micrometer-azure-monitor,http-client,micrometer-annotation,yaml \
dev.logicojp.micronaut.azuremonitor-metric
When using Micronaut Launch, click [FEATURES] and select the following features.
- validation
- graalvm
- micrometer-azure-monitor
- http-client
- micrometer-annotation
- yaml
After all features are selected, click [GENERATE PROJECT] and choose [Download Zip] to download an archetype in Zip file.
Implementation
In this section, we’re going to use the GDK sample code that we can find in the tutorial. The code is from the Micronaut Guides, but the database access and other parts have been taken out. We have made the following changes to the code to make it fit our needs.
a) Structure of the directory
In the GDK tutorial, folders called azure
and lib
are created, but this structure isn’t used in the standard Micronaut archetype. So, codes in both directories has now been combined.
b) Instrumentation Key
As the tutorial above and the Micronaut Micrometer documentation explain, we need to specify the Instrumentation Key. When we create an archetype using Micronaut CLI or Micronaut Launch, the configuration assuming the use of the Instrumentation Key is included in application.properties
/ application.yml
.
6.3 Azure Monitor Registry
Micronaut Micrometer
This configuration will work, but currently, Application Insights does not recommend accessing it using only the Instrumentation Key. So, it is better to modify the connection string to include the Instrumentation Key. To set it up, open the file application.properties
and enter the following information:
micronaut.metrics.export.azuremonitor.connectionString="InstrumentationKey=...."
In the case of application.yml
, we need to specify the connection string in YAML format.
micronaut:
metrics:
enabled: true
export:
azuremonitor:
enabled: true
connectionString: InstrumentationKey=....
We can also specify the environment variable MICRONAUT_METRICS_EXPORT_AZUREMONITOR_CONNECTIONSTRING
, but since this environment variable name is too long, it is better to use a shorter one. Here’s an example using AZURE_MONITOR_CONNECTION_STRING
(which is also long, if you think about it).
micronaut.metrics.export.azuremonitor.connectionString=${AZURE_MONITOR_CONNECTION_STRING}
micronaut:
metrics:
enabled: true
export:
azuremonitor:
enabled: true
connectionString: ${AZURE_MONITOR_CONNECTION_STRING}
The connection string can be specified because Micrometer, which is used internally, already supports it. We can find the AzurMonitorConfig.java file here.
AzureMonitorConfig.java
micrometer/implementations/micrometer-registry-azure-monitor/src/main/java/io/micrometer/azuremonitor/AzureMonitorConfig.java at main · micrometer-metrics/micrometer
The settings in application.properties
/application.yml
are as follows. For more information about the specified meter binders, please look at the following documents.
Meter Binder
Micronaut Micrometer
micronaut:
application:
name: azuremonitor-metric
metrics:
enabled: true
binders:
files:
enabled: true
jdbc:
enabled: true
jvm:
enabled: true
logback:
enabled: true
processor:
enabled: true
uptime:
enabled: true
web:
enabled: true
export:
azuremonitor:
enabled: true
step: PT1M
connectionString: ${AZURE_MONITOR_CONNECTION_STRING}
c) pom.xml
To use the GraalVM Reachability Metadata Repository, you need to add this dependency. The latest version is 0.11.0 as of 20 July, 2025.
<dependency>
<groupid>org.graalvm.buildtools</groupid>
<artifactid>graalvm-reachability-metadata</artifactid>
<version>0.11.0</version>
</dependency>
Add the GraalVM Maven plugin and enable the use of GraalVM Reachability Metadata obtained from the above dependency. This plugin lets us set optimization levels using buildArg (in this example, the optimisation level is specified). We can also add it to native-image.properties
, the native-image
tool (and the Maven/Gradle plugin) will read it.
<plugin>
<groupid>org.graalvm.buildtools</groupid>
<artifactid>native-maven-plugin</artifactid>
<configuration>
<metadatarepository>
<enabled>true</enabled>
</metadatarepository>
<buildargs combine.children="append">
<buildarg>-Ob</buildarg>
</buildargs>
<quickbuild>true</quickbuild>
</configuration>
</plugin>
For now, let’s build it as a Java application.
$ mvn clean package
Check if it works as a Java application
At first, verify that the application is running without any problems and that metrics are being sent to Application Insights. Then, run the application using the Tracing Agent to generate the necessary configuration files.
# (1) Collect configuration files such as reflect-config.json
$JAVA_HOME/bin/java \
-agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/{groupId}/{artifactId}/ \
-jar ./target/{artifactId}-{version}.jar
# (2)-a Generate a trace file
$JAVA_HOME/bin/java \
-agentlib:native-image-agent=trace-output=/path/to/trace-file.json \
-jar ./target/{artifactId}-{version}.jar
# (2)-b Generate a reachability metadata file from the collected trace file
native-image-configure generate \
--trace-input=/path/to/trace-file.json \
--output-dir=/path/to/config-dir/
Configure Native Image with the Tracing Agent
Collect Metadata with the Tracing Agent
The following files are stored in the specific directory.
jni-config.json
reflect-config.json
proxy-config.json
resource-config.json
reachability-metadata.json
These files can be located at src/main/resources/META-INF/native-image
. The native-image
tool picks up configuration files located in the directory src/main/resources/META-INF/native-image
. However, it is recommended that we place the files in subdirectories divided by groupId
and artifactId
, as shown below.
src/main/resources/META-INF/native-image/{groupId}/{artifactId}
native-image.properties
When creating a native image, we call the following command.
mvn package -Dpackaging=native-image
We should specify the timing of class initialization (build time or runtime), the command line options for the native-image
tool (the same command line options work in Maven/Gradle plugin), and the JVM arguments in the native-image.properties
file. Indeed, these settings can be specified in pom.xml
, but it is recommended that they be externalized.
a) Location of configuration files:
As described in the documentation, we can specify the location of configuration property files. If we build using the recommended method (placing the files in the directory src/main/resources/META-INF/native-image/{groupId}/{artifactId}
), we can specify the directory location using ${.}.
-H:DynamicProxyConfigurationResources
-H:JNIConfigurationResources
-H:ReflectionConfigurationResources
-H:ResourceConfigurationResources
-H:SerializationConfigurationResources
Native Image Build Configuration
b) HTTP/HTTPS protocols support:
We need to use --enable-https
/--enable-http
when using the HTTP(S) protocol in your application.
c) When classes are loaded and initialized:
In the case of AOT compilation, classes are usually loaded at compile time and stored in the image heap (at build time). However, some classes might be specified to be loaded when the program is running. In these cases, it is necessary to explicitly specify initialization at runtime (and vice versa, of course). There are two types of build arguments.
# Explicitly specify initialisation at runtime
--initialize-at-run-time=...
# Explicitly specify initialisation at build time
--initialize-at-build-time=...
To enable tracing of class initialization, use the following arguments.
# Enable tracing of class initialization
--trace-class-initialization=... # Deprecated in GraalVM 21.3
--trace-object-instantiation=... # Current option
Specify Class Initialization Explicitly
Class Initialization in Native Image
d) Prevent fallback builds:
If the application cannot be optimized during the Native Image build, the native-image tool will create a fallback file, which needs JVM. To prevent fallback builds, we need to specify the option --no-fallback
. For other build options, please look at the following document.
Build a Native Image application
Building a native image application takes a long time (though it has got quicker over time). If building it for testing purpose, we strongly recommend enabling Quick Build and setting the optimization level to -Ob
option (although this will still take time). See below for more information.
Maven plugin for GraalVM Native Image
Gradle plugin for GraalVM Native Image
Test as a native image application
When you start the native image application, we might see the following message: This message means that GC notifications are not available because GarbageCollectorMXBean
of JVM does not provide any notifications.
GC notifications will not be available because no GarbageCollectorMXBean of the JVM provides any. GCs=[young generation scavenger, complete scavenger]
Let’s check if the application works.
1) GET /books and GET /books/{isbn}
This is a normal REST API. Call both of them a few times.
2) GET /metrics
We can check the list of available metrics.
{
"names": [
"books.find",
"books.index",
"executor",
"executor.active",
"executor.completed",
"executor.pool.core",
"executor.pool.max",
"executor.pool.size",
"executor.queue.remaining",
"executor.queued",
"http.server.requests",
"jvm.classes.loaded",
"jvm.classes.unloaded",
"jvm.memory.committed",
"jvm.memory.max",
"jvm.memory.used",
"jvm.threads.daemon",
"jvm.threads.live",
"jvm.threads.peak",
"jvm.threads.started",
"jvm.threads.states",
"logback.events",
"microserviceBooksNumber.checks",
"microserviceBooksNumber.latest",
"microserviceBooksNumber.time",
"process.cpu.usage",
"process.files.max",
"process.files.open",
"process.start.time",
"process.uptime",
"system.cpu.count",
"system.cpu.usage",
"system.load.average.1m"
]
}
At first, the following three metrics are custom ones added in the class MicroserviceBooksNumberService
.
microserviceBooksNumber.checks
microserviceBooksNumber.time
microserviceBooksNumber.latest
And, the following two metrics are custom ones collected in the class BooksController
, which collect information such as the time taken and the number of calls. Each metric can be viewed at GET /metrics/{metric name}
.
books.find
books.index
The following is an example of microserviceBooksNumber.*
.
// miroserviceBooksNumber.checks
{
"name": "microserviceBooksNumber.checks",
"measurements": [
{
"statistic": "COUNT",
"value": 12
}
]
}
// microserviceBooksNumber.time
{
"name": "microserviceBooksNumber.time",
"measurements": [
{
"statistic": "COUNT",
"value": 12
},
{
"statistic": "TOTAL_TIME",
"value": 0.212468
},
{
"statistic": "MAX",
"value": 0.032744
}
],
"baseUnit": "seconds"
}
//microserviceBooksNumber.latest
{
"name": "microserviceBooksNumber.latest",
"measurements": [
{
"statistic": "VALUE",
"value": 2
}
]
}
Here is an example of the metric books.*
.
// books.index
{
"name": "books.index",
"measurements": [
{
"statistic": "COUNT",
"value": 6
},
{
"statistic": "TOTAL_TIME",
"value": 3.08425
},
{
"statistic": "MAX",
"value": 3.02097
}
],
"availableTags": [
{
"tag": "exception",
"values": [
"none"
]
}
],
"baseUnit": "seconds"
}
// books.find
{
"name": "books.find",
"measurements": [
{
"statistic": "COUNT",
"value": 7
}
],
"availableTags": [
{
"tag": "result",
"values": [
"success"
]
},
{
"tag": "exception",
"values": [
"none"
]
}
]
}
Metrics from Azure Monitor (application insights)
Here is the grid view of custom metrics in Application Insights (microserviceBooks.time
is the average value).
To confirm that the values match those in Application Insights, check the metric http.server.requests
, for example. We should see three items on the graph and the value is equal to the number of API responses (3).