Résoudre les problèmes liés aux bibliothèques clientes Cloud pour Java

Ce document présente la journalisation et le dépannage des problèmes courants liés aux bibliothèques clientes Cloud pour Java.

Journalisation

Il existe deux façons d'activer la journalisation pour les bibliothèques clientes : en utilisant java.util.logging ou en utilisant la journalisation de débogage de la bibliothèque cliente avec SLF4J.

Utiliser java.util.logging

Les bibliothèques clientes Cloud pour Java utilisent le package de l'API de journalisation Java (java.util.logging). La configuration du niveau de journalisation révèle des informations qui vous aident à résoudre les problèmes, y compris :

  • Timing de la communication client-serveur sous-jacente.
  • En-têtes des messages de requête et de réponse.
  • Messages détaillés dans les bibliothèques de dépendances sous-jacentes.

Pour activer rapidement la journalisation détaillée pour les bibliothèques clientes Cloud pour Java, créez un fichier logging.properties avec le contenu suivant :

# 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

Ensuite, exécutez votre application avec -Djava.util.logging.config.file=path/to/logging.properties comme VM argument, et non comme Program argument.

Si vous utilisez IntelliJ, spécifiez l'argument de VM dans Run/Debug Configuration (Configuration d'exécution/de débogage) :

Configuration d'exécution/de débogage IntelliJ montrant où spécifier les arguments de VM

Si la JVM de votre programme s'exécute correctement avec la configuration, vous verrez la journalisation au niveau FINE dans votre console.

Exemple de résultat :

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)
...

C'est l'une des façons de configurer le niveau de journalisation. Pour en savoir plus sur l'utilisation de l'API de journalisation Java, consultez la présentation de la journalisation Java.

Journalisation de débogage de la bibliothèque cliente (SLF4J)

Les bibliothèques clientes Cloud sont fournies avec la journalisation du débogage, que vous pouvez activer pour vous aider à résoudre les problèmes d'intégration de votre application à l'API. Lorsque la journalisation est activée, les événements clés tels que les requêtes et les réponses, ainsi que les charges utiles de données et les métadonnées (comme les en-têtes), sont consignés dans le flux d'erreur standard.

AVERTISSEMENT : La journalisation du débogage de la bibliothèque cliente inclut vos charges utiles de données en texte brut, qui peuvent inclure des données sensibles telles que des informations permettant d'identifier personnellement l'utilisateur pour vous-même ou vos clients, des clés privées ou d'autres données de sécurité qui pourraient être compromises en cas de fuite. Veillez à toujours appliquer de bonnes pratiques d'hygiène des données avec les journaux de votre application et à suivre le principe du moindre accès. Google recommande également d'activer la journalisation du débogage de la bibliothèque cliente uniquement de manière temporaire pendant le débogage actif, et de ne pas l'utiliser de manière permanente en production.

Conditions préalables

Nos bibliothèques sont compatibles avec la journalisation de débogage à l'aide de l'interface SLF4J.

La BOM des bibliothèques et les bibliothèques clientes n'incluent pas la dépendance slf4j-api. Vous devrez configurer les dépendances de journalisation, y compris SLF4J et les implémentations et configurations de journalisation correspondantes, avant d'activer la journalisation de débogage de la bibliothèque cliente.

NOTE : Vous devez disposer de SLF4J et des fournisseurs de journalisation correspondants (logback, log4j2, etc.) dans votre classpath pour pouvoir utiliser la fonctionnalité de journalisation de débogage de la bibliothèque cliente. Sinon, aucune journalisation de débogage ne sera effectuée, même si vous l'activez à l'aide d'une variable d'environnement.

Activer la journalisation avec une variable d'environnement

La journalisation de débogage est désactivée par défaut. La journalisation du débogage n'est pas activée, sauf si elle l'est explicitement, même si les conditions préalables sont remplies.

Vous pouvez activer la journalisation de débogage de la bibliothèque cliente en définissant la variable d'environnement GOOGLE_SDK_JAVA_LOGGING sur true (sans tenir compte de la casse). Si cette valeur n'est pas définie ou si elle est différente, la journalisation de débogage de la bibliothèque cliente est désactivée.

Exemples de configurations de journalisation

La documentation fournit un exemple de journalisation de débogage à l'aide de Logback.

Ajoutez les dépendances Logback à votre application. La dépendance slf4j sera ainsi ajoutée de manière transitive. Les utilisateurs doivent se référer à la configuration Logback pour les dernières versions. Vous pouvez ignorer cette étape si votre application comporte déjà des dépendances logback dans le classpath.

Si vous utilisez Maven :

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

Ajoutez le contenu au fichier de configuration logback si vous souhaitez afficher les journaux de niveau DEBUG. Vous pouvez ignorer cette étape si vous n'avez besoin que des journaux de niveau INFO. Pour en savoir plus, consultez la configuration 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 n'est pas configuré correctement

Si vous voyez des exceptions liées à ALPN is not configured properly, par exemple :

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.

Utilisez l'outil de vérification de la compatibilité pour voir si votre environnement est compatible avec les clients basés sur gRPC.

L'incompatibilité peut être due à l'un des facteurs suivants :

Résoudre les problèmes liés à ClassNotFoundException, NoSuchMethodError et NoClassDefFoundError

Ces erreurs sont souvent dues à la présence de plusieurs versions ou de versions conflictuelles de la même dépendance dans le chemin de classe. Ces conflits de dépendances se produisent souvent avec guava ou protobuf-java.

Plusieurs sources peuvent entraîner des conflits de chemin d'accès aux classes :

  • Plusieurs versions de la même dépendance transitive dans l'arborescence des dépendances.
  • Votre classpath d'exécution comporte des versions de dépendances différentes de celles que vous avez spécifiées dans la compilation.

Par exemple, si vous avez une dépendance directe ou transitive sur Guava version 19.0 et que google-cloud-java utilise Guava version 30.0, il est possible que google-cloud-java utilise des méthodes Guava qui n'existent pas dans Guava 19.0 et provoque NoSuchMethodError.

De même, si votre classpath contient une ancienne version de protobuf-java, mais que google-cloud-java nécessite une version plus récente, vous pouvez voir NoClassDefFoundError qui ne parvient pas à initialiser les classes google-cloud-java.

Exemple :

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

Valider le conflit

Vérifiez l'arborescence des dépendances pour voir si vous avez plusieurs versions des mêmes dépendances :

$ mvn dependency:tree

Recherchez les versions de dépendances potentiellement conflictuelles telles que guava ou protobuf-java.

Si l'erreur ne se produit que lors de l'exécution, il est possible que votre environnement d'exécution introduise des fichiers JAR en conflit dans votre classpath d'exécution. Un cas typique est celui où Hadoop, Spark ou d'autres logiciels de serveur sur lesquels votre application s'exécute ont des versions de fichiers JAR netty, guava ou protobuf-java en conflit dans le classpath.

Détecter les conflits lors de la compilation

Pour détecter les erreurs de liaison de dépendances au moment de la compilation, ajoutez la règle d'application Linkage Checker dans votre fichier pom.xml :

      <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>

Toutefois, il n'existe aucun moyen de détecter les conflits de classpath d'exécution. Vous devez savoir exactement quels fichiers JAR/classes sont inclus dans le classpath d'exécution, car chaque environnement de serveur est différent.

Résoudre le conflit

Il existe différentes stratégies pour résoudre les conflits, mais vous devez comprendre leur cause racine :

  • Si vous contrôlez l'arborescence des dépendances, mettez à niveau les dépendances en conflit (par exemple, en mettant à niveau Guava). Cette approche est la plus robuste, mais elle peut nécessiter un effort important et plusieurs versions de la bibliothèque pour assurer la compatibilité.
  • Si vous ne pouvez pas modifier ni envoyer de nouvelles versions de vos dépendances, importez com.google.cloud:libraries-bom:25.1.0 (ou une version plus récente) pour sélectionner des versions de dépendances cohérentes. Cette approche simplifie la gestion des dépendances. Par exemple, voici comment vous pouvez dépendre de versions cohérentes de Guava et de com.google.cloud:google-cloud-storage sans définir explicitement la version de l'une ou l'autre :
  ...
  <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>
  ...
  • Les notes de version de libraries-bom indiquent les bibliothèques de dépendances compatibles. Par exemple, https://github.com/googleapis/java-cloud-bom/releases/tag/v26.31.0 affiche :

    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
    

    En examinant le graphique des dépendances de votre projet (mvn dependency:tree -Dverbose, gradle dependencies ou sbt dependencyTree), vous constaterez peut-être que certaines dépendances ont des versions inattendues, ce qui peut entraîner des conflits de dépendances.

  • Si la modification des versions de dépendances introduit d'autres échecs, envisagez d'ombrer les dépendances qui sont en conflit avec les bibliothèques Java Google Cloud .

    Par exemple, pour ombrer guava et protobuf-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>