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.

Geração de registros

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

Usar java.util.logging

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

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

Para ativar rapidamente a geração de registros detalhada para as 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 o VM argument, não o Program argument.

Se você usa o IntelliJ, especifique o argumento da VM em configuração de execução/depuração:

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

Se a JVM do programa estiver em execução com a configuração correta, você verá a geração de registros no 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 maneira de configurar o nível de geração de registros. Para mais informações sobre o uso da API de geração de registros do Java, consulte Visão geral da geração de registros do Java.

Geração de registros de depuração da biblioteca de cliente (SLF4J)

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

AVISO:a geração de registros de depuração da biblioteca de cliente inclui seus payloads de dados em texto simples, que podem 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 de aplicativos e siga o princípio de privilégio mínimo. O Google também recomenda que a geração de registros de depuração da biblioteca de cliente seja ativada apenas temporariamente durante a depuração ativa e não seja usada permanentemente na 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. Você precisará configurar as 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 caminho de classe 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 a geração de registros com uma variável de ambiente

A geração de registros de depuração fica desativada por padrão. A geração de registros de depuração não é ativada, mesmo que os pré-requisitos sejam atendidos.

É possível ativar a geração de registros de depuração da biblioteca de cliente definindo a variável de ambiente GOOGLE_SDK_JAVA_LOGGING como true, sem diferenciar maiúsculas de minúsculas. Se não estiver definida ou tiver qualquer outro valor, a geração de registros de depuração da biblioteca de cliente será desativada.

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 slf4j de maneira transitiva. Os usuários precisam consultar a configuração do Logback para conferir as versões mais recentes. Você pode pular esta etapa se o aplicativo já tiver dependências do Logback no caminho de classe.

Se você usa 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. Você pode pular esta etapa se precisar apenas 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 conferir se o ambiente é compatível com clientes baseados em gRPC.

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

Resolver problemas de ClassNotFoundException, NoSuchMethodError e NoClassDefFoundError

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

Várias fontes podem causar conflitos de caminho de classe:

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

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

Da mesma forma, se o caminho de classe tiver uma versão mais antiga de protobuf-java, mas google-cloud-java exigir uma versão mais recente, você poderá encontrar 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 conferir 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 a execução, o ambiente de execução poderá estar introduzindo JARs conflitantes no caminho de classe de execução. Um caso típico é que o Hadoop, o Spark ou outro software de servidor em que o aplicativo é executado tenha versões conflitantes de JARs netty, guava ou protobuf-java no caminho de classe.

Detectar conflitos durante a build

Para detectar erros de vinculação de dependência no tempo de compilação, adicione a regra do Linkage Checker Enforcer 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 caminho de classe de execução. Você precisa estar totalmente ciente de quais JARs/classes estão incluídos no caminho de classe de execução, porque cada ambiente de servidor é diferente.

Resolver o conflito

Há diferentes estratégias para resolver conflitos, mas é necessário entender a causa raiz deles:

  • Se você controla a árvore de dependências, faça upgrade das dependências conflitantes (por exemplo, fazendo upgrade do Guava). Essa abordagem é a mais robusta, mas pode exigir um 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 de 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 encontrar algumas dependências com versões inesperadas, o que pode causar conflitos de dependência.

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

    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>