Soluciona problemas de las bibliotecas cliente de Cloud para Java

En este documento, se proporciona una descripción general del registro y la solución de problemas comunes con las bibliotecas cliente de Cloud para Java.

Logging

Existen dos formas de habilitar el registro para las bibliotecas cliente: con java.util.logging o con el registro de depuración de la biblioteca cliente con SLF4J.

Usa java.util.logging

Las bibliotecas cliente de Cloud para Java usan el paquete de la API de registro de Java (java.util.logging). Configurar el nivel de registro revela información que te ayuda a solucionar problemas, como la siguiente:

  • Es el tiempo de la comunicación subyacente entre el cliente y el servidor.
  • Encabezados de mensajes de solicitud y respuesta
  • Mensajes detallados en las bibliotecas de dependencia subyacentes.

Para habilitar rápidamente el registro detallado de las bibliotecas cliente de Cloud para Java, crea un archivo logging.properties con el siguiente contenido:

# 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

A continuación, ejecuta tu aplicación con -Djava.util.logging.config.file=path/to/logging.properties como VM argument, no como Program argument.

Si usas IntelliJ, especifica el argumento de VM en Run/Debug Configuration:

Configuración de ejecución/depuración de IntelliJ que muestra dónde especificar los argumentos de la VM

Si la JVM de tu programa se ejecuta con la configuración correcta, verás el registro a nivel FINE en tu consola.

Resultado de ejemplo:

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

Esta es una forma de configurar el nivel de registro. Para obtener más información sobre el uso de la API de Logging de Java, consulta Descripción general de Logging de Java.

Registro de depuración de la biblioteca cliente (SLF4J)

Las bibliotecas cliente de Cloud incluyen el registro de depuración opcional que puede ayudarte a solucionar problemas relacionados con la integración de tu aplicación en la API. Cuando se activa el registro, los eventos clave, como las solicitudes y las respuestas, junto con las cargas útiles de datos y los metadatos, como los encabezados, se registran en el flujo de error estándar.

ADVERTENCIA: El registro de depuración de la biblioteca cliente incluye las cargas útiles de datos en texto sin formato, lo que podría incluir datos sensibles, como PII para ti o tus clientes, claves privadas o cualquier otro dato de seguridad que podría verse comprometido si se filtra. Siempre practica una buena higiene de datos con los registros de tu aplicación y sigue el principio de privilegio mínimo. Google también recomienda que el registro de depuración de la biblioteca cliente se habilite solo de forma temporal durante la depuración activa y no se use de forma permanente en la producción.

Requisitos

Nuestras bibliotecas admiten el registro de depuración con la interfaz SLF4J.

Las bibliotecas cliente y la BOM de bibliotecas no incluyen la dependencia slf4j-api. Deberás configurar las dependencias de registro, incluidas SLF4J y las implementaciones y configuraciones de registro correspondientes, antes de activar el registro de depuración de la biblioteca cliente.

NOTE: Debes tener SLF4J y los proveedores de registro correspondientes (p.ej., logback, log4j2, etcétera) en tu classpath para usar la función de registro de depuración de la biblioteca cliente. De lo contrario, no habrá registro de depuración, incluso si lo habilitas con la variable de entorno.

Habilita el registro con una variable de entorno

El registro de depuración está desactivado de forma predeterminada. El registro de depuración no se activa, a menos que se haga de forma explícita, incluso si se cumplen los requisitos previos.

Para habilitar el registro de depuración de la biblioteca cliente, establece la variable de entorno GOOGLE_SDK_JAVA_LOGGING en true, sin distinción entre mayúsculas y minúsculas. Si no se configura o se establece cualquier otro valor, se inhabilita el registro de depuración de la biblioteca cliente.

Ejemplos de configuraciones de registros

En la documentación, se proporciona un ejemplo de registro de depuración con Logback.

Agrega dependencias de Logback a tu aplicación. Esto incorporará la dependencia de slf4j de forma transitiva. Los usuarios deben consultar la configuración de Logback para obtener las versiones más recientes. Puedes omitir este paso si tu aplicación ya tiene dependencias de Logback en la ruta de clase.

Si usas Maven, haz lo siguiente:

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

Agrega el contenido al archivo de configuración de Logback si deseas ver registros de nivel DEBUG. Puedes omitir este paso si solo necesitas registros de nivel INFO. Para obtener más información, consulta la configuración de 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 no está configurado correctamente

Si ves excepciones relacionadas con ALPN is not configured properly, como las siguientes:

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.

Usa el comprobador de compatibilidad para ver si tu entorno es compatible con los clientes basados en gRPC.

La incompatibilidad puede significar una de las siguientes situaciones:

Soluciona problemas de ClassNotFoundException, NoSuchMethodError y NoClassDefFoundError

Estos errores suelen deberse a que hay varias versiones o versiones conflictivas de la misma dependencia en la ruta de clase. Estos conflictos de dependencia suelen ocurrir con guava o protobuf-java.

Varias fuentes pueden causar conflictos en la ruta de acceso a la clase:

  • Hay varias versiones de la misma dependencia transitiva en el árbol de dependencias.
  • Tu classpath de tiempo de ejecución tiene versiones de dependencias diferentes de las que especificaste en la compilación.

Por ejemplo, si tienes una dependencia directa o transitiva en la versión 19.0 de Guava y google-cloud-java usa la versión 30.0 de Guava, es posible que google-cloud-java use métodos de Guava que no existen en Guava 19.0 y provoquen NoSuchMethodError.

Del mismo modo, si tu ruta de acceso a la clase tiene una versión anterior de protobuf-java, pero google-cloud-java requiere una versión posterior, es posible que veas NoClassDefFoundError que no puede inicializar las clases de google-cloud-java.

Por ejemplo:

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

Valida el conflicto

Verifica el árbol de dependencias para ver si tienes varias versiones de las mismas dependencias:

$ mvn dependency:tree

Busca versiones de dependencias que puedan entrar en conflicto, como guava o protobuf-java.

Si experimentas el error solo durante el tiempo de ejecución, es posible que tu entorno de ejecución esté introduciendo archivos JAR en conflicto en la ruta de acceso de clase del tiempo de ejecución. Un caso típico es que Hadoop, Spark o algún otro software de servidor en el que se ejecuta tu aplicación tengan versiones conflictivas de los archivos JAR netty, guava o protobuf-java en la ruta de clase.

Detecta conflictos durante la compilación

Para detectar errores de vinculación de dependencias en el tiempo de compilación, agrega la regla de aplicación de Linkage Checker en tu 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>

Sin embargo, no hay forma de detectar conflictos de classpath en el tiempo de ejecución. Debes saber perfectamente qué archivos JAR o clases se incluyen en la ruta de acceso de clases del entorno de ejecución, ya que cada entorno de servidor es diferente.

Cómo resolver el conflicto

Existen diferentes estrategias para resolver conflictos, pero debes comprender la causa raíz de los conflictos:

  • Si controlas el árbol de dependencias, actualiza las dependencias en conflicto (por ejemplo, actualiza Guava). Este enfoque es el más sólido, pero puede requerir un esfuerzo significativo y varias versiones de la biblioteca para garantizar la compatibilidad.
  • Si no puedes modificar ni enviar nuevas versiones de tus dependencias, importa com.google.cloud:libraries-bom:25.1.0 (o una versión más reciente) para seleccionar versiones de dependencias coherentes. Este enfoque simplifica la administración de dependencias. Por ejemplo, así puedes depender de versiones coherentes de Guava y com.google.cloud:google-cloud-storage sin establecer explícitamente la versión de ninguna de ellas:
  ...
  <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>
  ...
  • Las notas de la versión de libraries-bom muestran las bibliotecas de dependencias compatibles. Por ejemplo, https://github.com/googleapis/java-cloud-bom/releases/tag/v26.31.0 muestra lo siguiente:

    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
    

    Si examinas el gráfico de dependencias de tu proyecto (mvn dependency:tree -Dverbose, gradle dependencies o sbt dependencyTree), es posible que encuentres que algunas dependencias tienen versiones inesperadas, lo que podría causar conflictos de dependencias.

  • Si cambiar las versiones de las dependencias introduce otros errores, considera sombreado de dependencias que entren en conflicto con las bibliotecas de Java. Google Cloud

    Por ejemplo, para sombrear guava y 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>