The original post (Japanese) was written on 13 August 2025.
Zero code instrumentationでMicronautアプリケーションからAzure Monitorにtraceやmetricを送信したい – Logico Inside
This entry is a series posts below. Please take a look for background information.
Send signals from Micronaut native image applications to Azure Monitor | Microsoft Community Hub
Received another question from the customer.
I understand that I can get metrics and traces, but is it possible to send them to Azure Monitor (Application Insights) without using code?
If you are not familiar with zero-code instrumentation, please check the following URL.
The customer wondered if the dependencies would take care of everything else when they only specified the dependencies and destinations. To confirm this (and to provide a sample), we have prepared the following environment. As I wrote in the previous post, logs are dealt with in a different way depending on how they are used (IaaS, PaaS, etc.), so they are not included in this example.
This example is a REST API application that can be used to find, add, change, and delete movie information. It uses PostgreSQL as a data store and sends information about how the system is performing to Azure Monitor, specifically Application Insights. You can find the code below.
Prerequisites
- Maven: 3.9.10
- JDK: 21
- Micronaut: 4.9.0 or later
And we need to provision an instance of Azure Monitor (application insights) and PostgreSQL Flexible Server.
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.
The following features are needed when creating an archetype for this app.
- graalvm
- management
- micrometer-azure-monitor
- azure-tracing
- yaml
- validation
- postgres
- jdbc-hikari
- data-jpa
Dependencies
The basics of sending traces and metrics are as described in the previous two entries. In this post, we want to obtain traces for HTTP and JDBC connections, so we will add the following two dependencies.
<dependency>
<groupid>io.micronaut.tracing</groupid>
<artifactid>micronaut-tracing-opentelemetry-http</artifactid>
</dependency>
<dependency>
<groupid>io.micronaut.tracing</groupid>
<artifactid>micronaut-tracing-opentelemetry-jdbc</artifactid>
</dependency>
Additionally, we need to add this dependency to use the GraalVM Reachability Metadata Repository. The latest version is 0.11.0 as of 13 August, 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>
Application configuration
This app connects to a database and Azure Monitor, so
we need the following information.
- Database where the app connects.
- Azure Monitor related information.
1) Database
We specify data source information in application.yml
.
2) Azure Monitor
Set the connection string for Application Insights. Because of dependency issues, it is necessary to set different locations for Metric and Trace, which is a bit inconvenient. However, it is recommended to pass it via environment variables to make it as common as possible.
Here is the sample of application.yml
.
micronaut:
application:
name: micronaut-telemetry-movie
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}
datasources:
default:
driver-class-name: org.postgresql.Driver
db-type: postgres
url: ${JDBC_URL}
username: ${JDBC_USERNAME}
password: ${JDBC_PASSWORD}
dialect: POSTGRES
schema-generate: CREATE_DROP
hikari:
connection-test-query: SELECT 1
connection-init-sql: SELECT 1
connection-timeout: 10000
idle-timeout: 30000
auto-commit: true
leak-detection-threshold: 2000
maximum-pool-size: 10
max-lifetime: 60000
transaction-isolation: TRANSACTION_READ_COMMITTED
azure:
tracing:
connection-string: ${AZURE_MONITOR_CONNECTION_STRING}
otel:
exclusions: /health, /info, /metrics, /actuator/health, /actuator/info, /actuator/metrics
For now, let’s build it as a Java application.
Test as a Java application
Make sure the application is running smoothly, that traces are being sent to Application Insights, and that metrics are being output. Now, run the application using the Tracing Agent and create 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
Make the following files in the specified folder.
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.
This is also explained in the metric entry, so some details will be left out. If needed, please check the metric entry.
Send metrics from Micronaut native image applications to Azure Monitor | Microsoft Community Hub
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
Let’s check if the application works. At first, we have to populate initial data with the following command. This command adds 3 records.
curl -X PUT https://<container apps="" url="">/api/movies</container>
{
"message":"Database initialized with default movies."
}
Now let’s verify if three records exist.
curl https://<container apps="" url="">/api/movies</container>
[
{
"id": 1,
"title": "Inception",
"releaseYear": 2010,
"directors": "Christopher Nolan",
"actors": "Leonardo DiCaprio, Joseph Gordon-Levitt, Elliot Page"
},
{
"id": 2,
"title": "The Shawshank Redemption",
"releaseYear": 1994,
"directors": "Frank Darabont",
"actors": "Tim Robbins, Morgan Freeman, Bob Gunton"
},
{
"id": 3,
"title": "The Godfather",
"releaseYear": 1972,
"directors": "Francis Ford Coppola",
"actors": "Marlon Brando, Al Pacino, James Caan"
}
]
(1) Azure Monitor (Application Insights)
We should see the images like this.
(2) Metrics
We can see which metrics we can check with the API call GET /metrics.
{
"names": [
"executor",
"executor.active",
"executor.completed",
"executor.pool.core",
"executor.pool.max",
"executor.pool.size",
"executor.queue.remaining",
"executor.queued",
"hikaricp.connections",
"hikaricp.connections.acquire",
"hikaricp.connections.active",
"hikaricp.connections.creation",
"hikaricp.connections.idle",
"hikaricp.connections.max",
"hikaricp.connections.min",
"hikaricp.connections.pending",
"hikaricp.connections.timeout",
"hikaricp.connections.usage",
"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",
"process.cpu.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"
]
}
But because this is a native image application, we can’t get the right information about the JVM. For example, if we invoke the API with GET /metrics/jvm.memory.max, we will see the following. What does -2 mean?
{
"name": "jvm.memory.max",
"measurements": [
{
"statistic": "VALUE",
"value": -2.0
}
],
"availableTags": [
{
"tag": "area",
"values": [
"nonheap"
]
},
{
"tag": "id",
"values": [
"runtime code cache (native metadata)",
"runtime code cache (code and data)"
]
}
],
"description": "The maximum amount of memory in bytes that can be used for memory management",
"baseUnit": "bytes"
}
To find out how much the CPU is being used, run GET /metrics/process.cpu.usage, and we’ll get this result.
{
"name": "process.cpu.usage",
"measurements": [
{
"statistic": "VALUE",
"value": 0.0017692156477295067
}
],
"description": "The \"recent cpu usage\" for the Java Virtual Machine process"
}
To add logs to Azure Monitor “traces” table...
Some of you might want to use the information in the following entry with zero-code instrumentation, but currently you cannot.
Send logs from Micronaut native image applications to Azure Monitor | Microsoft Community Hub
This is because we cannot get the OpenTelemetry
object needed to write to the Application Insights traces
table. Therefore, it must be explicitly declared. The following example clearly states and sets up Appender
in the MovieController
constructor. The way Appender
is set up is not included here, as it was explained before.
@Inject
AzureTracingConfigurationProperties azureTracingConfigurationProperties;
private static final Logger logger = LoggerFactory.getLogger(MovieController.class);
public MovieController(AzureTracingConfigurationProperties azureTracingConfigurationProperties) {
this.azureTracingConfigurationProperties = azureTracingConfigurationProperties;
AutoConfiguredOpenTelemetrySdkBuilder sdkBuilder = AutoConfiguredOpenTelemetrySdk.builder();
AzureMonitorAutoConfigure.customize(sdkBuilder, azureTracingConfigurationProperties.getConnectionString());
OpenTelemetryAppender.install(sdkBuilder.build().getOpenTelemetrySdk());
logger.info("OpenTelemetry configured for MovieController.");
}
Although explicit declaration is required, logs will be recorded in Traces as long as this setting is enabled.