From e3877806a0eadc73bd5db43083cd080082d66210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?TATSUNO=20=E2=80=9CTaz=E2=80=9D=20Yasuhiro?= Date: Tue, 1 Sep 2026 18:53:53 +0900 Subject: [PATCH] fix(firestore): support opentelemetry-instrumentation-grpc 2.25.0+ GrpcTelemetry renamed newClientInterceptor to createClientInterceptor in opentelemetry-instrumentation-grpc-1.6 2.25.0-alpha with no deprecated alias, so EnabledTraceUtil resolves the method reflectively. The build only sees the version google-cloud-shared-dependencies manages, so a new job runs the unit tests against one release on each side of the rename. It overrides the managed version with a maven property, the same way the nightly protobuf compatibility check passes -Dprotobuf.version. --- .github/workflows/java-firestore-ci.yaml | 35 ++++++++++++++++ .../firestore/telemetry/EnabledTraceUtil.java | 40 ++++++++++++++++++- .../telemetry/EnabledTraceUtilTest.java | 15 +++++++ 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/.github/workflows/java-firestore-ci.yaml b/.github/workflows/java-firestore-ci.yaml index 04b73937da4a..37471023db09 100644 --- a/.github/workflows/java-firestore-ci.yaml +++ b/.github/workflows/java-firestore-ci.yaml @@ -29,6 +29,7 @@ jobs: runs-on: ubuntu-latest outputs: library: ${{ steps.filter.outputs.library }} + otel_grpc: ${{ steps.filter.outputs.otel_grpc }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: @@ -39,6 +40,9 @@ jobs: filters: | library: - 'java-firestore/**' + otel_grpc: + - 'java-firestore/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/**' + - 'sdk-platform-java/java-shared-dependencies/third-party-dependencies/pom.xml' units: needs: filter if: ${{ needs.filter.outputs.library == 'true' }} @@ -60,6 +64,37 @@ jobs: - run: .kokoro/build.sh env: JOB_TYPE: test + otel-grpc-compatibility: + needs: filter + if: ${{ needs.filter.outputs.otel_grpc == 'true' }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # One release on each side of the createClientInterceptor rename. + otel-grpc-version: ['2.1.0-alpha', '2.25.0-alpha'] + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false + - uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4.8.0 + with: + distribution: temurin + java-version: 11 + cache: maven + - run: .kokoro/build.sh + env: + JOB_TYPE: install + - run: | + mvn test -B -V -ntp \ + -Dclirr.skip \ + -Denforcer.skip \ + -Dmaven.javadoc.skip \ + -Dopentelemetry-grpc-instrumentation.version="${OTEL_GRPC_VERSION}" \ + -T 1C + working-directory: java-firestore + env: + OTEL_GRPC_VERSION: ${{ matrix.otel-grpc-version }} units-java8: needs: filter if: ${{ needs.filter.outputs.library == 'true' }} diff --git a/java-firestore/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/EnabledTraceUtil.java b/java-firestore/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/EnabledTraceUtil.java index 863012b4e5cc..d10be4ab6ce6 100644 --- a/java-firestore/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/EnabledTraceUtil.java +++ b/java-firestore/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/EnabledTraceUtil.java @@ -23,6 +23,7 @@ import com.google.api.core.InternalApi; import com.google.cloud.firestore.FirestoreOptions; import com.google.common.base.Throwables; +import io.grpc.ClientInterceptor; import io.grpc.ManagedChannelBuilder; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; @@ -34,6 +35,9 @@ import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.instrumentation.grpc.v1_6.GrpcTelemetry; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -44,6 +48,13 @@ */ @InternalApi public class EnabledTraceUtil implements TraceUtil { + // opentelemetry-instrumentation-grpc-1.6 2.25.0-alpha renamed newClientInterceptor() to + // createClientInterceptor(). See https://github.com/googleapis/google-cloud-java/issues/13095 + private static final String[] CLIENT_INTERCEPTOR_METHOD_NAMES = { + "createClientInterceptor", "newClientInterceptor" + }; + private static final Method CLIENT_INTERCEPTOR_METHOD = resolveClientInterceptorMethod(); + private final Tracer tracer; private final OpenTelemetry openTelemetry; private final FirestoreOptions firestoreOptions; @@ -73,13 +84,40 @@ public OpenTelemetry getOpenTelemetry() { return openTelemetry; } + @SuppressWarnings("JavaReflectionMemberAccess") + private static Method resolveClientInterceptorMethod() { + for (String methodName : CLIENT_INTERCEPTOR_METHOD_NAMES) { + try { + return GrpcTelemetry.class.getMethod(methodName); + } catch (NoSuchMethodException ignored) { + // Try the next known name. + } + } + throw new IllegalStateException( + "GrpcTelemetry declares none of " + + Arrays.toString(CLIENT_INTERCEPTOR_METHOD_NAMES) + + ". An incompatible opentelemetry-instrumentation-grpc-1.6 version is on the" + + " classpath."); + } + + private static ClientInterceptor createClientInterceptor(GrpcTelemetry grpcTelemetry) { + try { + return (ClientInterceptor) CLIENT_INTERCEPTOR_METHOD.invoke(grpcTelemetry); + } catch (IllegalAccessException e) { + throw new IllegalStateException(e); + } catch (InvocationTargetException e) { + Throwables.throwIfUnchecked(e.getCause()); + throw new IllegalStateException(e.getCause()); + } + } + // The gRPC channel configurator that intercepts gRPC calls for tracing purposes. public class OpenTelemetryGrpcChannelConfigurator implements ApiFunction { @Override public ManagedChannelBuilder apply(ManagedChannelBuilder managedChannelBuilder) { GrpcTelemetry grpcTelemetry = GrpcTelemetry.create(getOpenTelemetry()); - return managedChannelBuilder.intercept(grpcTelemetry.newClientInterceptor()); + return managedChannelBuilder.intercept(createClientInterceptor(grpcTelemetry)); } } diff --git a/java-firestore/google-cloud-firestore/src/test/java/com/google/cloud/firestore/telemetry/EnabledTraceUtilTest.java b/java-firestore/google-cloud-firestore/src/test/java/com/google/cloud/firestore/telemetry/EnabledTraceUtilTest.java index d3fb7e216787..0dddd1ca64df 100644 --- a/java-firestore/google-cloud-firestore/src/test/java/com/google/cloud/firestore/telemetry/EnabledTraceUtilTest.java +++ b/java-firestore/google-cloud-firestore/src/test/java/com/google/cloud/firestore/telemetry/EnabledTraceUtilTest.java @@ -17,8 +17,10 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.api.core.ApiFunction; import com.google.cloud.firestore.FirestoreOpenTelemetryOptions; import com.google.cloud.firestore.FirestoreOptions; +import io.grpc.ManagedChannelBuilder; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.sdk.OpenTelemetrySdk; @@ -111,6 +113,19 @@ public void openTelemetryInstanceRegistersGrpcChannelConfigurator() { assertThat(traceUtil.getChannelConfigurator()).isNotNull(); } + // Fails on an opentelemetry-grpc-1.6 version whose interceptor method name EnabledTraceUtil does + // not handle. The version shared-dependencies does not manage is covered by the + // otel-grpc-compatibility job in java-firestore-ci.yaml. + @Test + public void grpcChannelConfiguratorInterceptsWithGrpcTelemetry() { + OpenTelemetrySdk.builder().buildAndRegisterGlobal(); + ApiFunction configurator = + defaultEnabledTraceUtil().getChannelConfigurator(); + ManagedChannelBuilder builder = ManagedChannelBuilder.forAddress("localhost", 9999); + + assertThat(configurator.apply(builder)).isNotNull(); + } + @Test public void usesEnabledContext() { assertThat(defaultEnabledTraceUtil().currentContext() instanceof EnabledTraceUtil.Context)