MicronautからAzure Monitorにmetricを送信したい

このエントリは2025/07/20現在の情報に基づいています。将来の機能追加や変更に伴い、記載事項からの乖離が発生する可能性があります。

このエントリは、以下のエントリに関連するもので、背景などはこちらを参照のこと。

準備するもの

利用したツールは以下。

  • Maven: 3.9.10
  • JDK: 21
  • Micronaut: 4.9.1

参考にしたチュートリアルは以下。

Create a Micronaut Application to Collect Metrics and Monitor Them on Azure Monitor Metrics
https://graal.cloud/gdk/gdk-modules/metrics/micronaut-metrics-azure/
Collect Metrics with Micronaut
https://guides.micronaut.io/latest/micronaut-metrics-maven-java.html

Archetypeの作成

MicronautのCLI (mn) もしくはMicronaut Launch(Webサイト)でarchetypeを作成する。今回はアプリケーション設定でapplication.propertiesではなく、application.ymlを使う。そのため、yamlを使うための依存関係を取り込めるよう、featureとして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

Micronaut Launchを使う場合、【FEATURES】をクリックして、

  • validation
  • graalvm
  • micrometer-azure-monitor
  • http-client
  • micrometer-annotation
  • yaml

を選択した後、【GENERATE PROJECT】をクリックして【Download Zip】を選択すると、Zip形式でarchetypeを取得できる。

実装

実装コードはGDKのサンプルコードをそのまま使う(実体はMicronaut Guidesのコードから、データベースアクセスなどを省いたもの)が、以下の点を変更している。

a) ディレクトリ構成

GDKのチュートリアルでは、azurelibというディレクトリが作成されるが、通常のMicronautのarchetypeではこのような構成にならない。そのため、両ディレクトリに存在するコードはマージしておく。

b) Instrumentation Keyの設定

上記チュートリアルにも、そしてMicronaut Micrometerの以下のドキュメントにも、Instrumentation Keyの指定が必要との記載がある。Micronaut CLIやMicronaut Launchでarchetypeを作成すると、Instrumentation Keyを使用する前提の設定がapplication.properties/application.ymlに入っている。

6.3 Azure Monitor Registry
https://micronaut-projects.github.io/micronaut-micrometer/5.12.0/guide/#metricsAndReportersAzureMonitor

もちろんこれでも動作はするが、Application Insightsは現在、Instrumentation Keyだけを使ったアクセスは非推奨なので、接続文字列を指定するように変更しておくべきである。application.propertiesでは、以下のように設定する。

micronaut.metrics.export.azuremonitor.connectionString="InstrumentationKey=...."

application.ymlであればYAML形式で。

micronaut:
  metrics:
    enabled: true
    export:
      azuremonitor:
        enabled: true
        connectionString: InstrumentationKey=....

もちろん環境変数 MICRONAUT_METRICS_EXPORT_AZUREMONITOR_CONNECTIONSTRING でも指定できるが、環境変数名が長いので、短い環境変数を使うのが吉。以下はAZURE_MONITOR_CONNECTION_STRINGを使っている例(これも長いと言えば長いが)。

micronaut.metrics.export.azuremonitor.connectionString=${AZURE_MONITOR_CONNECTION_STRING}
micronaut:
  metrics:
    enabled: true
    export:
      azuremonitor:
        enabled: true
        connectionString: ${AZURE_MONITOR_CONNECTION_STRING}

connectionStringの指定が可能なのは、内部で使っているMicrometerで対応済みだから。実装(AzureMonitorConfig.java)はこちら。

https://github.com/micrometer-metrics/micrometer/blob/main/implementations/micrometer-registry-azure-monitor/src/main/java/io/micrometer/azuremonitor/AzureMonitorConfig.java

今回application.properties/application.ymlで指定した設定は以下の通り。指定したmeter binderの詳細は以下のドキュメントを参照のこと。

Meter binder
https://micronaut-projects.github.io/micronaut-micrometer/latest/guide/#_meter_binder

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の変更

GraalVM Reachability Metadata Repositoryを利用できるよう、以下の依存関係を追加する。2025/07/20現在の最新は0.11.0。

<dependency>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>graalvm-reachability-metadata</artifactId>
    <version>0.11.0</version>
    <classifier>repository</classifier>
    <type>zip</type>
</dependency>

GraalVM maven pluginを追加し、上記依存関係で取得したGraalVM Reachability Metadataを利用できるようにしておく。このpluginでは、buildArgを使って最適化レベルなども設定できる(この例では最適化レベルを指定)。もちろん、native-image.propertiesに追加すれば、native-imageツール(Maven/Gradle pluginも同じ)が読み取ってくれる。

<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>

これでひとまずJavaアプリケーションとしてビルドしておく。

mvn clean package

Javaアプリケーションとしての動作確認

問題なくアプリケーションが動作し、Application Insightsにmetricが送信されていることを確認する。このタイミングで、Tracing Agentを使ってアプリケーションを実行し、必要な構成ファイルを生成しておく。

# (1) 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 trace fileを生成
$JAVA_HOME/bin/java -agentlib:native-image-agent=trace-output=/path/to/trace-file.json -jar ./target/{artifactId}-{version}.jar
# (2)-b 作成したtrace fileからartifact固有のreachability metadataを生成
native-image-configure generate --trace-input=/path/to/trace-file.json --output-dir=/path/to/config-dir/

Configure Native Image with the Tracing Agent
https://www.graalvm.org/latest/reference-manual/native-image/guides/configure-with-tracing-agent/
Collecting Metadata with the Tracing Agent
https://www.graalvm.org/latest/reference-manual/native-image/metadata/AutomaticMetadataCollection/

以下のファイルを所定のディレクトリに作成する。

ファイルjni-config.json
reflect-config.json
proxy-config.json
resource-config.json
reachability-metadata.json
場所src/main/resources/META-INF/native-image
native-imageツールは上記ディレクトリ以下にある構成ファイルを拾い出すが、以下のようにgroupId、artifactIdでディレクトリを切ったサブディレクトリにファイルを配置してビルドすることが推奨されている。
src/main/resources/META-INF/native-image/{groupId}/{artifactId}

Native Image作成時の設定

mvn package -Dpackaging=native-image

でNative Imageを作成するが、クラスの初期化タイミング(ビルド時、実行時)やnative-imageツール(Maven plugin)へのコマンドラインオプション、JVM引数をnative-image.propertiesに設定する。pom.xml内にも記載できるが、こうした設定は外部化しておくことを推奨。

a) 各種構成ファイルの在処

ドキュメントに記載の通り、Dynamic Proxy、Reflectionなどの構成プロパティファイルの在処を指定できる。上記の推奨に従って同じディレクトリ(src/main/resources/META-INF/native-image/{groupId}/{artifactId})に入れてビルドする場合、ディレクトリの場所として${.}を使ってnative-image.properties内で指定できる。

  • -H:DynamicProxyConfigurationResources
  • -H:JNIConfigurationResources
  • -H:ReflectionConfigurationResources
  • -H:ResourceConfigurationResources
  • -H:SerializationConfigurationResources

Embed a Configuration File
https://www.graalvm.org/latest/reference-manual/native-image/overview/BuildConfiguration/#embed-a-configuration-file

b) HTTP/HTTPSプロトコルのサポート

--enable-https/--enable-httpは、アプリケーションでHTTP(S)プロトコルを使う場合には必須である。

URL Protocols in Native Image
https://www.graalvm.org/latest/reference-manual/native-image/dynamic-features/URLProtocols/

c) クラスロードのタイミング

AOTコンパイルでは、コンパイル時にクラスをロードし、イメージヒープに保持する(ビルド時)挙動が通常だが、クラスによっては実行時にロードすることを指定している場合がある。その場合は実行時の初期化を明示的に指定する必要がある(もちろんその逆も然り)。その際のビルド引数が以下の2種類。

# 実行時の初期化を明示的に指定
--initialize-at-run-time=...

# ビルド時の初期化を明示的に指定
--initialize-at-build-time=...

クラス初期化のトレースを有効化するには以下のビルド引数を使う。

# クラス初期化のトレース有効化
# GraalVM 21.3でdeprecated
--trace-class-initialization=...

# 現行のオプション
--trace-object-instantiation=... 

Specify Class Initialization Explicitly
https://www.graalvm.org/latest/reference-manual/native-image/guides/specify-class-initialization/
Class Initialization in Native Image
https://www.graalvm.org/latest/reference-manual/native-image/optimizations-and-performance/ClassInitialization/

d) fallbackビルドを抑止する

Native Imageのビルド時にアプリケーションを最適化できない場合、JVMが必要なfallbackファイルを生成してしまうが、これではただのJavaアプリケーションと何も変わらないので、可能な限り

--no-fallback

というオプションをビルド引数として指定しておく。

その他のビルドオプションは以下を参照。

Build Options
https://www.graalvm.org/latest/reference-manual/native-image/overview/Options/#build-options

Native Imageのビルド

ビルドには時間を要するので、開発環境かつテストであれば、Quick Buildを有効にして、最適化レベルを-Obにすることを推奨する(それでも時間はかかる)。

Native Image Options
https://graalvm.github.io/native-build-tools/latest/maven-plugin.html#native-image-options
https://graalvm.github.io/native-build-tools/latest/gradle-plugin.html#native-image-options
Optimization Levels
https://www.graalvm.org/latest/reference-manual/native-image/optimizations-and-performance/#optimization-levels

Native Imageとしての動作確認

Native Imageの場合、以下のメッセージが起動時に出る場合がある。これはGarbageCollectorMXBeanが通知を提供しないため、GC通知は利用できない、というメッセージ。

GC notifications will not be available because no GarbageCollectorMXBean of the JVM provides any. GCs=[young generation scavenger, complete scavenger]

これを踏まえた上で、実際に動作確認する。

1) GET /booksGET /books/{isbn}

これは通常のREST API。両方を何度か呼び出しておく。

2) GET /metrics

取得できる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"
  ]
}

まず、

  • microserviceBooksNumber.checks
  • microserviceBooksNumber.time
  • microserviceBooksNumber.latest

は、MicroserviceBooksNumberServiceクラス内で追加されたカスタムメトリック。そして

  • books.find
  • books.index

はBookControllerクラス内で取得したカスタムメトリックで、呼び出しに要した時間や件数などを収集している。各metricは、GET /metrics/{metric名称}で確認できる。以下は 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
    }
  ]
}

以下は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"
      ]
    }
  ]
}

Azure Monitor (application insights) から確認

Application InsightsでカスタムメトリックをそれぞれGrid表示(microserviceBooks.timeは平均値)したものがこちら。

Application Insightsの値と一致していることを確認するため、たとえばhttp.server.requestsというメトリックを確認すると、グラフ上は3件で、APIの応答も3件であることがわかる。

{
  "name": "http.server.requests",
  "measurements": [
    {
      "statistic": "COUNT",
      "value": 3
    },
    ...
  ],
  ...
}

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください