1. 概要

このチュートリアルでは、Googleが設計したJava用の流暢なロギングAPIであるFloggerフレームワークについて説明します。

2. Floggerを使用する理由

Log4jやLogbackなど、現在市場に出回っているすべてのロギングフレームワークで、なぜさらに別のロギングフレームワークが必要なのですか?

Floggerには、他のフレームワークに比べていくつかの利点があることがわかりました。見てみましょう。

2.1. 読みやすさ

FloggerのAPIの流暢な性質は、読みやすくするのに大いに役立ちます。

10回の反復ごとにメッセージをログに記録する例を見てみましょう。

従来のロギングフレームワークでは、次のようになります。

int i = 0;

// ...

if (i % 10 == 0) {
    logger.info("This log shows every 10 iterations");
    i++;
}

しかし今、Floggerを使用すると、上記を次のように簡略化できます。

logger.atInfo().every(10).log("This log shows every 10 iterations");

Floggerバージョンのロガーステートメントは従来のバージョンよりも少し冗長に見えると主張する人もいるかもしれませんが、それはより優れた機能を可能にし、最終的にはより読みやすく表現力豊かなログステートメントにつながります

2.2. パフォーマンス

ログオブジェクトでtoStringを呼び出さない限り、ログオブジェクトは最適化されます。

User user = new User();
logger.atInfo().log("The user is: %s", user);

上記のようにログを記録する場合、バックエンドにはログを最適化する機会があります。 一方、 toString を直接呼び出すか、文字列を連結すると、この機会は失われます。

logger.atInfo().log("Ths user is: %s", user.toString());
logger.atInfo().log("Ths user is: %s" + user);

2.3. 拡張性

Floggerフレームワークは、ロギングフレームワークに期待される基本的な機能のほとんどをすでにカバーしています。

ただし、機能を追加する必要がある場合があります。 このような場合、APIを拡張することが可能です。

現在、これには別のサポートクラスが必要です。たとえば、 UserLogger クラスを記述して、FloggerAPIを拡張できます。

logger.at(INFO).forUserId(id).withUsername(username).log("Message: %s", param);

これは、メッセージを一貫してフォーマットしたい場合に役立ちます。 UserLogger は、カスタムメソッド forUserId(String id)および withUsername(String username)。の実装を提供します。

これを行うには、 UserLoggerクラスがAbstractLoggerクラスを拡張し、APIの実装を提供する必要があります。 FluentLogger を見ると、これは追加のメソッドがない単なるロガーであるため、このクラスをそのままコピーすることから始めて、メソッドを追加することでこの基盤から構築することができます。 。

2.4. 効率

従来のフレームワークでは、varargsが広く使用されています。 これらのメソッドでは、メソッドを呼び出す前に、新しい Object[]を割り当てて入力する必要があります。 さらに、渡される基本的なタイプはすべて自動ボックス化する必要があります。

これはすべて、呼び出しサイトで追加のバイトコードと遅延が発生します。ログステートメントが実際に有効になっていない場合は特に残念ですこのコストは、ループします。 Floggerは、varargsを完全に回避することで、これらのコストを削減します。

Floggerは、ロギングステートメントを構築できる流暢なコールチェーンを使用することでこの問題を回避します。これにより、フレームワークは log メソッドに対して少数のオーバーライドのみを持つことができ、したがってvarargsやauto-boxingなどを回避できます。 これは、APIが組み合わせ爆発なしでさまざまな新機能に対応できることを意味します。

一般的なロギングフレームワークには、次のメソッドがあります。

level(String, Object)
level(String, Object...)

ここで、 level は、約7つのログレベル名(たとえば、 strong )のいずれかであり、追加のログレベルを受け入れる正規のログメソッドがあります。

log(Level, Object...)

これに加えて、通常、ログステートメントに関連付けられている原因( Throwable インスタンス)をとるメソッドのバリエーションがあります。

level(Throwable, String, Object)
level(Throwable, String, Object...)

APIが3つの懸念事項を1つのメソッド呼び出しに結合していることは明らかです。

  1. ログレベルを指定しようとしています(メソッドの選択)
  2. ログステートメントにメタデータを添付しようとしています(Throwable cause)
  3. また、ログメッセージと引数を指定します。

このアプローチは、これらの独立した懸念を満たすために必要なさまざまなロギング方法の数をすばやく増やします。

これで、チェーンに2つのメソッドを含めることが重要である理由がわかります。

logger.atInfo().withCause(e).log("Message: %s", arg);

それでは、コードベースでどのように使用できるかを見てみましょう。

3. 依存関係

Floggerの設定は非常に簡単です。 floggerflogger-system-backendpom:に追加するだけです。

<dependencies>
    <dependency>
        <groupId>com.google.flogger</groupId>
        <artifactId>flogger</artifactId>
        <version>0.4</version>
    </dependency>
    <dependency>
        <groupId>com.google.flogger</groupId>
        <artifactId>flogger-system-backend</artifactId>
        <version>0.4</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>

これらの依存関係を設定したら、自由に使用できるAPIの調査に進むことができます。

4. FluentAPIの探索

まず、ロガーのstaticインスタンスを宣言しましょう。

private static final FluentLogger logger = FluentLogger.forEnclosingClass();

これで、ロギングを開始できます。 簡単なことから始めましょう。

int result = 45 / 3;
logger.atInfo().log("The result is %d", result);

ログメッセージは、 %s、%d %016x など、Javaのprintf形式指定子のいずれかを使用できます。

4.1. ログサイトでの作業の回避

Floggerの作成者は、ログサイトでの作業は避けることをお勧めします。

コンポーネントの現在の状態を要約するための次の長期的な方法があるとしましょう。

public static String collectSummaries() {
    longRunningProcess();
    int items = 110;
    int s = 30;
    return String.format("%d seconds elapsed so far. %d items pending processing", s, items);
}

ログステートメントでcollectSummariesを直接呼び出したくなります。

logger.atFine().log("stats=%s", collectSummaries());

ただし、構成されたログレベルやレート制限に関係なく、collectSummariesメソッドが毎回呼び出されるようになりました。

無効化されたロギングステートメントのコストを実質的に無料にすることは、ロギングフレームワークの中核です。 つまり、これらの多くは、害を及ぼすことなくコード内にそのまま残すことができます。 先ほどのようにログステートメントを作成すると、この利点が失われます。

代わりに、LazyArgs.lazyメソッドを使用する必要があります。

logger.atFine().log("stats=%s", LazyArgs.lazy(() -> collectSummaries()));

現在、ログサイトではほとんど作業が行われていません。ラムダ式のインスタンスが作成されているだけです。 Floggerは、実際にメッセージをログに記録する場合にのみ、このラムダを評価します。

isEnabled を使用してログステートメントを保護することは許可されていますが、次のようになります。

if (logger.atFine().isEnabled()) {
    logger.atFine().log("summaries=%s", collectSummaries());
}

これは必須ではなく、Floggerがこれらのチェックを行うため、回避する必要があります。 このアプローチはまた、レベルごとにログステートメントを保護するだけであり、レート制限されたログステートメントには役立ちません。

4.2. 例外への対処

例外はどうですか、どのように処理しますか?

FloggerにはwithStackTraceメソッドが付属しており、これを使用してThrowableインスタンスをログに記録できます。

try {
    int result = 45 / 0;
} catch (RuntimeException re) {
    logger.atInfo().withStackTrace(StackSize.FULL).withCause(re).log("Message");
}

ここで、 withStackTrace は、定数値 SMALL、MEDIUM、LARGEまたはFULLを持つStackSize列挙型を引数として取ります。 withStackTrace()によって生成されたスタックトレースは、デフォルトのjava.util.loggingバックエンドでLogSiteStackTrace例外として表示されます。 ただし、他のバックエンドはこれを別の方法で処理することを選択する場合があります。

4.3. ロギング構成とレベル

これまで、ほとんどの例で logger.atInfo を使用してきましたが、Floggerは他の多くのレベルをサポートしています。 これらについて見ていきますが、最初に、ロギングオプションを構成する方法を紹介しましょう。

ロギングを構成するには、LoggerConfigクラスを使用します。

たとえば、ログレベルを FINEに設定する場合:

LoggerConfig.of(logger).setLevel(Level.FINE);

また、Floggerはさまざまなログレベルをサポートしています。

logger.atInfo().log("Info Message");
logger.atWarning().log("Warning Message");
logger.atSevere().log("Severe Message");
logger.atFine().log("Fine Message");
logger.atFiner().log("Finer Message");
logger.atFinest().log("Finest Message");
logger.atConfig().log("Config Message");

4.4. レート制限

レート制限の問題はどうですか? すべての反復をログに記録したくない場合をどのように処理しますか?

Floggerはevery(int n)メソッドで救いの手を差し伸べます

IntStream.range(0, 100).forEach(value -> {
    logger.atInfo().every(40).log("This log shows every 40 iterations => %d", value);
});

上記のコードを実行すると、次の出力が得られます。

Sep 18, 2019 5:04:02 PM com.baeldung.flogger.FloggerUnitTest lambda$givenAnInterval_shouldLogAfterEveryTInterval$0
INFO: This log shows every 40 iterations => 0 [CONTEXT ratelimit_count=40 ]
Sep 18, 2019 5:04:02 PM com.baeldung.flogger.FloggerUnitTest lambda$givenAnInterval_shouldLogAfterEveryTInterval$0
INFO: This log shows every 40 iterations => 40 [CONTEXT ratelimit_count=40 ]
Sep 18, 2019 5:04:02 PM com.baeldung.flogger.FloggerUnitTest lambda$givenAnInterval_shouldLogAfterEveryTInterval$0
INFO: This log shows every 40 iterations => 80 [CONTEXT ratelimit_count=40 ]

10秒ごとにログに記録したい場合はどうなりますか? 次に、 atMostEvery(int n、TimeUnit unit)を使用できます。

IntStream.range(0, 1_000_0000).forEach(value -> {
    logger.atInfo().atMostEvery(10, TimeUnit.SECONDS).log("This log shows [every 10 seconds] => %d", value);
});

これにより、結果は次のようになります。

Sep 18, 2019 5:08:06 PM com.baeldung.flogger.FloggerUnitTest lambda$givenATimeInterval_shouldLogAfterEveryTimeInterval$1
INFO: This log shows [every 10 seconds] => 0 [CONTEXT ratelimit_period="10 SECONDS" ]
Sep 18, 2019 5:08:16 PM com.baeldung.flogger.FloggerUnitTest lambda$givenATimeInterval_shouldLogAfterEveryTimeInterval$1
INFO: This log shows [every 10 seconds] => 3545373 [CONTEXT ratelimit_period="10 SECONDS [skipped: 3545372]" ]
Sep 18, 2019 5:08:26 PM com.baeldung.flogger.FloggerUnitTest lambda$givenATimeInterval_shouldLogAfterEveryTimeInterval$1
INFO: This log shows [every 10 seconds] => 7236301 [CONTEXT ratelimit_period="10 SECONDS [skipped: 3690927]" ]

5. 他のバックエンドでのFloggerの使用

たとえば、Slf4jやLog4jなどをすでに使用している既存のアプリケーションにFloggerを追加したい場合はどうでしょうか。 これは、既存の構成を利用したい場合に役立ちます。 これから説明するように、Floggerは複数のバックエンドをサポートします。

5.1. Slf4jを使用したFlogger

Slf4jバックエンドを構成するのは簡単です。 まず、flogger-slf4j-backend依存関係をpomに追加する必要があります。

<dependency>
    <groupId>com.google.flogger</groupId>
    <artifactId>flogger-slf4j-backend</artifactId>
    <version>0.4</version>
</dependency>

次に、デフォルトのバックエンドとは異なるバックエンドを使用することをFloggerに通知する必要があります。 これを行うには、システムプロパティを使用してFloggerファクトリを登録します。

System.setProperty(
  "flogger.backend_factory", "com.google.common.flogger.backend.slf4j.Slf4jBackendFactory#getInstance");

そして今、私たちのアプリケーションは既存の構成を使用します。

5.2. Log4jを使用したFlogger

Log4jバックエンドを構成するための同様の手順に従います。 flogger-log4j-backend依存関係をpomに追加しましょう。

<dependency>
    <groupId>com.google.flogger</groupId>
    <artifactId>flogger-log4j-backend</artifactId>
    <version>0.4</version>
    <exclusions>
        <exclusion>
            <groupId>com.sun.jmx</groupId>
            <artifactId>jmxri</artifactId>
        </exclusion>
        <exclusion>
            <groupId>com.sun.jdmk</groupId>
            <artifactId>jmxtools</artifactId>
        </exclusion>
        <exclusion>
            <groupId>javax.jms</groupId>
            <artifactId>jms</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>apache-log4j-extras</artifactId>
    <version>1.2.17</version>
</dependency>

また、Log4jのFloggerバックエンドファクトリを登録する必要があります。

System.setProperty(
  "flogger.backend_factory", "com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance");

これで、既存のLog4j構成を使用するようにアプリケーションがセットアップされました。

6. 結論

このチュートリアルでは、従来のロギングフレームワークの代わりにFloggerフレームワークを使用する方法を見てきました。 フレームワークを使用するときに恩恵を受けることができるいくつかの強力な機能を見てきました。

また、Slf4jやLog4jなどのさまざまなバックエンドを登録することで既存の構成を活用する方法も確認しました。

いつものように、このチュートリアルのソースコードはGitHubから入手できます。