コンテンツに移動
DevOps & SRE

Java アプリケーションのログからより多くの分析情報を取得

2022年3月22日
https://storage.googleapis.com/gweb-cloudblog-publish/images/Java_applications_logs.max-2600x2600.jpg
Google Cloud Japan Team

※この投稿は米国時間 2022 年 3 月 8 日に、Google Cloud blog に投稿されたものの抄訳です。

Java アプリケーションのログの取得がより一層簡単になりました。開発者は、Cloud Logging の Java 用クライアント ライブラリの新しいバージョンを使用して、アプリケーション ログからより多くのデータを取得できます。このライブラリは、取り込まれるすべてのログエントリに現在の実行コンテキストに関する情報を暗黙的に格納します。この記事を読むと、コードを 1 行も書かずに、HTTP リクエスト、HTTP トレース情報、追加のメタデータをログから取得する方法がわかります。

ログデータを Google Cloud Logging に取り込む方法には、次の 3 通りがあります。

  • Logging API を直接呼び出す独自のソリューションを開発する。

  • GKE のような Google Cloud マネージド環境のロギング機能を利用するか、Google Cloud Ops エージェントをインストールして、アプリケーション ログを stdout および stderr に出力する。

  • 目的のプログラミング言語用の Google Cloud Logging クライアント ライブラリを使用する。

このライブラリは、Logging API の使用に関するベスト プラクティスに従って作成された、すぐに使用可能なボイラープレート構造を提供します。Java アプリケーションは、Google Cloud Logging ライブラリを使用して、Java Logging および Logback フレームワークとのインテグレーションを通じてログを取り込むことができます。

Java 用 Google Logging クライアント ライブラリを初めて使用する場合は、Java 用 Cloud Logging の設定はじめにの手順に従ってください。

Java 用 Logging クライアント ライブラリのバージョン 3.6 リリースには、環境のリソースに関するメタデータの自動格納(Cloud Run や Cloud Functions をサポート)、HTTP リクエストのコンテキスト情報、ログ エクスプローラでログエントリをグループ化して表示できるようにするトレースの関連付けなど、長く待ち望まれてきた多くの機能が追加されています。このリリースのライブラリは、次の 3 つのパッケージで構成されています。

  • google-cloud-logging - Cloud Logging API の上位の手書き階層、およびレガシー Java Logging ソリューションとのインテグレーションを提供します。

  • google-cloud-logging-logback - Logback フレームワークとのインテグレーションであり、google-cloud-logging パッケージを使用してログを取り込みます。

  • google-cloud-logging-servlet-initializer - これはライブラリに新たに追加されたパッケージで、サーブレット ベースのウェブ アプリケーションとのインテグレーションを提供します。

これらの機能は、google-cloud-logging パッケージのバージョン 3.6.3 以上、および google-cloud-logging-logback パッケージのバージョン 0.123.3-alpha 以上で使用できます。

Maven を使用している場合は、pom.xml で各パッケージのバージョンを次のように更新します。

読み込んでいます...

Gradle を使用している場合は、依存関係を次のように更新します。

読み込んでいます...

これらのパッケージの新しいリリースを含む公式の Google Cloud BOM バージョン 0.167.0 を使用できます。

新機能

この Java ライブラリは、リソースタイプ、HTTP リクエストのメタデータ、トレースなどの実行環境に関する構造化された情報を挿入します。このライブラリを使用する場合、次の 3 通りのいずれかの形式でペイロードを記述できます。

  • Java 文字列として提供されたテキスト

  • Map<String, ?> または Struct のインスタンスとして提供された JSON オブジェクト

  • Any のインスタンスとして提供された protobuf オブジェクト

取り込まれた構造化ログをログ エクスプローラの高度なフィルタで絞り込むことにより、アプリケーションのモニタリングやトラブルシューティングを行うことができます。ログ エクスプローラは、構造化ログを使用してトレースとログの関連付けを確立し、同じトランザクションに属するログをグループ化します。関連付けられた「子」ログは、次のように「親」ログのエントリの下に表示されます。

https://storage.googleapis.com/gweb-cloudblog-publish/images/Grouped_logs_display_in_Logs_Explorer.max-700x700.jpg
ログ エクスプローラでのグループ化されたログの表示

以前のバージョンの Logging ライブラリでは、これらのフィールドに値を明示的に設定するコードを記述する必要がありました。たとえば、Logback フレームワークを使用する場合は、次のようなコードを記述して、取り込まれるログの trace フィールドに値を設定する必要がありました。

読み込んでいます...

さらに、各トランザクションの開始時にこのコードを呼び出す必要がありました。

新しいバージョンの Logging ライブラリでは、このような格納ロジックを実装する必要はありません。次のログエントリ フィールドの自動格納がサポートされています。

  • resource - アプリケーションが実行されているリソースタイプとその属性を表します。GCE インスタンスのほかに、GKE、AppEngine(スタンダードとフレキシブルの両方)、Cloud Run、Cloud Functions などの Google Cloud マネージド サービスがサポートされています。

  • httpRequest - 現在のアプリケーションのコンテキストから HTTP リクエストに関する情報を取得します。このコンテキストはスレッドごとに定義されており、アプリケーション コードで明示的に設定することも、Jakarta サーブレット リクエスト パイプラインから暗黙的に設定することもできます。

  • tracespanId - HTTP リクエスト ヘッダーからトレースデータを読み取ります。このトレースデータは、同じトランザクションに属する複数のログを関連付けるのに役立ちます。

  • sourceLocation - アプリケーションがログ取り込みメソッドを呼び出したコードの行番号と、クラスおよびメソッド名に関する情報を格納します。このデータは、Logging ライブラリ コードまたはシステム パッケージの一部でない最初のエントリに達するまでトレース スタックを上向きに走査することによって取得されます。

その後に開発者が行う必要があるのは、ペイロードおよび関連するペイロードのメタデータ ラベルを設定することです。現時点でライブラリによって自動的に格納されないログエントリのフィールドは、operation フィールドだけです。

ログエントリ内の情報の自動格納を無効にする

自動格納機能は開発者が完全にコントロールできます。自動格納は、その利便性を考慮してデフォルトで有効になっています。ただし、場合によっては、自動格納を無効にするほうが望ましいことがあります。たとえば、大量のログを出力するアプリケーションがあり、そのアプリケーションの帯域幅が狭い場合は、アプリケーションの通信用に接続の帯域幅を確保するため、自動格納を無効にできます。

Logging インターフェースの write() メソッドを使用してログを取り込む場合は、LoggingOptions 引数を次のように設定することで自動格納を無効にできます。

読み込んでいます...

Java Logging を使用している場合は、logging.properties ファイルに次の行を追加することで自動格納を無効にできます。

読み込んでいます...

Logback フレームワークを使用している場合は、Logback 構成に次の行を追加することで自動格納を無効にできます。

読み込んでいます...

現在のコンテキストはどのようにしてログに格納されるか

ログ エクスプローラに用意されているリッチなクエリ機能や表示機能(関連付けられたログの表示など)は、ログエントリの httpRequest や trace などのフィールドを使用します。新しいバージョンのライブラリは、Context クラスを使用して、現在のアプリケーション コンテキストに含まれる HTTP リクエストやトレースデータに関する情報を保存します。コンテキストのスコープはスレッド単位です。ログを Cloud Logging に取り込む前に、現在のコンテキストから HTTP リクエストとトレース情報が読み取られ、ログエントリ内の該当するフィールドに設定されます。これらのフィールドに自動的に値が格納されるのは、呼び出し元でこれらのフィールドの値が明示的に提供されていない場合のみに限られます。ContextHandler クラスを使用して、次のように現在のコンテキストの HTTP リクエストとトレースデータを設定できます。

読み込んでいます...

コンテキストが設定された後、そのコンテキストと同じスコープで取り込まれるすべてのログには、現在のコンテキストで設定された HTTP リクエストとトレース情報が格納されます。Context クラスでは、URL やリクエスト メソッドなどの部分的なデータを使用して HTTP リクエストを設定できます。

読み込んでいます...

Context クラスのビルダーは、Google トレース コンテキストおよび W3C トレース コンテキスト文字列の解析値からのトレース情報の設定もサポートしています。これにはそれぞれ loadCloudTraceContext() メソッドと loadW3CTraceParentContext() メソッドを使用します。

コンテキスト格納の実装は複雑なタスクです。Java ウェブサーバーは、リクエスト ハンドラの非同期実行をサポートしています。適切なスコープでコンテキストを管理するには、各ウェブサーバーの具体的な実装詳細に関する詳しい知識が必要となる場合があります。新しいバージョンの Logging ライブラリは、現在のコンテキストの管理プロセスを自動化する簡単な手段を提供し、開発者が自分でそれらのコードを実装する手間を省きます。この自動化は、Jakarta サーブレットを基にしている TomcatJettyUndertow などのすべてのウェブサーバーをサポートしています。現在の実装でサポートされている Jakarta サーブレットのバージョンは、4.0.4 以降です。この実装は、新しい google-cloud-logging-servlet-initializer パッケージに追加されています。現在のコンテキストを自動的に取得するために開発者がしなければならないことは、このパッケージをアプリケーションに追加することだけです。

Maven を使用している場合は、pom.xml に次の行を追加します。

読み込んでいます...

Gradle を使用している場合は、依存関係に次の行を追加します。

読み込んでいます...

この追加されたパッケージは、Java のサービス プロバイダ インターフェースを使用して ContextCaptureInitializer クラスを登録し、このクラスがサーブレット パイプラインに組み込まれて現在の HTTP リクエストに関する情報を取得します。次に、取得された情報が解析され、HttpRequest 構造体に値が格納されます。また、リクエストのヘッダーも解析されてトレース情報が取得されます。そこでは、x-cloud-trace-context ヘッダー(Google トレース コンテキスト)と traceparent ヘッダー(W3C トレース コンテキスト)がサポートされています。

Logging ライブラリをロギング エージェントと併用する

多くのアプリケーションは、Google Cloud マネージド サービスのロギング機能を利用します。アプリケーションはログを stdout および stderr に出力し、これらのログが Logging エージェントによって、またはロギング エージェント機能がある Cloud マネージド サービスによって Cloud Logging に取り込まれます。このアプローチには、非同期のログ処理によってアプリケーションのリソースが消費されないという利点があります。このアプローチの欠点は、アプリケーション コードで構造化ログのフィールドに値を格納する場合、または構造化ペイロードを提供する場合に、これらの出力をロギング エージェントが解析できる特別な JSON 形式に従ってフォーマットする必要があることです。また、ロギング エージェントは、マネージド環境に関するリソース情報を検出してログに格納することはできますが、その他の traceId や sourceLocation などのログエントリのフィールドを自動的に格納することはできません。

新しいリリースの Java 用 Logging ライブラリでは、Java Logging と Logback の両方のインテグレーションでロギング エージェントがサポートされるようになりました。これで、ライブラリのユーザーは該当するハンドラに対して、ログの書き込みを Logging API ではなく stdout にリダイレクトするよう指示できます。

Java Logging を使用している場合は、logging.properties ファイルに次の行を追加します。

読み込んでいます...

Logback を使用している場合は、Logback 構成に次の行を追加します。

読み込んでいます...

デフォルトでは、LoggingHandlerLoggingAppender はどちらも Logging API を呼び出してログを書き込みます。ロギング エージェントがログの取り込みに利用されるようにするには、上記の構成を追加する必要があります。

ロギング エージェントの使用に関する制限

ライブラリの Java Logging ハンドラまたは Logback アダプタを構成してログの書き込みを stdout にリダイレクトする場合は、ロギング エージェントの使用に伴う制約に注意してください。

Google Cloud マネージド サービス(GKE など)は、そのサービスがプロビジョニングするリソースにロギング エージェントを自動的にインストールします。たとえば、GKE クラスタでは、そのクラスタの各ワーカーノード(GCE インスタンス)にロギング エージェントがインストールされます。そのため、ロギング エージェントは自身が実行されるリソースについて制約を受け、取り込まれるログエントリの resource フィールドのカスタマイズをサポートできません。

また、取り込まれるすべてのログの logName はエージェントによって定義され、変更することはできません*。つまり、ログ名またはログエントリが保存される場所(ログの宛先名)をアプリケーションで定義することはできません。

カスタム リソースタイプを定義すること、またはログのルーティング先のプロジェクトまたはログ名を制御することが必須の場合は、ログの書き込みを標準出力にリダイレクトしないでください。

* (宛先ではなく)ログ名をカスタマイズすることは可能です。そのためには、GCE インスタンスで Logging エージェントの構成をカスタマイズし、ログ名を「tag」として定義します。

次のステップ

ロギング クライアントを最新バージョンにアップグレードすることの利点を以下にまとめます。

ログ エクスプローラのログ関連付け機能が必要な場合、または Cloud Logging の構造化ログを外部ソリューションに転送し、自動格納されたフィールドのデータを使用する場合は、新しい Logging ライブラリを使用します。

Jakarta サーブレットを使用するリクエスト ベースのアプリケーションを実行する場合は、google-cloud-logging-servlet-initializer パッケージを使用してコンテキスト管理を自動化します。これは、以前の Java EE サーブレット、または Java サーブレットを基にしていないウェブサーバー(Netty など)では機能しないことに注意してください。

Cloud Run や Cloud Functions のような Google Cloud サーバーレス環境でアプリケーションを実行する場合は、フォーマットされたログを標準出力にリダイレクトするように構成された Java Logging または Logback を使用することを検討してください(前のセクションの説明を参照)。ログの取り込みにロギング エージェントを利用すると、Cloud Run の CPU スロットリングや Cloud Functions の猶予期間ゼロなど、非同期ログ取り込みに関する信頼性の問題の一部が解決します。


- Developer Relations エンジニア Leonid Yankulin
投稿先