Resolver problemas das bibliotecas de cliente do Cloud para Java

Este documento oferece uma visão geral da geração de registros e da solução de problemas comuns com as bibliotecas de cliente do Cloud para Java.

Logging

Há duas maneiras de ativar o registro em log para as bibliotecas de cliente: usando java.util.logging ou usando o registro em log de depuração da biblioteca de cliente com SLF4J.

Como usar o java.util.logging

As bibliotecas de cliente do Cloud para Java usam o pacote da API Java Logging (java.util.logging). A configuração do nível de geração de registros revela informações que ajudam você a solucionar problemas, incluindo:

  • Tempo da comunicação cliente-servidor subjacente.
  • Cabeçalhos de mensagens de solicitação e resposta.
  • Mensagens detalhadas nas bibliotecas de dependência subjacentes.

Para ativar rapidamente o registro detalhado das bibliotecas de cliente do Cloud para Java, crie um arquivo logging.properties com o seguinte conteúdo:

# 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

Em seguida, execute o aplicativo com -Djava.util.logging.config.file=path/to/logging.properties como VM argument, não Program argument.

Se você usa o IntelliJ, especifique o argumento da VM em Executar/depurar configuração:

Configuração de execução/depuração do IntelliJ mostrando onde especificar argumentos de VM

Se a JVM do seu programa estiver sendo executada com a configuração correta, você verá o registro em nível FINE no console.

Exemplo de saída:

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

Essa é uma das maneiras de configurar o nível de geração de registros. Para mais informações sobre o uso da API Java Logging, consulte Visão geral do Java Logging.

Registro de depuração da biblioteca de cliente (SLF4J)

As bibliotecas de cliente do Cloud vêm com o registro de depuração opcional, que pode ajudar você a resolver problemas na integração do aplicativo com a API. Quando a geração de registros é ativada, eventos principais, como solicitações e respostas, além de payloads de dados e metadados, como cabeçalhos, são registrados no fluxo de erro padrão.

AVISO:o registro de depuração da biblioteca de cliente inclui seus payloads de dados em texto simples, que pode incluir dados sensíveis, como PII para você ou seus clientes, chaves privadas ou outros dados de segurança que podem ser comprometidos se vazarem. Sempre pratique uma boa higiene de dados com os registros do aplicativo e siga o princípio de menor acesso. O Google também recomenda que o registro de depuração da biblioteca de cliente seja ativado apenas temporariamente durante a depuração ativa e não seja usado permanentemente em produção.

Pré-requisito

Nossas bibliotecas oferecem suporte à geração de registros de depuração usando a interface SLF4J.

As bibliotecas de cliente e o BOM de bibliotecas não incluem a dependência slf4j-api. É necessário configurar dependências de geração de registros, incluindo SLF4J e implementações e configurações de geração de registros correspondentes, antes de ativar a geração de registros de depuração da biblioteca de cliente.

NOTE: é necessário ter o SLF4J e os provedores de geração de registros correspondentes, por exemplo, logback, log4j2 etc., no classpath para usar o recurso de geração de registros de depuração da biblioteca de cliente. Caso contrário, não haverá geração de registros de depuração, mesmo que você a ative usando a variável de ambiente.

Ativar o registro em registros com uma variável de ambiente

A geração de registros de depuração fica desativada por padrão. O registro de depuração não é ativado, mesmo que os pré-requisitos sejam atendidos, a menos que seja explicitamente ativado.

Para ativar o registro de depuração da biblioteca de cliente, defina a variável de ambiente GOOGLE_SDK_JAVA_LOGGING como true, sem sensibilidade a maiúsculas e minúsculas. Se não estiver definido ou tiver qualquer outro valor, o registro de depuração da biblioteca de cliente será desativado.

Exemplos de configurações de geração de registros

A documentação oferece um exemplo de geração de registros de depuração usando o Logback.

Adicione dependências do Logback ao aplicativo. Isso vai trazer a dependência do slf4j de maneira transitiva. Os usuários devem consultar a configuração do Logback para as versões mais recentes. Pule esta etapa se o aplicativo já tiver dependências do logback no caminho de classe.

Se você estiver usando o Maven:

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

Adicione o conteúdo ao arquivo de configuração do logback se quiser ver registros no nível DEBUG. Pule esta etapa se você só precisar de registros no nível INFO. Consulte a Configuração do Logback para mais informações.

<!-- 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"/>

O ALPN não está configurado corretamente

Se você encontrar exceções relacionadas a ALPN is not configured properly, como:

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.

Use o verificador de compatibilidade para saber se seu ambiente é compatível com clientes baseados em gRPC.

A incompatibilidade pode significar uma das seguintes situações:

Resolver problemas ClassNotFoundException, NoSuchMethodError e NoClassDefFoundError

Esses erros geralmente são causados por várias versões ou versões conflitantes da mesma dependência no classpath. Esses conflitos de dependência geralmente ocorrem com guava ou protobuf-java.

Várias origens podem causar conflitos de classpath:

  • Várias versões da mesma dependência transitiva na árvore de dependências.
  • Seu caminho de classe de execução tem versões diferentes de dependências do que você especificou no build.

Por exemplo, se você tiver uma dependência direta ou transitiva da versão 19.0 do Guava e google-cloud-java usar a versão 30.0, google-cloud-java poderá usar métodos do Guava que não existem na versão 19.0, causando NoSuchMethodError.

Da mesma forma, se seu classpath tiver uma versão mais antiga de protobuf-java, mas google-cloud-java exigir uma versão mais recente, talvez você veja NoClassDefFoundError que não inicializa as classes google-cloud-java.

Exemplo:

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

Validar o conflito

Verifique a árvore de dependências para saber se você tem várias versões das mesmas dependências:

$ mvn dependency:tree

Procure versões de dependências potencialmente conflitantes, como guava ou protobuf-java.

Se o erro ocorrer apenas durante o tempo de execução, talvez o ambiente de execução esteja introduzindo JARs conflitantes no classpath de tempo de execução. Um caso típico é que o Hadoop, o Spark ou outro software de servidor em que o aplicativo é executado têm versões conflitantes de JARs netty, guava ou protobuf-java no caminho de classe.

Detectar conflitos durante a criação

Para detectar erros de vinculação de dependência no momento da compilação, adicione a regra de imposição do verificador de vinculação no 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>

No entanto, não há como detectar conflitos de classpath de tempo de execução. Você precisa saber quais JARs/classes estão incluídos no classpath de tempo de execução, porque cada ambiente de servidor é diferente.

Resolver o conflito

Existem diferentes estratégias para resolver conflitos, mas é preciso entender a causa principal deles:

  • Se você controla a árvore de dependências, faça upgrade das dependências conflitantes (por exemplo, Guava). Essa abordagem é a mais robusta, mas pode exigir esforço significativo e várias versões de biblioteca para garantir a compatibilidade.
  • Se não for possível modificar e enviar novas versões das dependências, importe com.google.cloud:libraries-bom:25.1.0 (ou uma versão mais recente) para selecionar versões de dependência consistentes. Essa abordagem simplifica o gerenciamento de dependências. Por exemplo, é assim que você pode depender de versões consistentes do Guava e do com.google.cloud:google-cloud-storage sem definir explicitamente a versão de um deles:
  ...
  <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>
  ...
  • As notas da versão do libraries-bom mostram as bibliotecas de dependência compatíveis. Por exemplo, 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
    

    Ao examinar o gráfico de dependências do projeto (mvn dependency:tree -Dverbose, gradle dependencies ou sbt dependencyTree), você pode descobrir que algumas dependências têm versões inesperadas, o que pode causar conflitos.

  • Se a mudança de versões de dependência introduzir outras falhas, considere sombrear dependências que entrem em conflito com bibliotecas Java Google Cloud .

    Por exemplo, para sombrear 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>