The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

AWS CloudWatch用のQuarkusエクステンションの作成

AWS CloudWatch用のQuarkusエクステンションの作成

最近、QuarkusのアプリケーションログをAWS CloudWatchにロギングしたいという状況になりました。基本的には時間がかかりますが、大したことではありません。CloudWatchの依存関係を追加し、Log Handlerを作成し、提供されているAWS CloudWatch APIを介してCloudWatchにログをプッシュします。しかし、それを他の人と共有したい場合はどうでしょうか?もちろん、プロジェクトの一部としてGitHubに置いて、他の人がコピー&ペーストできるようにすることもできますが、それはコードを他の人と共有する最もエレガントな方法ではありません。

そのため、私たちはQuarkusのエクステンションを実装しました。これにより、他のユーザーがより簡単にQuarkusを使用できるようになり、車輪の再発明やコピーペーストの必要がなくなりました。どうすればいいのでしょうか?ここで、Quarkiverse Hubの出番です。Quarkiverseは、開発者が自分の拡張機能をホストして他の人と共有できるGitHubの組織です。エクステンションをQuarkiverseでホスティングすることで、従来のように自分ですべての作業を行うのではなく、無料でいくつかのメリットを得ることができます。Quarkiverseを使用すると、アーティファクトをビルドし、Sonatype Nexus Manager(または類似のもの)でリリースし、Maven Centralや他のリポジトリで配布する必要がありません。Quarkiverseにはこれらがすべて付属しているので、エクステンションの実装自体に集中することができます。以下の投稿では、CloudWatch Quarkusエクステンションを初期化、実装、共有するために必要なことを説明します。

Quarkiverseを使用してエクステンションをハブで公開し(これは私たちが推奨する方法です)、それによって得られるすべての利点を利用したい場合は、単にquarkusio/quarkusのGitHub組織で新しいエクステンション提案の課題をオープンする必要があります。こうすることで、テンプレートが生成され、あなたはエクステンションのコードを実装するだけなので、ほとんどの要件はすでに満たされていることになります。既存のプロジェクトをテンプレートとして使用する場合は、いくつかの要件に注意する必要があります。エクステンションのリリースやドキュメントの公開を自動化するために、Quarkiverseの組織下にあるプロジェクトには従わなければならないルールがあります。

  • The extension repository should be named quarkus-<project>

  • A Quarkiverse extension MUST belong to the io.quarkiverse.<project> groupId

  • The root pom.xml MUST inherit from io.quarkiverse:quarkiverse-parent

  • A Quarkiverse extension contains the following folders and files:

    • deployment

    • runtime

    • integration-test

    • docs

    • pom.xml

    • LICENSE

    • README

この記事では、runtimeとdeploymentの中身のみを取り上げます。その他の中身は、オプションであったり、すでにプロジェクトテンプレートによって生成されていたり、あるいは重要であっても、Quarkusのエクステンションがどのように作成されるかを知りたい場合には、最も重要で実用的なものではありません。まず、導入セクションから始めましょう。ここには、Quarkusエクステンションの初期化に必要なクラスが含まれています。この初期化クラスがないと、Quarkusアプリケーションの起動時にエクステンションが識別されません。

class LoggingCloudwatchProcessor {

    @BuildStep
    FeatureBuildItem feature() {
        return new FeatureBuildItem("logging-cloudwatch");
    }

    @BuildStep
    @Record(ExecutionTime.RUNTIME_INIT)
    LogHandlerBuildItem addCloudwatchLogHandler(final LoggingCloudWatchConfig config,
            final LoggingCloudWatchHandlerValueFactory cloudWatchHandlerValueFactory) {
        return new LogHandlerBuildItem(cloudWatchHandlerValueFactory.create(config));
    }
}

上のスニペットでは、 _@BuildStep_でアノテーションされ、新しいFeatureBuildItemを返す _feature()_メソッドを見ることができます。このメソッドは、アプリケーションの起動時にログに表示される機能の名前(logging-cloudwatch)を公開します。2 番目のメソッド _addCloudWatchHandler()_は、LoggingCloudWatchConfig と LoggingCloudWatchHandlerValueFactory クラスによって提供されるエクステンションのランタイム構成を初期化します。幸い、LogHandlerBuildItemが提供されているので、独自の実装を追加して既存のログハンドラを上書きすることができます。他にもたくさんのBuildItemが提供されているので、独自のエクステンションを作りたい方はぜひ参考にしてみてください。このメソッドのパラメータは、以下のスニペットで説明されるコンフィグクラスです。

@ConfigRoot(phase = ConfigPhase.RUN_TIME, name = "log.cloudwatch")
public class LoggingCloudWatchConfig {

    @ConfigItem(defaultValue = "true")
    boolean enabled;

    @ConfigItem
    public String region;

    // ...
}

_LoggingCloudWatchConfig_は、エクステンション自体と、そのエクステンションを使用するQuarkusアプリケーションの間のブリッジを構築しています。このクラスは、Quarkusアプリケーションの _application.propertiesエントリ_をエクステンションと組み合わせています。つまり、このクラスでは、 _application.properties_ファイルで利用可能なプロパティを定義し、外部からエクステンションを設定できるようにしています。 _ConfigurationRoot_は _application.properties_内のプロパティのプレフィックスを、 _@ConfigItems_はポストフィックスを定義します。このクラスで受け付ける一つのapplication.propertiesエントリーは、例えばlog.cloudwatch.enabledです。

_LoggingCloudWatchConfig_の他に、 _addCloudwatchLogHandler()_メソッドのパラメータがあります。それは対応するファクトリークラスです。

@Recorder
public class LoggingCloudWatchHandlerValueFactory {

    public RuntimeValue<Optional<Handler>> create(final LoggingCloudWatchConfig config) {
        if (!config.enabled) {
            return new RuntimeValue<>(Optional.empty());
        }

        AWSLogsClientBuilder clientBuilder = AWSLogsClientBuilder.standard();
        clientBuilder.setCredentials(new CloudWatchCredentialsProvider(config));

        // …

        AWSLogs awsLogs = clientBuilder.build();

        // …

        LoggingCloudWatchHandler handler = new LoggingCloudWatchHandler(awsLogs, config.logGroup.get(),
                config.logStreamName.get(), token);
        // …

        return new RuntimeValue<>(Optional.of(handler));
    }
}

LoggingCloudWatchHandlerValueFactoryは、アプリケーションのログを扱い、これらのログをAWSに置くという、エクステンションの実際のビジネスロジックと、前述の _application.properties_ファイルの設定との間の接着剤となります。 _create()_メソッドの中で、設定項目がチェックされ、CloudWatch接続の初期化に使用されていることがわかります。

さて、 _application.properties_エントリーを追加し、エクステンション名を公開し、ログ・メッセージをAWS CloudWatchに置くために必要なAWS CloudWatchオブジェクトを作成するハンドラー・クラスに設定を提供することで、エクステンションのユーザーが設定できるようにしたので、あとは足りない部分を1つ追加するだけです。ログハンドラー自体です。上のスニペットでは、 _LoggingCloudWatchHandlerValueFactory_で、すでに作成し、 _LoggingCloudwatchProcessor_クラスで使用しているRuntimeValueとして返しています。これが、既存のデフォルトログハンドラを上書きするために必要なコールチェーンです。

class LoggingCloudWatchHandler extends Handler {

    private AWSLogs awsLogs;
    private String logStreamName;
    private String logGroupName;
    private String sequenceToken;

    // ...

    LoggingCloudWatchHandler(AWSLogs awsLogs, String logGroup, String logStreamName, String token) {
        this.logGroupName = logGroup;
        this.awsLogs = awsLogs;
        this.logStreamName = logStreamName;
        this.sequenceToken = token;
    }

    @Override
    public void publish(LogRecord record) {

        // ...

        InputLogEvent logEvent = new InputLogEvent()
                .withMessage(body)
                .withTimestamp(System.currentTimeMillis());
        awsLogs.putLogEvents(request);
    }
}

このログハンドラは、java.util.LogHandlerであり、アプリケーションでログを書き込む際に呼び出されるpublishメソッドのパラメータとしてLogRecordオブジェクトを受け取ります。例えば、log.info("I Love Open Source!"); のようになります。正しく設定されていれば、このログハンドラはログを書き込むときに呼び出されます。AWS CloudWatchにログメッセージを出力したいので、そのためのロジックを追加する必要があります。そこで、InputLogEventを作成し、ログメッセージをCloudWatchに置く _putLogEvents()_を呼び出しています。基本的にはこれで終わりです。

この記事のスニペットは少し短くなっていますが、基本的にはこのエクステンションの内容です。

要約しましょう。エクステンションを初期化するProcessorクラス、エクステンションを設定可能にするために必要なConfigurationクラス、これらの設定を受けてAWS CloudWatch接続を作成するValue Factoryクラス、そして各ログメッセージをCloudWatchにプッシュするカスタム _LogHandler_クラスがあります。

これらの作業を終えて、唯一足りないのはエクステンションのバージョンをリリースすることです。これは、 _.github_フォルダ内の _project.yml_ファイルのcurrent-versionと _next-version_エントリを更新するPull Requestを開くことで行うことができます。このプルリクエストをマージすると、いくつかのGitHubアクションが実行され、新しいバージョンがMaven Centralに導入され、最終的に他の人もこのエクステンションを使用できるようになります :-)

まとめ

このように、Quarkusのエクステンションを作成、実装、他の人と共有することは非常に簡単です。コミュニティにとって有用なエクステンションのアイデアがあれば、quarkusio/quarkus GitHub Issuesセクションに新しいエクステンション提案の課題を作成して、自由にアイデアを提案してください :-)

万が一、質問や提案などがありましたら、お気軽に Twitterでご連絡ください。

よろしくお願いいたします、ベネット