CLOVER🍀

That was when it all began.

WildFly 40のMicroProfile Telemetryサブシステムを使って、トレース・メトリクス・ログの3つのシグナルを送信する

これは、なにをしたくて書いたもの?

WildFly 40はMicroProfile 7.1の実装が含まれています。

以前はトレースとメトリクスだけを見ていたのですが、ログも送信できるようになっていたので簡単に見ておきます。

WildFly 40とMicroProfile

WildFly 40はMicroProfile 7.1に対応しています。

WildFly 40 is released!

Getting Started Guide

この中には、OpenTelemetry向けの仕様であるMicroProfile Telemetry 2.1が含まれています。

MicroProfile Telemetry

トレース・メトリクス・ログの3つのシグナルに対応しています。

MicroProfile TelemetryはOpenTelemetry SDKとインテグレーションするための仕様です。もっとも、API仕様は
ありませんが。

またWildFlyにはMicroProfile Telemetryサブシステムがあるのですが、ログに対応しているかどうかがよくわかりません
でした。

WildFly Admin Guide / Subsystem configuration / MicroProfile Telemetry Subsystem

少なくともWildFlyが使っているMicroProfile Telemetryの実装であるSmallRye Telemetryはしばらくログには対応して
いなかったのですが、2.8.0でログをサポートするようになっています。

Support OpenTelemetry Logs

Release 2.8.0 · smallrye/smallrye-opentelemetry · GitHub

では、今動かすとどうなるでしょう?と試してみたのが今回のエントリーです。今回は最低限の設定で、簡単に動かして
みようと思います。

環境

今回の環境はこちら。

$ java --version
openjdk 25.0.3 2026-04-21
OpenJDK Runtime Environment (build 25.0.3+9-2-24.04.2-Ubuntu)
OpenJDK 64-Bit Server VM (build 25.0.3+9-2-24.04.2-Ubuntu, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.16 (2bdd9fddda4b155ebf8000e807eb73fd829a51d5)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 25.0.3, vendor: Ubuntu, runtime: /usr/lib/jvm/java-25-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "6.8.0-134-generic", arch: "amd64", family: "unix"

なんとなくですが、データベースも用意しておきます。MySQLで172.17.0.2で動作しているものとします。

 MySQL  localhost:33060+ ssl  practice  SQL > select version();
+-----------+
| version() |
+-----------+
| 8.4.10    |
+-----------+
1 row in set (0.0008 sec)

またテレメトリーデータはGrafana LGTMで受け取ることにします。

$ docker container run -it --rm --name lgtm \
    -p 3000:3000 -p 4040:4040 -p 4317:4317 -p 4318:4318 -p 9090:9090 \
    -e GF_PATHS_DATA=/data/grafana \
    grafana/otel-lgtm:0.28.0

つまり、こんな感じになるようにします。

flowchart LR
    クライアント --> |curl/HTTP| A
    subgraph WildFly
      A[JAX-RS / JPA]
    end
    subgraph Database
      A -- JDBC --> M
      M[MySQL]
    end
    subgraph Telemetry
      A -- OTLP over gRPC[4317] --> B["OpenTelemetry Collector"]
      B -- Tracing --> C["Grafana Tempo"]
      B -- Logs --> D["Grafana Loki"] 
      B -- Metrics --> E["Prometheus"]
    end

準備

それではアプリケーションを作成します。

Maven依存関係など。

    <properties>
        <maven.compiler.release>25</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.wildfly.bom</groupId>
                <artifactId>wildfly-ee</artifactId>
                <version>40.0.1.Final</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.wildfly.bom</groupId>
                <artifactId>wildfly-expansion</artifactId>
                <version>40.0.1.Final</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.ws.rs</groupId>
            <artifactId>jakarta.ws.rs-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.enterprise</groupId>
            <artifactId>jakarta.enterprise.cdi-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.transaction</groupId>
            <artifactId>jakarta.transaction-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.persistence</groupId>
            <artifactId>jakarta.persistence-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.data</groupId>
            <artifactId>jakarta.data-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate.orm</groupId>
            <artifactId>hibernate-core</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.microprofile.config</groupId>
            <artifactId>microprofile-config-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-context</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.logging</groupId>
            <artifactId>jboss-logging</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>ROOT</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.15.0</version>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.hibernate.orm</groupId>
                            <artifactId>hibernate-processor</artifactId>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>3.5.6</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.wildfly.plugins</groupId>
                <artifactId>wildfly-maven-plugin</artifactId>
                <version>6.0.0.Final</version>
                <executions>
                    <execution>
                        <id>package</id>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <overwrite-provisioned-server>true</overwrite-provisioned-server>
                    <discover-provisioning-info>
                        <version>40.0.1.Final</version>
                        <layers-for-jndi>
                            <layer>microprofile-telemetry</layer>
                        </layers-for-jndi>
                        <add-ons>
                            <add-on>mysql</add-on>
                        </add-ons>
                        <suggest>true</suggest>
                    </discover-provisioning-info>
                    <env>
                        <MYSQL_DATASOURCE>MySqlDs</MYSQL_DATASOURCE>
                        <MYSQL_JNDI>java:jboss/datasources/MySqlDs</MYSQL_JNDI>
                        <MYSQL_URL>jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&amp;connectionCollation=utf8mb4_0900_bin</MYSQL_URL>
                        <MYSQL_USER>kazuhira</MYSQL_USER>
                        <MYSQL_PASSWORD>password</MYSQL_PASSWORD>
                        <MYSQL_ENABLED>true</MYSQL_ENABLED>
                    </env>
                </configuration>
            </plugin>
        </plugins>
    </build>

Jakarta RESTful Web Services、Jakarta Contexts and Dependency Injection、Jakarta Dataの組み合わせですね。

今回用意したソースコードでは、microprofile-telemetry Layerは検出対象にならなかったので自分で追加しました…。

                        <layers-for-jndi>
                            <layer>microprofile-telemetry</layer>
                        </layers-for-jndi>

MicroProfile Configのプロパティーファイル。このファイルがMicroProfile Telemetryでのポイントですが、
otel.sdk.disabledfalseにしてOpenTelemetry SDKを有効にします。

src/main/resources/META-INF/microprofile-config.properties

otel.sdk.disabled=false
otel.service.name=application

あとはサンプルコードを並べていきましょう。

Jakarta Persistenceのエンティティー。

src/main/java/org/littlewings/wildfly/telemetry/book/Book.java

package org.littlewings.wildfly.telemetry.book;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "book")
public class Book {
    @Id
    private String isbn;

    private String title;

    private Integer price;

    // getter/setter、toStringは省略
}

JPAの設定。

src/main/resources/META-INF/persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_2.xsd"
             version="3.2">
    <persistence-unit name="MyPersistenceUnit" transaction-type="JTA">
        <jta-data-source>java:jboss/datasources/MySqlDs</jta-data-source>
        <properties>
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="jakarta.persistence.schema-generation.database.action" value="drop-and-create"/>
        </properties>
    </persistence-unit>
</persistence>

Jakarta Dataのリポジトリーインターフェース。

src/main/java/org/littlewings/wildfly/telemetry/book/BookRepository.java

package org.littlewings.wildfly.telemetry.book;

import jakarta.data.repository.BasicRepository;
import jakarta.data.repository.Repository;

@Repository
public interface BookRepository extends BasicRepository<Book, String> {}

JAX-RSのリソースクラス。

src/main/java/org/littlewings/wildfly/telemetry/book/BookResource.java

package org.littlewings.wildfly.telemetry.book;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.util.List;
import org.jboss.logging.Logger;

@Path("/books")
@ApplicationScoped
@Transactional
public class BookResource {
    private Logger logger = Logger.getLogger(BookResource.class);

    @Inject
    private BookRepository bookRepository;

    @GET
    @Path("/{isbn}")
    @Produces(MediaType.APPLICATION_JSON)
    public Book findByIsbn(@PathParam("isbn") String isbn) {
        Book book = bookRepository.findById(isbn).orElse(null);
        logger.infof("find book: isbn = [%s], found book = %s", isbn, book);
        return book;
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<Book> findAll() {
        List<Book> books = bookRepository.findAll().toList();
        logger.infof("find all: %s", books);
        return books;
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Book register(Book book) {
        logger.infof("register book: %s", book);
        return bookRepository.save(book);
    }

    @PUT
    @Path("/{isbn}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public void update(@PathParam("isbn") String isbn, Book book) {
        logger.infof("update book: %s", book);
        bookRepository.save(book);
    }

    @DELETE
    @Path("/{isbn}")
    public void delete(@PathParam("isbn") String isbn) {
        logger.infof("delete book: isbn = [%s]", isbn);
        bookRepository.deleteById(isbn);
    }
}

ログ出力を入れています。

JAX-RSの有効化。

src/main/java/org/littlewings/wildfly/telemetry/RestApplication.java

package org.littlewings.wildfly.telemetry;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

@ApplicationPath("/")
public class RestApplication extends Application {}

動かしてみる

では、確認してみましょう。

WildFlyを起動。

$ mvn wildfly:dev

いくつか動かしてみます。

$ curl localhost:8080/books --json '{"isbn": "978-4774183169", "title": "パーフェクト Java EE", "price": 3520}'
{"isbn":"978-4774183169","price":3520,"title":"パーフェクト Java EE"}


$ curl localhost:8080/books --json '{"isbn": "978-4297156589", "title": "作って学ぶ Spring Boot入門", "price": 2970}'
{"isbn":"978-4297156589","price":2970,"title":"作って学ぶ Spring Boot入門"}

$ curl -s localhost:8080/books | jq
[
  {
    "isbn": "978-4297156589",
    "price": 2970,
    "title": "作って学ぶ Spring Boot入門"
  },
  {
    "isbn": "978-4774183169",
    "price": 3520,
    "title": "パーフェクト Java EE"
  }
]


$ curl localhost:8080/books/978-4774183169
{"isbn":"978-4774183169","price":3520,"title":"パーフェクト Java EE"}

$ curl -X PUT localhost:8080/books/978-4297156589 --json '{"isbn": "978-4297156589", "title": "作って学ぶ Spring Boot入門", "price": 4970}'

$ curl -X DELETE localhost:8080/books/978-4774183169

http://localhost:3000/にアクセスして、結果を見てみましょう。

トレース。

ログも入っています。

アプリケーション側だけではなくて、WildFlyのログも送信されていますね。

なにも考えずに出力しているだけなので、トレースと関連付けられているかどうか気になるところです。

トレースの一覧から

ひとつトレースを選び、クリックするとスパンが開きます。すると「Logs for this span」というボタンを押すと

対応するログに移動します。

いいですね。

メトリクスもちゃんとあります。

よいのではないでしょうか。

気になること

ちょっと気になるのは、ログのフォーマットですね。今回はデフォルトのまま使いましたが、WildFlyの場合はJSON Formatterを
使えばよいのでしょうか?

OpenTelemetry SDKを使う場合は、ロギングライブラリーのAppenderを使おうということでした。

Instrumentation ecosystem | OpenTelemetry

ログのフォーマットはこの時に使うロギングライブラリーのレイアウトに任せていた気がしますが、WildFlyの場合は
どうなんでしょうね。

Bridge Logback into OpenTelemetry(Appender Instrumentation for Logback)を使って、OpenTelemetry Collectorにログを送信する - CLOVER🍀

また、以前もWildFlyのMicroProfile Telemetryを使った時に悩んだようですが、JDBCのトレースが取れません。

たぶん、こちらを使わないといけないんでしょうけどね。

https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/v2.28.0/instrumentation/jdbc/library

3年前に扱った時もまったく同じことを言っていたようです(笑)。

WildFlyのMicroProfile Telemetryサブシステムを使って、トレースを試す - CLOVER🍀

おわりに

WildFly 40のMicroProfile Telemetryサブシステムを使って、トレース・メトリクス・ログの3つのシグナルをGrafana LGTMに
送ってみました。

今回はログが送れるかどうかを見たかったので簡単に済ませましたが、OpenTelemetryと組み合わせようと思うと
やっぱりいろいろと気になることがありますね。

3年前と同じ疑問が解決していなかったりするので、どこかでまた頑張って調べましょうか…?