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

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

ロギング

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

java.util.logging の使用

Java 用 Cloud クライアント ライブラリは、Java ロギング 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 を使用する場合は、[Run/Debug Configuration] で 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、秘密鍵、漏洩すると漏洩する可能性のあるその他のセキュリティ データなどの機密データが含まれる可能性があります。アプリケーション ログでは常に適切なデータ衛生管理を行い、最小限のアクセス権の原則に従ってください。また、クライアント ライブラリのデバッグ ロギングは、アクティブなデバッグ中に一時的にのみ有効にし、本番環境で永続的に使用しないことをおすすめします。

前提条件

ライブラリは、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)を調べると、一部の依存関係に予期しないバージョンが含まれていることがわかります。これにより、依存関係の競合が発生する可能性があります。

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

    たとえば、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>