Java 用 Cloud クライアント ライブラリのトラブルシューティング

このドキュメントでは、Java 用 Cloud クライアント ライブラリのロギングと一般的な問題のトラブルシューティングの概要について説明します。

ロギング

クライアント ライブラリのロギングを有効にするには、java.util.logging を使用する方法と、SLF4J でクライアント ライブラリのデバッグ ロギングを使用する方法の 2 つがあります。

java.util.logging を使用する

Java 用 Cloud クライアント ライブラリは、Java Logging API(java.util.logging)パッケージを使用します。ロギングレベルを構成すると、トラブルシューティングに役立つ次の情報が表示されます。

  • 基盤となるクライアント / サーバー通信のタイミング。
  • リクエスト メッセージとレスポンス メッセージのヘッダー。
  • 基盤となる依存関係ライブラリの詳細メッセージ。

Java 用 Cloud クライアント ライブラリの詳細ロギングをすばやく有効にするには、次の内容で logging.properties ファイルを作成します。

# run java program pointing to this properties file with the java arg
#   -Djava.util.logging.config.file=path/to/logging.properties
handlers=java.util.logging.ConsoleHandler
java.util.logging.SimpleFormatter.format=%1$tF %1$tT,%1$tL %4$-8s %3$-50s - %5$s %6$s%n

# --- ConsoleHandler ---
java.util.logging.ConsoleHandler.level=ALL
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
.level=INFO

# --- Specify logging level for certain packages ---
# com.google.api is for HTTP 1.1 layer
com.google.api.level=ALL
# io.grpc is for gRPC + Netty layer
io.grpc.level=FINE
# com.google.auth is for authentication
com.google.auth.level=FINE

# Example when you specify the storage library's level. This works when
# the target Cloud library uses the logging API.
com.google.cloud.storage.level=INFO

次に、Program argument ではなく VM argument として -Djava.util.logging.config.file=path/to/logging.properties を指定してアプリケーションを実行します。

IntelliJ を使用する場合は、[実行/デバッグ構成] で VM 引数を指定します。

VM 引数を指定する場所を示す IntelliJ の実行/デバッグ構成

プログラムの JVM が構成どおりに実行されている場合は、コンソールに FINE レベルのロギングが表示されます。

出力例:

2023-04-05 13:03:01,761 FINE     com.google.auth.oauth2.DefaultCredentialsProvider  - Attempting to load credentials from well known file: /usr/local/google/home/suztomo/.config/gcloud/application_default_credentials.json
2023-04-05 13:03:01,847 FINE     io.grpc.ManagedChannelRegistry                     - Unable to find OkHttpChannelProvider
java.lang.ClassNotFoundException: io.grpc.okhttp.OkHttpChannelProvider
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:315)
...

これは、ロギングレベルを構成する方法の 1 つです。Java Logging API の使用方法について詳しくは、Java Logging の概要をご覧ください。

クライアント ライブラリのデバッグ ロギング(SLF4J)

Cloud クライアント ライブラリには、アプリケーションと API の統合のトラブルシューティングに役立つオプトインのデバッグ ロギングが用意されています。 ロギングが有効になると、リクエストやレスポンスなどの主要なイベントが、データ ペイロードやヘッダーなどのメタデータとともに、標準エラー ストリームに記録されます。

警告: クライアント ライブラリのデバッグ ロギングには、データ ペイロードが平文で含まれます。これには、自分や顧客の個人を特定できる情報(PII)、秘密鍵、漏洩すると侵害される可能性のあるその他のセキュリティ データなどの機密データが含まれる可能性があります。アプリケーション ログで常に適切なデータの整備を実践し、最小権限の原則に従ってください。また、Google では、クライアント ライブラリのデバッグ ロギングは、アクティブなデバッグ中にのみ一時的に有効にし、本番環境では永続的に使用しないことをおすすめします。

前提条件

このライブラリは、SLF4J インターフェースを使用したデバッグ ロギングをサポートしています。

クライアント ライブラリと ライブラリ BOM には、slf4j-api の依存関係は含まれていません。 クライアント ライブラリのデバッグ ロギングを有効にする前に、SLF4J や対応するロギング実装と構成などのロギングの依存関係を設定する必要があります。

NOTE: クライアント ライブラリの デバッグ ロギング機能を使用するには、SLF4J と対応するロギング プロバイダ( logback、log4j2 など)がクラスパスに必要です。そうしないと、 環境変数を使用して有効にしてもデバッグ ロギングは行われません。

環境変数を使用してロギングを有効にする

デフォルトでは、デバッグ ロギングはオフになっています。 前提条件を満たしていても、明示的に有効にしない限り、デバッグ ロギングは有効になりません。

クライアント ライブラリのデバッグ ロギングを有効にするには、環境変数 GOOGLE_SDK_JAVA_LOGGINGtrue(大文字と小文字を区別しない)に設定します。 設定されていない場合や他の値の場合、クライアント ライブラリのデバッグ ロギングは無効になります。

ロギングの設定例

ドキュメントでは、Logback を使用したデバッグ ロギングの例を示しています。

Logback の依存関係をアプリケーションに追加すると、slf4j の依存関係が推移的に取り込まれます。 最新バージョンについては、Logback の設定 をご覧ください。 アプリケーションのクラスパスに logback の依存関係がすでにある場合は、この手順をスキップできます。

Maven を使用している場合:

<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>LATEST_VERSION</version>
</dependency>

DEBUG レベルのログを表示する場合は、logback 構成ファイルにコンテンツを追加します。 INFO レベルのログのみが必要な場合は、この手順をスキップできます。 詳細については、Logback の構成 をご覧ください。

<!-- set Client Library log level to DEBUG -->
<logger name="com.google.api"  level="DEBUG"/>
<!-- set Auth Library log level to DEBUG -->
<logger name="com.google.auth" level="DEBUG"/>

ALPN が正しく構成されていない

ALPN is not configured properly に関連する例外が表示された場合は、次のようにします。

Caused by: java.lang.IllegalArgumentException: ALPN is not configured properly. See https://github.com/grpc/grpc-java/blob/master/SECURITY.md#troubleshooting for more information.

互換性チェッカーを使用して、環境が gRPC ベースのクライアントと互換性があるかどうかを確認します。

非互換性とは、次のいずれかを意味します。

ClassNotFoundExceptionNoSuchMethodErrorNoClassDefFoundError のトラブルシューティング

これらのエラーは、クラスパスに同じ依存関係の複数のバージョンまたは競合するバージョンが存在することが原因で発生することがよくあります。このような依存関係の競合は、guava または protobuf-java で発生することがよくあります。

クラスパスの競合は、次の複数のソースが原因で発生する可能性があります。

  • 依存関係ツリーに同じ推移的依存関係の複数のバージョンがある。
  • ランタイム クラスパスの依存関係のバージョンが、ビルドで指定したバージョンと異なる。

たとえば、Guava バージョン 19.0 に直接的または推移的な依存関係があり、google-cloud-java が Guava バージョン 30.0 を使用している場合、google-cloud-java は Guava 19.0 に存在しない Guava メソッドを使用し、NoSuchMethodError が発生する可能性があります。

同様に、クラスパスに古いバージョンの protobuf-java があり、google-cloud-java で新しいバージョンが必要な場合は、google-cloud-java クラスの初期化に失敗する NoClassDefFoundError が表示されることがあります。

次に例を示します。

java.lang.NoClassDefFoundError: Could not initialize class com.google.pubsub.v1.PubsubMessage$AttributesDefaultEntryHolder

競合を検証する

依存関係ツリーを確認して、同じ依存関係の複数のバージョンがあるかどうかを確認します。

$ mvn dependency:tree

guavaprotobuf-java など、競合する可能性のある依存関係のバージョンを探します。

ランタイムでのみエラーが発生する場合は、ランタイム環境で競合する JAR がランタイム クラスパスに導入されている可能性があります。一般的なケースとして、アプリケーションが実行される Hadoop、Spark、その他のサーバー ソフトウェアのクラスパスに、競合するバージョンの nettyguavaprotobuf-java JAR が含まれていることが考えられます。

ビルド中に競合を検出する

コンパイル時に依存関係のリンクエラーを検出するには、pom.xml に Linkage Checker Enforcer Rule を追加します。

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-enforcer-plugin</artifactId>
        <version>3.0.0-M3</version>
        <dependencies>
          <dependency>
            <groupId>com.google.cloud.tools</groupId>
            <artifactId>linkage-checker-enforcer-rules</artifactId>
            <version>1.5.7</version>
          </dependency>
        </dependencies>
        <executions>
          <execution>
            <id>enforce-linkage-checker</id>
            <!-- Important! Should run after compile -->
            <phase>verify</phase>
            <goals>
              <goal>enforce</goal>
            </goals>
            <configuration>
              <rules>
                <LinkageCheckerRule
                    implementation="com.google.cloud.tools.dependencies.enforcer.LinkageCheckerRule"/>
              </rules>
            </configuration>
          </execution>
        </executions>
      </plugin>

ただし、ランタイム クラスパスの競合を検出する方法はありません。サーバー環境はそれぞれ異なるため、ランタイム クラスパスに含まれる JAR/クラスを十分に把握する必要があります。

競合を解決する

競合を解決するにはさまざまな方法がありますが、競合の根本原因を理解する必要があります。

  • 依存関係ツリーを制御する場合は、競合する依存関係をアップグレードします(Guava のアップグレードなど)。この方法は最も堅牢ですが、互換性を確保するには多大な労力と複数のライブラリ リリースが必要になる場合があります。
  • 依存関係の新しいバージョンを変更して push できない場合は、com.google.cloud:libraries-bom:25.1.0(またはそれ以降のバージョン)をインポートして、一貫した依存関係のバージョンを選択します。この方法により、依存関係の管理が簡素化されます。たとえば、Guava と com.google.cloud:google-cloud-storage の一貫したバージョンに依存するには、どちらのバージョンも明示的に設定せずに次のようにします。
  ...
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>libraries-bom</artifactId>
        <version>25.1.0</version>
        <type>pom</type>
        <scope>import</scope>
       </dependency>
     </dependencies>
  </dependencyManagement>
  ...
  <dependencies>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-storage</artifactId>
    </dependency>
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
    </dependency>
    ...
  </dependencies>
  ...
  • libraries-bom のリリースノートには、互換性のある依存関係ライブラリが記載されています。たとえば、https://github.com/googleapis/java-cloud-bom/releases/tag/v26.31.0 には次のように表示されます。

    These client libraries are built with the following Java libraries:
    
    - Guava: 32.1.3-jre
    - Protobuf Java: 3.25.2
    - Google Auth Library: 1.22.0
    - Google API Client: 2.2.0
    - gRPC: 1.61.0
    - GAX: 2.41.0
    - Google Cloud Core: 2.31.0
    

    プロジェクトの依存関係グラフ(mvn dependency:tree -Dverbosegradle dependenciessbt dependencyTree)を調べると、一部の依存関係に予期しないバージョンが含まれていることがあり、依存関係の競合が発生する可能性があります。

  • 依存関係のバージョンを変更すると他のエラーが発生する場合は、 Java ライブラリと競合する依存関係のシェーディング を検討してください。 Cloud de Confiance by S3NS

    たとえば、guavaprotobuf-java をシェーディングするには、次のようにします。

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>...</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <keepDependenciesWithProvidedScope>false</keepDependenciesWithProvidedScope>
              <relocations>
                <!-- move protobuf to a shaded package -->
                <relocation>
                  <pattern>com.google.protobuf</pattern>
                  <shadedPattern>myapp.shaded.com.google.protobuf</shadedPattern>
                </relocation>
                <!-- move Guava to a shaded package -->
                <relocation>
                  <pattern>com.google.common</pattern>
                  <shadedPattern>myapp.shaded.com.google.common</shadedPattern>
                </relocation>
              </relocations>
            </configuration>
          </execution>
        </executions>
      </plugin>