为 AWS CloudWatch 创建 Quarkus 扩展

创建 AWS CloudWatch 的 Quarkus 扩展

我们最近遇到了一个需求,希望将 Quarkus 应用程序的日志记录到 AWS CloudWatch。基本来说,这需要一些时间,但并非难事。添加 CloudWatch 依赖项,创建一个 Log Handler,并通过提供的 AWS CloudWatch API 将日志推送到 CloudWatch。但如果您想与他人共享呢?当然,您可以将其放在 GitHub 上作为您项目的一部分,这样其他人就可以复制粘贴,但这并不是与他人共享代码的最优雅的方式。

因此,我们实现了一个 Quarkus 扩展,以便其他人可以更轻松地使用它,而无需重新发明轮子或复制代码。我该怎么做?这正是 Quarkiverse Hub 发挥作用的地方。Quarkiverse 是一个 GitHub 组织,开发人员可以在其中托管和共享他们的扩展。当您在 Quarkiverse 中托管扩展时,您将免费获得许多好处,而不是像以前那样独自完成所有事情。通过使用 Quarkiverse,您无需构建 artifact,使用 Sonatype Nexus Manager(或类似工具)发布它,并在 Maven Central 和其他存储库上分发。Quarkiverse 提供了所有这些功能,因此您可以专注于实现扩展本身。接下来的文章将描述初始化、实现和共享 CloudWatch Quarkus 扩展所需的步骤。

如果您想使用 Quarkiverse 将您的扩展发布到 Hub(我们推荐这样做)并利用其所有优势,您只需在 quarkusio/quarkus GitHub 组织中打开一个新的扩展提案 issue。通过这样做,大多数要求已经满足,因为会为您生成一个模板,您只需实现您的扩展代码即可。如果您使用现有项目作为模板,则需要注意一些要求。为了自动化扩展发布和文档发布,Quarkiverse 组织下的项目需要遵循一些规则

  • 扩展存储库的名称应为 quarkus-<project>

  • Quarkiverse 扩展必须属于 io.quarkiverse.<project> groupId

  • 根 pom.xml必须继承自 io.quarkiverse:quarkiverse-parent

  • Quarkiverse 扩展包含以下文件夹和文件

    • deployment

    • runtime

    • integration-test

    • 文档

    • pom.xml

    • LICENSE

    • README

本文仅涵盖运行时和部署内容,因为其他内容是可选的、已由项目模板生成,或者虽然重要但并不是学习如何创建 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)。第二个方法 addCloudWatchHandler() 初始化了 LoggingCloudWatchConfig 和 LoggingCloudWatchHandlerValueFactory 类提供的扩展运行时配置。幸运的是,提供了一个 LogHandlerBuildItem,因此我们可以通过添加我们自己的实现来覆盖现有的日志处理程序。还提供了许多其他 BuildItems,因此如果您想创建自己的扩展,绝对值得一看。此方法的参数是一个将在下一个代码片段中介绍的配置类。

@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 文件中可用的属性,并使扩展可以从外部配置。@ConfigRoot 定义了 application.properties 中属性的 prefix,而 @ConfigItems 是 postfix。我们通过此类接受的一个 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 的处理程序类,使扩展可供扩展用户配置,我们只需要添加最后一块缺失的拼图:Log Handler 本身。在上面的代码片段中,在 LoggingCloudWatchHandlerValueFactory 中,我们已经创建了它,并将其作为 RuntimeValue 返回,我们在 LoggingCloudwatchProcessor 类中使用它。这就是覆盖现有默认日志处理程序所需的调用链。

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,它将 LogRecord 对象作为 publish 方法的参数,该方法在应用程序中写入日志时将被调用。例如 log.info("I Love Open Source!");。如果配置正确,在写入日志时将调用此日志处理程序。由于我们想将日志消息推送到 AWS CloudWatch,我们需要添加执行此操作的逻辑。因此,我们创建一个 InputLogEvent 并调用 putLogEvents(),后者将日志消息推送到 CloudWatch。基本上就是这样。

本文中的代码片段有所删减,但基本上扩展就包含这些内容。

总结一下:有一个处理器类用于初始化扩展,一个配置类用于使扩展可配置,一个值工厂类用于获取这些配置并创建 AWS CloudWatch 连接,以及一个自定义的 LogHandler 类,用于将每条日志消息推送到 CloudWatch。

完成所有这些之后,唯一需要做的就是发布扩展版本。这可以通过打开一个 Pull Request 来完成,该 Pull Request 会更新 .github 文件夹中 project.yml 文件中的 current-versionnext-version 条目。合并此 Pull Request 后,一些 GitHub Actions 将被触发,它们会将您的新版本带到 Maven Central,最终其他人也可以使用您的扩展了 :-)

总结

如您所见,创建、实现和与他人共享 Quarkus 扩展实际上非常容易。因此,如果您有一个可能对社区有用的扩展想法,请随时通过在 quarkusio/quarkus GitHub Issues 部分创建一个新的扩展提案 issue 来提出您的想法 :-)

如果您有任何问题、建议或其他事项,请随时通过 Twitter 与我联系。

此致,Bennet