Java용 Cloud 클라이언트 라이브러리 문제 해결

이 문서에서는 Java용 Cloud 클라이언트 라이브러리의 로깅 및 일반적인 문제 해결에 대해 간략하게 설명합니다.

로깅

클라이언트 라이브러리의 로깅을 사용 설정하는 방법에는 두 가지가 있습니다. java.util.logging를 사용하거나 SLF4J와 함께 클라이언트 라이브러리 디버그 로깅을 사용하는 것입니다.

java.util.logging 사용

Java용 Cloud 클라이언트 라이브러리는 Java 로깅 API (java.util.logging) 패키지를 사용합니다. 로깅 수준을 구성하면 다음을 비롯한 문제 해결에 도움이 되는 정보가 표시됩니다.

  • 기본 클라이언트-서버 통신의 타이밍입니다.
  • 요청 및 응답 메시지 헤더입니다.
  • 기본 종속 항목 라이브러리의 상세 메시지

Java용 Cloud 클라이언트 라이브러리의 상세 로깅을 빠르게 사용 설정하려면 다음 콘텐츠로 logging.properties 파일을 만드세요.

# 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

그런 다음 Program argument가 아닌 VM argument으로 -Djava.util.logging.config.file=path/to/logging.properties를 사용하여 애플리케이션을 실행합니다.

IntelliJ를 사용하는 경우 실행/디버그 구성에서 VM 인수를 지정합니다.

VM 인수를 지정할 위치를 보여주는 IntelliJ 실행/디버그 구성

프로그램의 JVM이 올바른 구성으로 실행되면 콘솔에 FINE 수준 로깅이 표시됩니다.

출력 예시:

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

로깅 수준을 구성하는 한 가지 방법입니다. Java 로깅 API 사용에 대한 자세한 내용은 Java 로깅 개요를 참고하세요.

클라이언트 라이브러리 디버그 로깅 (SLF4J)

Cloud 클라이언트 라이브러리에는 애플리케이션과 API의 통합 문제를 해결하는 데 도움이 되는 선택적 디버그 로깅이 제공됩니다. 로깅이 활성화되면 요청 및 응답과 같은 주요 이벤트가 데이터 페이로드 및 헤더와 같은 메타데이터와 함께 표준 오류 스트림에 로깅됩니다.

경고: 클라이언트 라이브러리 디버그 로깅에는 데이터 페이로드가 일반 텍스트로 포함되어 있으며, 여기에는 본인 또는 고객의 PII, 비공개 키, 유출 시 보안이 침해될 수 있는 기타 보안 데이터와 같은 민감한 데이터가 포함될 수 있습니다. 항상 애플리케이션 로그를 사용하여 적절한 데이터 정제를 실행하고 최소 액세스 원칙을 따르세요. 또한 Google에서는 클라이언트 라이브러리 디버그 로깅을 활성 디버깅 중에만 일시적으로 사용하고 프로덕션에서는 영구적으로 사용하지 않는 것이 좋습니다.

기본 요건

Google 라이브러리는 SLF4J 인터페이스를 사용한 디버그 로깅을 지원합니다.

클라이언트 라이브러리 및 라이브러리 BOM에 slf4j-api 종속 항목이 포함되지 않습니다. 클라이언트 라이브러리 디버그 로깅을 사용 설정하기 전에 SLF4J 및 해당 로깅 구현과 구성을 포함한 로깅 종속 항목을 설정해야 합니다.

NOTE: 클라이언트 라이브러리 디버그 로깅 기능을 사용하려면 클래스 경로에 SLF4J 및 해당 로깅 제공자(예: logback, log4j2 등)가 있어야 합니다. 그렇지 않으면 환경 변수를 사용하여 사용 설정해도 디버그 로깅이 실행되지 않습니다.

환경 변수로 로깅 사용 설정

디버그 로깅은 기본적으로 사용 중지되어 있습니다. 기본 요건을 충족하더라도 명시적으로 사용 설정하지 않는 한 디버그 로깅이 사용 설정되지 않습니다.

대소문자를 구분하지 않고 환경 변수 GOOGLE_SDK_JAVA_LOGGINGtrue로 설정하여 클라이언트 라이브러리 디버그 로깅을 사용 설정할 수 있습니다. 설정되지 않았거나 다른 값인 경우 클라이언트 라이브러리 디버그 로깅이 사용 중지됩니다.

로깅 설정 예시

이 문서에서는 Logback을 사용한 디버그 로깅의 예를 제공합니다.

애플리케이션에 Logback 종속 항목을 추가합니다. 그러면 slf4j 종속 항목이 간접적으로 가져와집니다. 사용자는 최신 버전에 관해 Logback 설정을 참고해야 합니다. 애플리케이션의 클래스 경로에 이미 logback 종속 항목이 있는 경우 이 단계를 건너뛰어도 됩니다.

Maven을 사용하는 경우:

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

디버그 수준 로그를 보려면 logback 구성 파일에 콘텐츠를 추가합니다. INFO 수준 로그만 필요한 경우 이 단계를 건너뛸 수 있습니다. 자세한 내용은 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이 올바르게 구성되지 않음

다음과 같이 ALPN is not configured properly와 관련된 예외가 표시되는 경우

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.

호환성 검사기를 사용하여 환경이 gRPC 기반 클라이언트와 호환되는지 확인합니다.

비호환성은 다음 중 하나를 의미할 수 있습니다.

ClassNotFoundException, NoSuchMethodError, NoClassDefFoundError 문제 해결

이러한 오류는 클래스 경로에 동일한 종속 항목의 여러 버전이나 충돌하는 버전이 있는 경우에 자주 발생합니다. 이러한 종속 항목 충돌은 guava 또는 protobuf-java에서 자주 발생합니다.

여러 소스로 인해 클래스 경로 충돌이 발생할 수 있습니다.

  • 종속 항목 트리에 동일한 임시 종속 항목이 여러 버전 있습니다.
  • 런타임 클래스 경로에 빌드에 지정한 것과 다른 버전의 종속 항목이 있습니다.

예를 들어 Guava 버전 19.0에 대한 직접 또는 전이 종속 항목이 있고 google-cloud-java이 Guava 버전 30.0을 사용하는 경우 google-cloud-java이 Guava 19.0에 없는 Guava 메서드를 사용하여 NoSuchMethodError이 발생할 수 있습니다.

마찬가지로 클래스 경로에 이전 버전의 protobuf-java이 있지만 google-cloud-java에 최신 버전이 필요한 경우 google-cloud-java 클래스를 초기화하지 못하는 NoClassDefFoundError이 표시될 수 있습니다.

예를 들면 다음과 같습니다.

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

충돌 검증

종속 항목 트리를 확인하여 동일한 종속 항목의 버전이 여러 개 있는지 확인합니다.

$ mvn dependency:tree

guava 또는 protobuf-java과 같이 충돌할 수 있는 종속 항목의 버전을 찾습니다.

런타임 중에만 오류가 발생하는 경우 런타임 환경에서 충돌하는 JAR를 런타임 클래스 경로에 도입하는 것일 수 있습니다. 일반적인 사례는 애플리케이션이 실행되는 Hadoop, Spark 또는 기타 서버 소프트웨어의 클래스 경로에 충돌하는 버전 netty, guava 또는 protobuf-java JAR가 있는 것입니다.

빌드 중 충돌 감지

컴파일 시간에 종속 항목 연결 오류를 감지하려면 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>

하지만 런타임 클래스 경로 충돌을 감지할 방법은 없습니다. 모든 서버 환경이 다르므로 런타임 클래스 경로에 포함된 JAR/클래스를 완전히 알고 있어야 합니다.

충돌 해결

충돌을 해결하는 다양한 전략이 있지만 충돌의 근본 원인을 이해해야 합니다.

  • 종속 항목 트리를 제어하는 경우 충돌하는 종속 항목을 업그레이드합니다 (예: Guava 업그레이드). 이 접근 방식은 가장 강력하지만 호환성을 보장하려면 상당한 노력과 여러 라이브러리 출시가 필요할 수 있습니다.
  • 종속 항목의 새 버전을 수정하고 푸시할 수 없는 경우 com.google.cloud:libraries-bom:25.1.0 (또는 최신 버전)를 가져와 일관된 종속 항목 버전을 선택합니다. 이 접근 방식을 사용하면 종속 항목 관리가 간소화됩니다. 예를 들어 다음은 Guava와 com.google.cloud:google-cloud-storage의 일관된 버전을 명시적으로 설정하지 않고도 이에 종속될 수 있는 방법입니다.
  ...
  <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>
  ...
  • libraries-bom 출시 노트에는 호환되는 종속 항목 라이브러리가 표시됩니다. 예를 들어 https://github.com/googleapis/java-cloud-bom/releases/tag/v26.31.0에는 다음이 표시됩니다.

    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
    

    프로젝트의 종속 항목 그래프(mvn dependency:tree -Dverbose, gradle dependencies 또는 sbt dependencyTree)를 검사하면 일부 종속 항목의 버전이 예상과 달라 종속 항목 충돌이 발생할 수 있습니다.

  • 종속 항목 버전을 변경하여 다른 오류가 발생하는 경우 Google Cloud Java 라이브러리와 충돌하는 종속 항목 셰이딩을 고려하세요.

    예를 들어 guavaprotobuf-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>