Risolvere i problemi delle librerie client di Cloud per Java

Questo documento fornisce una panoramica del logging e della risoluzione dei problemi comuni con le librerie client di Cloud per Java.

Logging

Esistono due modi per abilitare il logging per le librerie client: utilizzando java.util.logging o utilizzando il logging di debug della libreria client con SLF4J.

Utilizzo di java.util.logging

Le librerie client di Cloud per Java utilizzano il pacchetto dell'API Java Logging (java.util.logging). La configurazione del livello di logging rivela informazioni utili per la risoluzione dei problemi, tra cui:

  • Tempi di comunicazione client-server sottostante.
  • Intestazioni dei messaggi di richiesta e risposta.
  • Messaggi dettagliati nelle librerie di dipendenze sottostanti.

Per abilitare rapidamente il logging dettagliato per le librerie client di Cloud per Java, crea un file logging.properties con i seguenti contenuti:

# 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

Poi, esegui l'applicazione con -Djava.util.logging.config.file=path/to/logging.properties come VM argument, non come Program argument.

Se utilizzi IntelliJ, specifica l'argomento della VM in Run/Debug Configuration:

Configurazione di esecuzione/debug di IntelliJ che mostra dove specificare gli argomenti della VM

Se la JVM del programma è in esecuzione con la configurazione corretta, vedrai il logging a livello FINE nella console.

Output di esempio:

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

Questo è un modo per configurare il livello di logging. Per saperne di più sull'utilizzo dell'API Java Logging, consulta la panoramica di Java Logging.

Logging di debug della libreria client (SLF4J)

Le librerie client di Cloud includono il logging di debug con consenso esplicito che può aiutarti a risolvere i problemi di integrazione dell'applicazione con l'API. Quando il logging è attivato, gli eventi chiave come richieste e risposte, insieme ai payload di dati e ai metadati, come le intestazioni, vengono registrati nel flusso di errori standard.

AVVISO:il logging di debug della libreria client include i payload di dati in testo non crittografato, che potrebbero includere dati sensibili come PII per te o per i tuoi clienti, chiavi private o altri dati di sicurezza che potrebbero essere compromessi in caso di fuga di dati. Applica sempre una buona pulizia dei dati con i log delle applicazioni e segui il principio del minimo accesso. Google consiglia inoltre di attivare il logging di debug della libreria client solo temporaneamente durante il debug attivo e di non utilizzarlo in modo permanente in produzione.

Prerequisito

Le nostre librerie supportano il logging di debug utilizzando l'SLF4J interfaccia.

Le librerie client e il BOM delle librerie non includono la dipendenza slf4j-api. Prima di attivare il logging di debug della libreria client, devi configurare le dipendenze di logging, inclusi SLF4J e le implementazioni e configurazioni di logging corrispondenti.

NOTE: per utilizzare la funzionalità di logging di debug della libreria client, devi avere SLF4J e i provider di logging corrispondenti, ad es. logback, log4j2 e così via, nel classpath; in caso contrario, non verrà eseguito alcun logging di debug anche se lo abiliti utilizzando la variabile di ambiente.

Abilitare il logging con una variabile di ambiente

Per impostazione predefinita, il logging di debug è disattivato. Il logging di debug non viene attivato a meno che non sia esplicitamente, anche se i prerequisiti sono soddisfatti.

Puoi abilitare il logging di debug della libreria client impostando la variabile di ambiente GOOGLE_SDK_JAVA_LOGGING su true, senza distinzione tra maiuscole e minuscole. Se non è impostata o ha un altro valore, il logging di debug della libreria client è disattivato.

Esempi di configurazioni di logging

La documentazione fornisce un esempio di logging di debug utilizzando Logback.

Aggiungi le dipendenze Logback all'applicazione, in modo da includere la dipendenza slf4j in modo transitivo. Gli utenti devono fare riferimento alla configurazione di Logback per le versioni più recenti. Puoi saltare questo passaggio se la tua applicazione ha già dipendenze logback nel classpath.

Se utilizzi Maven:

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

Aggiungi i contenuti al file di configurazione logback se vuoi visualizzare i log a livello DEBUG. Puoi saltare questo passaggio se hai bisogno solo di log a livello INFO. Per saperne di più, consulta la configurazione di 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 non è configurato correttamente

Se visualizzi eccezioni correlate a ALPN is not configured properly, ad esempio:

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.

Utilizza lo strumento di verifica della compatibilità per verificare se il tuo ambiente è compatibile con i client basati su gRPC.

L'incompatibilità potrebbe significare una delle seguenti opzioni:

Risolvere i problemi relativi a ClassNotFoundException, NoSuchMethodError e NoClassDefFoundError

Questi errori sono spesso causati dalla presenza di più versioni o di versioni in conflitto della stessa dipendenza nel classpath. Questi conflitti di dipendenze si verificano spesso con guava o protobuf-java.

Più origini potrebbero causare conflitti di classpath:

  • Più versioni della stessa dipendenza transitiva nell'albero delle dipendenze.
  • Il classpath di runtime ha versioni di dipendenze diverse da quelle specificate nella build.

Ad esempio, se hai una dipendenza diretta o transitiva dalla versione 19.0 di Guava e google-cloud-java utilizza la versione 30.0 di Guava, google-cloud-java potrebbe utilizzare metodi Guava che non esistono in Guava 19.0 e causare NoSuchMethodError.

Allo stesso modo, se il classpath ha una versione precedente di protobuf-java, ma google-cloud-java richiede una versione successiva, potresti visualizzare NoClassDefFoundError che non riesce a inizializzare le classi google-cloud-java.

Ad esempio:

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

Convalidare il conflitto

Controlla l'albero delle dipendenze per verificare se hai più versioni delle stesse dipendenze:

$ mvn dependency:tree

Cerca le versioni di dipendenze potenzialmente in conflitto, come guava o protobuf-java.

Se si verifica l'errore solo durante il runtime, l'ambiente di runtime potrebbe introdurre JAR in conflitto nel classpath di runtime. Un caso tipico è che Hadoop, Spark o altro software server su cui viene eseguita l'applicazione hanno versioni in conflitto di JAR netty, guava o protobuf-java nel classpath.

Rilevare i conflitti durante la build

Per rilevare gli errori di collegamento delle dipendenze in tempo di compilazione, aggiungi la regola di applicazione del controllo dei collegamenti nel file 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>

Tuttavia, non è possibile rilevare i conflitti di classpath di runtime. Devi essere pienamente consapevole di quali JAR/classi sono inclusi nel classpath di runtime perché ogni ambiente server è diverso.

Risolvere il conflitto

Esistono diverse strategie per risolvere i conflitti, ma devi comprenderne la causa principale:

  • Se controlli l'albero delle dipendenze, esegui l'upgrade delle dipendenze in conflitto (ad esempio, l'upgrade di Guava). Questo approccio è il più solido, ma può richiedere un impegno significativo e più release di librerie per garantire la compatibilità.
  • Se non puoi modificare e inviare nuove versioni delle dipendenze, importa com.google.cloud:libraries-bom:25.1.0 (o una versione più recente) per selezionare versioni di dipendenze coerenti. Questo approccio semplifica la gestione delle dipendenze. Ad esempio, ecco come puoi dipendere da versioni coerenti di Guava e com.google.cloud:google-cloud-storage senza impostare esplicitamente la versione di una delle due:
  ...
  <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>
  ...
  • Le note di rilascio di libraries-bom mostrano le librerie di dipendenze compatibili. Ad esempio, https://github.com/googleapis/java-cloud-bom/releases/tag/v26.31.0 mostra:

    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
    

    Esaminando il grafico delle dipendenze del progetto (mvn dependency:tree -Dverbose, gradle dependencies o sbt dependencyTree), potresti scoprire che alcune dipendenze hanno versioni impreviste, che potrebbero causare conflitti di dipendenze.

  • Se la modifica delle versioni delle dipendenze introduce altri errori, valuta la possibilità di ombreggiare le dipendenze in conflitto con Google Cloud le librerie Java.

    Ad esempio, per ombreggiare guava e 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>