The original post (Japanese) was written on 29 July 2025.
MicronautからAzure Monitorにlogを送信したい – 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
Where can we post logs?
Log destination differs depending upon managed services such as App Service, Container Apps, etc. We can also send logs to specified destination which is different destination from the default one. In the case of Azure Container Apps, for instance, we have several options to send logs.
Type | Destination | How to |
Write console output to a log |
If diagnostic settings are configured, destination table may differ from the above. | The output destination can be changed in the diagnostic settings. This is handled by Container Apps, so no user action is required. |
Use DCE (Data Collection Endpoint) to write logs to custom table in Log Analytics Workspace | Custom tables in Log Analytics Workspace. |
Follow these tutorials listed below.
|
Using the Log Appender | traces table in Application Insights | When writing logs to thetraces table in Application Insights, Log Appender configuration is required. |
Log storage and monitoring options in Azure Container Apps
From now on, we elaborate the 3rd way — write logs to the traces table in Application Insights.
Prerequisites
- Maven: 3.9.10
- JDK: 21
- Micronaut: 4.9.0 or later
Regarding logs, the logs posted with the following 4 log libraries are automatically collected. In this entry, we use Logback.
- Log4j2
- Logback
- JBoss Logging
- java.util.logging
Create Azure resource (Application Insights)
Create a resource group and configure Application Insights. Refer to the following documentation for details.
Create and configure Application Insights resources - Azure Monitor
That’s it for the Azure setup.
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.
mn create-app \
--build=maven \
--jdk=21 \
--lang=java \
--test=junit \
--features=graalvm,azure-tracing,yaml \
dev.logicojp.micronaut.azuremonitor-log
When using Micronaut Launch, click [FEATURES] and select the following features.
- graalvm
- azure-tracing
- yaml
After all features are selected, click [GENERATE PROJECT] and choose [Download Zip] to download an archetype in Zip file.
Add dependencies and plugins to pom.xml
In order to output logs to Application Insights, the following dependencies must be added.
<dependency>
<groupid>io.opentelemetry.instrumentation</groupid>
<artifactid>opentelemetry-logback-appender-1.0</artifactid>
</dependency>
<dependency>
<groupid>com.microsoft.azure</groupid>
<artifactid>applicationinsights-logging-logback</artifactid>
</dependency>
<dependency>
<groupid>io.micronaut.tracing</groupid>
<artifactid>micronaut-tracing-opentelemetry-http</artifactid>
</dependency>
In this entry, we are using Logback for log output, so we are using opentelemetry-logback-appender-1.0. However, should you be using a different library, it will be necessary to specify the appropriate an appender for that library.
The dependency com.azure:azure-monitor-opentelemetry-autoconfigure
is being included transitively since io.micronaut.tracing:azure-tracing
depends upon the dependency. If Azure tracing has not yet been added, the following dependencies must be added explicitly.
<dependency>
<groupid>com.azure</groupid>
<artifactid>azure-monitor-opentelemetry-autoconfigure</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 29 July, 2025.
<dependency>
<groupid>org.graalvm.buildtools</groupid>
<artifactid>graalvm-reachability-metadata</artifactid>
<version>0.11.0</version>
<classifier>repository</classifier>
<type>zip</type>
</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
In order to proceed, it is necessary to include both Application Insights-specific settings and Azure-tracing settings. To ensure optimal performance when using Azure tracing, please refer to the settings outlined below.
Send traces from Micronaut native image applications to Azure Monitor | Microsoft Community Hub
For Application Insights-specific settings, please refer to the documentation provided.
Configuration options - Azure Monitor Application Insights for Java - Azure Monitor
According to the documentation, when specifying a connection string, the configuration should be as follows.
You can also set the connection string by using the environment variable
APPLICATIONINSIGHTS_CONNECTION_STRING
. It then takes precedence over the connection string specified in the JSON configuration.Or you can set the connection string by using the Java system property
applicationinsights.connection.string
. It also takes precedence over the connection string specified in the JSON configuration.
Initially, it may appear that there is no alternative but to use environment variables or Java system properties. However, in the case of Micronaut (and similarly for Spring Boot and Quarkus), the connection string can be configured using the relationship between application settings and environment variables. This allows for defining it in application.properties
or application.yml
.
For instance, in the case of the connection string mentioned above, if we specify it using an environment variable, we would use APPLICATIONINSIGHTS_CONNECTION_STRING
. In Micronaut, we can specify it as shown in lines 5–7 of the following application.yml
example (the key matches the one used when setting it as a system property).
The configuration of application.yml, including Application Insights-specific settings, is as follows:
applicationinsights:
connection:
string: ${AZURE_MONITOR_CONNECTION_STRING}
sampling:
percentage: 100
instrumentation:
logging:
level: "INFO"
preview:
captureLogbackMarker: true
captureControllerSpans: true
azure:
tracing:
connection-string: ${AZURE_MONITOR_CONNECTION_STRING}
Codes
a) To enable Application Insights
We need to explicitly create an OpenTelemetry
object to send logs. Please note that while Azure-tracing enables Application Insights, the OpenTelemetry
object generated during this process is not publicly accessible and cannot be retrieved from outside.
AutoConfiguredOpenTelemetrySdkBuilder sdkBuilder = AutoConfiguredOpenTelemetrySdk.builder();
OpenTelemetry openTelemetry = sdkBuilder.build().getOpenTelemetrySdk();
AzureMonitorAutoConfigure.customize(sdkBuilder, "connectionString");
b) Log Appender
When we create the archetype, src/main/resources/logback.xml
should be generated. In this file, add an Appender to associate with the io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender
class object.
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level)
%magenta(%logger{36}) - %msg%n
</pattern>
</encoder>
</appender>
<appender name="OpenTelemetry" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
<captureexperimentalattributes>true</captureexperimentalattributes>
<capturecodeattributes>true</capturecodeattributes>
<capturemarkerattribute>true</capturemarkerattribute>
<capturekeyvaluepairattributes>true</capturekeyvaluepairattributes>
<capturemdcattributes>*</capturemdcattributes>
</appender>
<root level="info">
<appender-ref ref="STDOUT">
<appender-ref ref="OpenTelemetry">
</appender-ref></appender-ref></root>
</configuration>
Then, associate the OpenTelemetry
object we created earlier with Log Appender so that logs can be sent using OpenTelemetry.
OpenTelemetryAppender.install(openTelemetry);
c) Other implementation
The objective of this article is to verify the Trace and Trace log. To that end, we will develop a rudimentary REST API, akin to a “Hello World” application. However, we will utilize the logger feature to generate multiple logs. In a real-world application, we would likely refine this process to avoid generating excessive logs.
For example, HelloController.java
is shown below.
package dev.logicojp.micronaut;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.*;
import io.micronaut.http.exceptions.HttpStatusException;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.scheduling.annotation.ExecuteOn;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Controller("/api/hello")
@ExecuteOn(TaskExecutors.IO)
public class HelloController {
private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
public HelloController(OpenTelemetry _openTelemetry){
OpenTelemetryAppender.install(_openTelemetry);
logger.info("OpenTelemetry is configured and ready to use.");
}
@Get
@Produces(MediaType.APPLICATION_JSON)
public GreetingResponse hello(@QueryValue(value = "name", defaultValue = "World") String name) {
logger.info("Hello endpoint was called with query parameter: {}", name);
// Simulate some processing
HelloService helloService = new HelloService();
GreetingResponse greetingResponse = helloService.greet(name);
logger.info("Processing complete, returning response");
return greetingResponse;
}
@Post
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Status(HttpStatus.ACCEPTED)
public void setGreetingPrefix(@Body GreetingPrefix greetingPrefix) {
String prefix = greetingPrefix.prefix();
if (prefix == null || prefix.isBlank()) {
logger.error("Received request to set an empty or null greeting prefix.");
throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Prefix cannot be null or empty");
}
HelloService helloService = new HelloService();
helloService.setGreetingPrefix(prefix);
logger.info("Greeting prefix set to: {}", prefix);
}
}
For now, let’s build it as a Java application.
mvn clean package
Test as a Java application
Please verify that the application is running without any issues …
- that traces are being sent to Application Insights
- that logs are being sent to the
traces
table - that they can be confirmed on the Trace screen.
If the call is GET /api/hello?name=Logico_jp, the traces
table will look like this:
In the Trace application, it should resemble this structure, in conjunction with the Request.
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
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
Verify that this application works the same as a normal Java application. For example, call GET /api/hello?name=xxxx, GET /api/hello?name=, GET /api/hello , and POST /api/hello.
Check if traces and logs are visible in Azure Monitor (application insights)
When reviewing the traces table in Application Insights, it becomes evident that four records were added at 3:14 p.m.
When checking traces…
As can be seen in the traces
table, the logs have indeed been added to the trace. Naturally, the occurrence times remain consistent.
Summary
I have outlined the process of writing to the traces table in Application Insights. However, it should be noted that some code is necessary to configure the Log Appender. Consequently, zero code instrumentation cannot be achieved strictly. However, the actual configuration is relatively minor, so implementation is not difficult.