ロギングを第 2 世代 Java ランタイムに移行する

第 2 世代のランタイム(Java 11 以降)のロギングには、第 1 世代のランタイム(Java 8)とは異なる側面があります。第 1 世代のランタイムとは異なり、第 2 世代のランタイムでは、アプリログがログ エクスプローラ内のリクエストログの下にグループ化されません。また、第 2 世代ランタイムでは、App Engine がリクエストに関連付けられた各アプリログを個別のログエントリとして Cloud Logging に書き込みます。

このガイドでは、第 1 世代のランタイム アプリと同じロギング メソッドを実装し、アプリを Java 11 以降に移行する際に、ほぼ同じフィルタリングとロギングの相関結果を得る方法について説明します。

主な違い

次の表に、第 1 世代のランタイムと第 2 世代のランタイムのロギングの違いを示します。

第 1 世代ランタイム(Java 8) 第 2 世代のランタイム(Java 11 以降)
リクエストログとアプリログ(アプリケーション ログとも呼ばれます) App Engine では、リクエストログ内にすべてのアプリログが埋め込まれています。 App Engine では、リクエストログ内にアプリログが埋め込まれることはありません。
stdoutstderr App Engine は、stdoutstderr に書き込まれたログをリクエストログ内に埋め込みます。 App Engine では、stdoutstderr に書き込まれたログは埋め込まれません。
App Engine プラットフォームのログ App Engine は内部プラットフォーム ログを出力しません。 App Engine は、ログ名が /var/log/google_init.log のインスタンスの起動時またはシャットダウン中に内部プラットフォーム ログを出力します。
Cloud Logging API の割り当て 各リクエストは、1 つの Cloud Logging の書き込みに対応します。 リクエストに関連付けられた各アプリのログエントリは、1 つの Cloud Logging の書き込みに対応します。第 2 世代のランタイムでは、Cloud Logging API の使用量が増加します。

ログとアプリログをリクエストする

第 1 世代のランタイムと第 2 世代のランタイムのロギング動作は、次のように異なります。

  • 第 1 世代のランタイムでは、App Engine はアプリログをリクエストログの protoPayload.line フィールドに埋め込みます。アプリログを表示するには、ログ エクスプローラでリクエストを開きます。

    次の図は、第 1 世代のランタイムのログを示しています。リクエストログとアプリログが関連付けられています。

    Java 8 の関連するログ

  • 第 2 世代のランタイムでは、リクエストログに protoPayload.line フィールドが存在しないため、リクエストログにアプリログ エントリは含まれません。代わりに、App Engine は各アプリログを別々のエントリとして記録します。ログ名は、ロギング設定によって異なります。var/log/app には、java.util.logging や Simple Logging Facade for Java(SLF4J)などの標準的なロギング フレームワークを使用して出力されたログが含まれています。stderr ログと stdout ログには、System.err.print() または System.out.print() を使用して記録されたアプリログが含まれます。

    次の図は、第 2 世代のランタイムのログを示しています。リクエストログとアプリログは個別に表示されています。

    Java 11 での個別のログ

stdoutstderr

第 1 世代のランタイムでは、App Engine は stdoutstderr に出力されたログをアプリログと見なし、これらのアプリログ エントリを関連するリクエストログの下に自動的にグループ化します。

第 2 世代のランタイムでは、App Engine はデフォルトで stdoutstderr に出力されたログを関連付けません。stdoutstderr を使用してアプリログ エンティティをリクエストログとグループ化するには、stdoutstderr に構造化ログを書き込むの手順を行います。

特定の App Engine プラットフォーム ログ(JVM、Jetty、内部インフラストラクチャ ログ)も stderr に出力されるため、stdoutstderr へのロギングはおすすめしません。アプリログとプラットフォーム ログは stderr ログ名で一緒に表示されるため、曖昧さが生じます。App Engine はプラットフォーム ログの量が多いため、第 2 世代ランタイムではこれが誇張されます。

App Engine プラットフォームのログ

第 1 世代ランタイムでは、App Engine はプラットフォーム ログは出力されません。

第 2 世代のランタイムでは、App Engine はインスタンスの起動時またはシャットダウン中にプラットフォーム ログを /var/log/google_init.log という名前で出力します。

プラットフォームのログは無視して構いません。これらのログが表示されないようにするには、クエリ logName="/var/log/google_init.log" を使用してログ エクスプローラにフィルタを追加します。

次の画像は、第 2 世代ランタイムでのプラットフォームのログを示しています。

プラットフォームのログ

Cloud Logging API の割り当て

第 1 世代のランタイムでは、App Engine はリクエストの実行中に発行されたすべてのアプリログを 1 つのリクエストログに埋め込み、その埋め込まれたリクエストを Cloud Logging に送信します。各リクエストは、1 つの Cloud Logging の書き込みに対応します。

第 2 世代のランタイムでは、App Engine は各アプリログを個別のログエントリで Cloud Logging に送信するため、リクエストあたりの Cloud Logging 書き込みの合計数が増加します。

Google Cloud は、Cloud Logging が使用する全体のストレージに基づいて課金を行います。ログに必要なストレージ全体が大幅に増加しなくても、App Engine がリクエストごとに書き込むアプリログの数によっては、1 分あたりの書き込み量は増加します。アプリが Cloud Logging の書き込み割り当てを超えた場合は、使用量割り当ての増加をリクエストできます。

詳細については、Cloud Logging の料金をご覧ください。

移行プロセスの概要

第 2 世代のランタイムのリクエストログにアプリログが埋め込まれていない場合でも、第 1 世代アプリと同様にログ表示エクスペリエンスは保持できます。

第 2 世代ランタイムでロギングを設定するには、次のいずれかのオプションを選択できます。

java.util.logging(JUL)パッケージを使用する

第 1 世代ランタイムを使用するアプリですべてのロギング要件に対して java.util.logging パッケージを実装している場合、第 2 世代ランタイムへの移行でコードを変更する必要はありません。

次のサンプルは、第 2 世代ランタイムのロギングに java.util.logging パッケージを使用する方法を示しています。

    package com.example.appengine.my_app;
    import java.io.IOException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.*;
    import java.util.logging.Logger;

    @WebServlet(name = "HelloAppEngine", value = "/")
    public class HelloAppEngine extends HttpServlet {

      // Use the java.util.logging.Logger to log messages
      private static final Logger logger = Logger.getLogger(HelloAppEngine.class.getName());

      @Override
      public void doGet(HttpServletRequest request, HttpServletResponse response)
          throws IOException {
        // Sample log messages
        logger.info("Info message");
        logger.warning("Warning message");
        logger.severe("Severe message");

        response.setContentType("text/plain");
        response.getWriter().println("Hello App Engine");
      }
    }

リクエストログでアプリログをグループ化する

第 2 世代ランタイムでは、リクエストログの protoPayload.line フィールドにアプリログは含まれません。ログ エクスプローラでは、trace フィールドを使用してリクエストログとアプリログをグループ化します。java.util.logging パッケージは、すべてのリクエストログとアプリログにトレース ID を自動的に追加します。ログ エクスプローラで相関ログを表示するには、相関ログを表示するをご覧ください。

Simple Logging Facade for Java(SLF4J)を使用する

SLF4J は、Java アプリケーションで使用される最も一般的なロギング インターフェースです。ファサードである SLF4J インターフェースは、ロギング バックエンドのプラットフォームに依存しないため、アプリに適したバックエンドを選択できます。

次の図は、java.util.logging ライブラリと Logback アペンダーを含む 2 つの SLF4J バックエンドのインテグレーションのオプションを示しています。

SLF4J の概要

java.util.logging パッケージを含む SLF4J

SLF4J では、ロギング バックエンドとして java.util.logging を含む SLF4J を使用すると、アプリを Cloud Logging と統合できます。デフォルトでは、App Engine はすべてのリクエストログとアプリログにトレース ID を追加します。このメカニズムにより、ログ エクスプローラでリクエストログとアプリログをグループ化できます。

java.util.logging を含む SLF4J を実装する手順は次のとおりです。

  1. slf4j-jdk14.jar 依存関係を pom.xml ファイルに追加します。slf4j-jdk14.jar ファイルは、java.util.logging ライブラリのバックエンドのインテグレーションを提供します。

    <!-- SLF4J interface -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>2.0.4</version>
    </dependency>
    <!-- JUL implementation for SLF4J -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-jdk14</artifactId>
      <version>2.0.9</version>
    </dependency>
    
  2. WEB-INF/logging.properties ファイルを作成してカスタム構成を追加します。

    com.example.appengine.java8.HelloAppEngine.level = INFO
    
  3. appengine-web.xml ファイルを更新して、WEB-INF/logging.properties<property> 値を含めます。

    <system-properties>
      <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
    </system-properties>
    

次の画像は、java.util.logging を含む SLF4J が、トレース ID をアプリログに自動的に追加する方法を示しています。

トレース ID を使用してリクエストごとにアプリログをグループ化する

ログ エクスプローラ ビューを第 1 世代のランタイムのようなリクエスト中心のビューにチューニングするには、相関ログを表示するをご覧ください。

Logback アペンダーを含む SLF4J を使用する

第 1 世代のランタイム アプリで、Logback を含む SLF4J の組み込み実装を使用している場合は、ソースコードを更新し、リクエストとアプリログをグループ化するための追加の手順を実装する必要があります。

アプリを Cloud Logging と統合する手順は次のとおりです。

  1. google-cloud-logging-logback アペンダーを pom.xml ファイルに追加して、Logback のロギング アペンダーをインストールします。

     <!-- SLF4J interface -->
     <dependency>
           <groupId>org.slf4j</groupId>
           <artifactId>slf4j-api</artifactId>
           <version>2.0.4</version>
     </dependency>
    
     <!-- Logback JARs -->
     <dependency>
           <groupId>ch.qos.logback</groupId>
           <artifactId>logback-classic</artifactId>
           <version>1.3.6</version>
     </dependency>
     <dependency>
           <groupId>ch.qos.logback</groupId>
           <artifactId>logback-core</artifactId>
           <version>1.3.5</version>
     </dependency>
    
     <!-- Google Cloud logging appender for Logback -->
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-logging-logback</artifactId>
     </dependency>
    
  2. LogEntry フィールドにトレース ID を追加するロギング エンハンサーを作成します。次の TraceIdLoggingEnhancer クラスは、ApiProxy API を使用して、リクエストに関連付けられたトレース ID を取得します。

      import com.google.appengine.api.utils.SystemProperty;
      import com.google.cloud.logging.LogEntry;
      import com.google.cloud.logging.LoggingEnhancer;
    
      import com.google.apphosting.api.ApiProxy;
      import com.google.apphosting.api.ApiProxy.Environment;
    
      // Add trace ID to the log entry
      public class TraceIdLoggingEnhancer implements LoggingEnhancer {
    
        @Override
        public void enhanceLogEntry(LogEntry.Builder logEntry) {
          final String PROJECT_ID = SystemProperty.applicationId.get();
    
          Environment environment = ApiProxy.getCurrentEnvironment();
    
          if (environment instanceof ApiProxy.EnvironmentWithTrace) {
            ApiProxy.EnvironmentWithTrace environmentWithTrace = (ApiProxy.EnvironmentWithTrace) environment;
            environmentWithTrace
                .getTraceId()
                .ifPresent(
                    id ->
                      logEntry.setTrace(String.format("projects/%s/traces/%s", PROJECT_ID, id)));
          }
        }
      }
      // [END logging_enhancer]
    
  3. logback.xml ファイルに Cloud Logging アペンダーの構成を追加して、Logback を構成します。Logback フレームワークは、WEB-INF/classeslogback.xml ファイルを介して構成を管理します。

    <configuration>
      <appender name="CLOUD" class="com.google.cloud.logging.logback.LoggingAppender">
          <!-- This should be set to the new Logging Enhancer in the app code. -->
          <enhancer>com.example.appengine.my_app.enhancers.TraceIdLoggingEnhancer</enhancer>
          <resourceType>gae_app</resourceType>
      </appender>
      <root level="info">
          <appender-ref ref="CLOUD" />
      </root>
    </configuration>
    

    次の図は、最終的なアプリ ディレクトリの構造を示しています。

    ディレクトリ構造

相関ログを表示する

各ログエントリのトレース ID フィールドを使用して、ログ エクスプローラで相関ログを表示できます。ログ エクスプローラで相関ログを表示するには:

  1. Google Cloud コンソールのナビゲーション パネルで、[ロギング] を選択してから、[ログ エクスプローラ] を選択します。

    [ログ エクスプローラ] に移動

  2. [リソースの種類] で [GAE アプリケーション] を選択します。

  3. リクエストログを表示して関連付けるには、[ログ名] で [request_log] を選択します。また、リクエストログで関連付けるには、[関連付け] をクリックして [request_log] を選択します。

    ログの関連付け

  4. [クエリ結果] ペインで、ログエントリを開くには、[開く] をクリックします。エントリを開くと、各リクエストログに関連付けられたアプリログが表示されます。