Blog Post

Apps on Azure Blog
8 MIN READ

Send signals from Micronaut applications to Azure Monitor through zero-code instrumentation

Logico_jp's avatar
Logico_jp
Icon for Microsoft rankMicrosoft
Aug 15, 2025

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.

Zero-code | OpenTelemetry

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.

GitHub - anishi1222/micronaut-telemetry-movie: Zero code instrumentation (Azure Monitor, GraalVM Native Image, and Micronaut)

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.

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

Optimizations and Performance

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.

 

Updated Aug 15, 2025
Version 2.0
No CommentsBe the first to comment