From 6b7763c50137b16d631a566af0915b45aaf24238 Mon Sep 17 00:00:00 2001 From: becomeStar Date: Sun, 21 Dec 2025 23:59:44 +0900 Subject: [PATCH 01/11] opentelemetry: Add optional targetAttributeFilter to module Introduce an optional Predicate targetAttributeFilter in the OpenTelemetryMetricsModule to control how grpc.target is recorded in metrics. Also add support in GrpcOpenTelemetry.Builder to allow users to configure this filter when building the module. --- .../grpc/opentelemetry/GrpcOpenTelemetry.java | 20 ++- .../OpenTelemetryMetricsModule.java | 26 +++- .../opentelemetry/GrpcOpenTelemetryTest.java | 12 ++ .../OpenTelemetryMetricsModuleTest.java | 138 ++++++++++++++++++ 4 files changed, 194 insertions(+), 2 deletions(-) diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java index 4341b27daa4..f66b7f035e2 100644 --- a/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java @@ -48,6 +48,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Predicate; +import javax.annotation.Nullable; /** * The entrypoint for OpenTelemetry metrics functionality in gRPC. @@ -97,7 +99,8 @@ private GrpcOpenTelemetry(Builder builder) { this.resource = createMetricInstruments(meter, enableMetrics, disableDefault); this.optionalLabels = ImmutableList.copyOf(builder.optionalLabels); this.openTelemetryMetricsModule = new OpenTelemetryMetricsModule( - STOPWATCH_SUPPLIER, resource, optionalLabels, builder.plugins); + STOPWATCH_SUPPLIER, resource, optionalLabels, builder.plugins, + builder.targetAttributeFilter); this.openTelemetryTracingModule = new OpenTelemetryTracingModule(openTelemetrySdk); this.sink = new OpenTelemetryMetricSink(meter, enableMetrics, disableDefault, optionalLabels); } @@ -141,6 +144,11 @@ Tracer getTracer() { return this.openTelemetryTracingModule.getTracer(); } + @VisibleForTesting + Predicate getTargetAttributeFilter() { + return this.openTelemetryMetricsModule.getTargetAttributeFilter(); + } + /** * Registers GrpcOpenTelemetry globally, applying its configuration to all subsequently created * gRPC channels and servers. @@ -359,6 +367,8 @@ public static class Builder { private final Collection optionalLabels = new ArrayList<>(); private final Map enableMetrics = new HashMap<>(); private boolean disableAll; + @Nullable + private Predicate targetAttributeFilter; private Builder() {} @@ -421,6 +431,14 @@ Builder enableTracing(boolean enable) { return this; } + /** + * Sets an optional filter for the grpc.target attribute. + */ + public Builder targetAttributeFilter(@Nullable Predicate filter) { + this.targetAttributeFilter = filter; + return this; + } + /** * Returns a new {@link GrpcOpenTelemetry} built with the configuration of this {@link * Builder}. diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java index 3e5137e0034..55f1d25b6ca 100644 --- a/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java @@ -56,6 +56,7 @@ import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -95,15 +96,30 @@ final class OpenTelemetryMetricsModule { private final boolean localityEnabled; private final boolean backendServiceEnabled; private final ImmutableList plugins; + @Nullable + private final Predicate targetAttributeFilter; OpenTelemetryMetricsModule(Supplier stopwatchSupplier, OpenTelemetryMetricsResource resource, Collection optionalLabels, List plugins) { + this(stopwatchSupplier, resource, optionalLabels, plugins, null); + } + + OpenTelemetryMetricsModule(Supplier stopwatchSupplier, + OpenTelemetryMetricsResource resource, + Collection optionalLabels, List plugins, + @Nullable Predicate targetAttributeFilter) { this.resource = checkNotNull(resource, "resource"); this.stopwatchSupplier = checkNotNull(stopwatchSupplier, "stopwatchSupplier"); this.localityEnabled = optionalLabels.contains(LOCALITY_KEY.getKey()); this.backendServiceEnabled = optionalLabels.contains(BACKEND_SERVICE_KEY.getKey()); this.plugins = ImmutableList.copyOf(plugins); + this.targetAttributeFilter = targetAttributeFilter; + } + + @VisibleForTesting + Predicate getTargetAttributeFilter() { + return targetAttributeFilter; } /** @@ -124,7 +140,15 @@ ClientInterceptor getClientInterceptor(String target) { pluginBuilder.add(plugin); } } - return new MetricsClientInterceptor(target, pluginBuilder.build()); + String filteredTarget = recordTarget(target); + return new MetricsClientInterceptor(filteredTarget, pluginBuilder.build()); + } + + String recordTarget(String target) { + if (targetAttributeFilter == null) { + return target; + } + return targetAttributeFilter.test(target) ? target : "other"; } static String recordMethodName(String fullMethodName, boolean isGeneratedMethod) { diff --git a/opentelemetry/src/test/java/io/grpc/opentelemetry/GrpcOpenTelemetryTest.java b/opentelemetry/src/test/java/io/grpc/opentelemetry/GrpcOpenTelemetryTest.java index 1ae7b755a48..11c886f3b41 100644 --- a/opentelemetry/src/test/java/io/grpc/opentelemetry/GrpcOpenTelemetryTest.java +++ b/opentelemetry/src/test/java/io/grpc/opentelemetry/GrpcOpenTelemetryTest.java @@ -35,6 +35,7 @@ import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; import io.opentelemetry.sdk.trace.SdkTracerProvider; import java.util.Arrays; +import java.util.function.Predicate; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -130,6 +131,17 @@ public void builderDefaults() { ); } + @Test + public void builderTargetAttributeFilter() { + Predicate filter = t -> t.contains("allowed.com"); + GrpcOpenTelemetry module = GrpcOpenTelemetry.newBuilder() + .targetAttributeFilter(filter) + .build(); + + assertThat(module.getTargetAttributeFilter()) + .isSameInstanceAs(filter); + } + @Test public void enableDisableMetrics() { GrpcOpenTelemetry.Builder builder = GrpcOpenTelemetry.newBuilder(); diff --git a/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricsModuleTest.java b/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricsModuleTest.java index 58759294fca..c4193b40006 100644 --- a/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricsModuleTest.java +++ b/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricsModuleTest.java @@ -75,6 +75,7 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Predicate; import javax.annotation.Nullable; import org.junit.After; import org.junit.Before; @@ -1667,12 +1668,149 @@ public void serverBaggagePropagationToMetrics() { assertEquals("67", capturedBaggage.getEntryValue("user-id")); } + @Test + public void targetAttributeFilter_notSet_usesOriginalTarget() { + // Test that when no filter is set, the original target is used + String target = "dns:///example.com"; + OpenTelemetryMetricsResource resource = GrpcOpenTelemetry.createMetricInstruments(testMeter, + enabledMetricsMap, disableDefaultMetrics); + OpenTelemetryMetricsModule module = newOpenTelemetryMetricsModule(resource); + + Channel interceptedChannel = + ClientInterceptors.intercept( + grpcServerRule.getChannel(), module.getClientInterceptor(target)); + ClientCall call; + call = interceptedChannel.newCall(method, CALL_OPTIONS); + + // Make the call + Metadata headers = new Metadata(); + call.start(mockClientCallListener, headers); + + // End the call + call.halfClose(); + call.request(1); + + io.opentelemetry.api.common.Attributes attributes = io.opentelemetry.api.common.Attributes.of( + TARGET_KEY, target, + METHOD_KEY, method.getFullMethodName()); + + assertThat(openTelemetryTesting.getMetrics()) + .anySatisfy( + metric -> + assertThat(metric) + .hasInstrumentationScope(InstrumentationScopeInfo.create( + OpenTelemetryConstants.INSTRUMENTATION_SCOPE)) + .hasName(CLIENT_ATTEMPT_COUNT_INSTRUMENT_NAME) + .hasUnit("{attempt}") + .hasLongSumSatisfying( + longSum -> + longSum + .hasPointsSatisfying( + point -> + point + .hasAttributes(attributes)))); + } + + @Test + public void targetAttributeFilter_allowsTarget_usesOriginalTarget() { + // Test that when filter allows the target, the original target is used + String target = "dns:///example.com"; + Predicate targetFilter = t -> t.contains("example.com"); + OpenTelemetryMetricsResource resource = GrpcOpenTelemetry.createMetricInstruments(testMeter, + enabledMetricsMap, disableDefaultMetrics); + OpenTelemetryMetricsModule module = newOpenTelemetryMetricsModule(resource, targetFilter); + + Channel interceptedChannel = + ClientInterceptors.intercept( + grpcServerRule.getChannel(), module.getClientInterceptor(target)); + ClientCall call; + call = interceptedChannel.newCall(method, CALL_OPTIONS); + + // Make the call + Metadata headers = new Metadata(); + call.start(mockClientCallListener, headers); + + // End the call + call.halfClose(); + call.request(1); + + io.opentelemetry.api.common.Attributes attributes = io.opentelemetry.api.common.Attributes.of( + TARGET_KEY, target, + METHOD_KEY, method.getFullMethodName()); + + assertThat(openTelemetryTesting.getMetrics()) + .anySatisfy( + metric -> + assertThat(metric) + .hasInstrumentationScope(InstrumentationScopeInfo.create( + OpenTelemetryConstants.INSTRUMENTATION_SCOPE)) + .hasName(CLIENT_ATTEMPT_COUNT_INSTRUMENT_NAME) + .hasUnit("{attempt}") + .hasLongSumSatisfying( + longSum -> + longSum + .hasPointsSatisfying( + point -> + point + .hasAttributes(attributes)))); + } + + @Test + public void targetAttributeFilter_rejectsTarget_mapsToOther() { + // Test that when filter rejects the target, it is mapped to "other" + String target = "dns:///example.com"; + Predicate targetFilter = t -> t.contains("allowed.com"); + OpenTelemetryMetricsResource resource = GrpcOpenTelemetry.createMetricInstruments(testMeter, + enabledMetricsMap, disableDefaultMetrics); + OpenTelemetryMetricsModule module = newOpenTelemetryMetricsModule(resource, targetFilter); + + Channel interceptedChannel = + ClientInterceptors.intercept( + grpcServerRule.getChannel(), module.getClientInterceptor(target)); + ClientCall call; + call = interceptedChannel.newCall(method, CALL_OPTIONS); + + // Make the call + Metadata headers = new Metadata(); + call.start(mockClientCallListener, headers); + + // End the call + call.halfClose(); + call.request(1); + + io.opentelemetry.api.common.Attributes attributes = io.opentelemetry.api.common.Attributes.of( + TARGET_KEY, "other", + METHOD_KEY, method.getFullMethodName()); + + assertThat(openTelemetryTesting.getMetrics()) + .anySatisfy( + metric -> + assertThat(metric) + .hasInstrumentationScope(InstrumentationScopeInfo.create( + OpenTelemetryConstants.INSTRUMENTATION_SCOPE)) + .hasName(CLIENT_ATTEMPT_COUNT_INSTRUMENT_NAME) + .hasUnit("{attempt}") + .hasLongSumSatisfying( + longSum -> + longSum + .hasPointsSatisfying( + point -> + point + .hasAttributes(attributes)))); + } + private OpenTelemetryMetricsModule newOpenTelemetryMetricsModule( OpenTelemetryMetricsResource resource) { return new OpenTelemetryMetricsModule( fakeClock.getStopwatchSupplier(), resource, emptyList(), emptyList()); } + private OpenTelemetryMetricsModule newOpenTelemetryMetricsModule( + OpenTelemetryMetricsResource resource, Predicate filter) { + return new OpenTelemetryMetricsModule( + fakeClock.getStopwatchSupplier(), resource, emptyList(), emptyList(), filter); + } + static class CallInfo extends ServerCallInfo { private final MethodDescriptor methodDescriptor; private final Attributes attributes; From 1a0453f4da430caebafa42c7c250bb5fdc1c6d50 Mon Sep 17 00:00:00 2001 From: becomeStar Date: Wed, 24 Dec 2025 22:23:39 +0900 Subject: [PATCH 02/11] opentelemetry: Add Javadoc for target attribute filter --- .../main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java | 7 ++++++- .../io/grpc/opentelemetry/OpenTelemetryMetricsModule.java | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java index f66b7f035e2..a3f88605f5d 100644 --- a/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java @@ -432,7 +432,12 @@ Builder enableTracing(boolean enable) { } /** - * Sets an optional filter for the grpc.target attribute. + * Sets an optional filter to control recording of the {@code grpc.target} metric attribute. + * + *

If the predicate returns {@code true}, the original target is recorded. Otherwise, + * the target is recorded as {@code "other"} to limit metric cardinality. + * + *

If unset, all targets are recorded as-is. */ public Builder targetAttributeFilter(@Nullable Predicate filter) { this.targetAttributeFilter = filter; diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java index 55f1d25b6ca..bbc83ed3a81 100644 --- a/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java @@ -69,6 +69,10 @@ * tracer. It's the tracer that reports per-attempt stats, and the factory that reports the stats * of the overall RPC, such as RETRIES_PER_CALL, to OpenTelemetry. * + *

This module optionally applies a target attribute filter to limit the cardinality of + * the {@code grpc.target} attribute in client-side metrics by mapping disallowed targets + * to a stable placeholder value. + * *

On the server-side, there is only one ServerStream per each ServerCall, and ServerStream * starts earlier than the ServerCall. Therefore, only one tracer is created per stream/call, and * it's the tracer that reports the summary to OpenTelemetry. From c9d19428eb68a19f04c41ecc5019e76f697fa187 Mon Sep 17 00:00:00 2001 From: becomeStar Date: Wed, 24 Dec 2025 22:46:39 +0900 Subject: [PATCH 03/11] opentelemetry: Simplify ClientCall initialization in tests --- .../OpenTelemetryMetricsModuleTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricsModuleTest.java b/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricsModuleTest.java index c4193b40006..be8ccbd6aea 100644 --- a/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricsModuleTest.java +++ b/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricsModuleTest.java @@ -1679,8 +1679,8 @@ public void targetAttributeFilter_notSet_usesOriginalTarget() { Channel interceptedChannel = ClientInterceptors.intercept( grpcServerRule.getChannel(), module.getClientInterceptor(target)); - ClientCall call; - call = interceptedChannel.newCall(method, CALL_OPTIONS); + + ClientCall call = interceptedChannel.newCall(method, CALL_OPTIONS); // Make the call Metadata headers = new Metadata(); @@ -1723,8 +1723,8 @@ public void targetAttributeFilter_allowsTarget_usesOriginalTarget() { Channel interceptedChannel = ClientInterceptors.intercept( grpcServerRule.getChannel(), module.getClientInterceptor(target)); - ClientCall call; - call = interceptedChannel.newCall(method, CALL_OPTIONS); + + ClientCall call = interceptedChannel.newCall(method, CALL_OPTIONS); // Make the call Metadata headers = new Metadata(); @@ -1767,8 +1767,8 @@ public void targetAttributeFilter_rejectsTarget_mapsToOther() { Channel interceptedChannel = ClientInterceptors.intercept( grpcServerRule.getChannel(), module.getClientInterceptor(target)); - ClientCall call; - call = interceptedChannel.newCall(method, CALL_OPTIONS); + + ClientCall call = interceptedChannel.newCall(method, CALL_OPTIONS); // Make the call Metadata headers = new Metadata(); From f14d91df0ac0ea2c7e3b8d3cb6e618f0c3d458ef Mon Sep 17 00:00:00 2001 From: becomeStar Date: Thu, 25 Dec 2025 22:58:33 +0900 Subject: [PATCH 04/11] opentelemetry: Rerun flaky test From 0aae4d531c59c5875b2f5c9b1c65f0992599ab0a Mon Sep 17 00:00:00 2001 From: becomeStar Date: Fri, 26 Dec 2025 00:33:27 +0900 Subject: [PATCH 05/11] opentelemetry: Rerun flaky test From 8468d2f53d175598a71e79037cacf5d3c5a70318 Mon Sep 17 00:00:00 2001 From: becomeStar Date: Wed, 31 Dec 2025 17:08:43 +0900 Subject: [PATCH 06/11] opentelemetry: Add null safety to target filtering logic --- .../java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java index bbc83ed3a81..c2cec825dba 100644 --- a/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java @@ -149,7 +149,7 @@ ClientInterceptor getClientInterceptor(String target) { } String recordTarget(String target) { - if (targetAttributeFilter == null) { + if (targetAttributeFilter == null || target == null) { return target; } return targetAttributeFilter.test(target) ? target : "other"; From 92038a08971250c398ea2affe47ebc731787b641 Mon Sep 17 00:00:00 2001 From: becomeStar Date: Sat, 3 Jan 2026 16:34:01 +0900 Subject: [PATCH 07/11] opentelemetry: Use internal interface for target filter Replace Predicate with a package-private TargetFilter interface in fields to ensure compatibility with Android API levels < 24. This prevents potential runtime issues for Android users who do not use the filtering feature. Mark the API as experimental and link the tracking issue. --- .../grpc/opentelemetry/GrpcOpenTelemetry.java | 23 +++++++++++++++---- .../OpenTelemetryMetricsModule.java | 8 +++---- .../opentelemetry/GrpcOpenTelemetryTest.java | 11 +++++---- .../OpenTelemetryMetricsModuleTest.java | 18 +++++++++++---- 4 files changed, 42 insertions(+), 18 deletions(-) diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java index a3f88605f5d..907dbc194cc 100644 --- a/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java @@ -100,7 +100,7 @@ private GrpcOpenTelemetry(Builder builder) { this.optionalLabels = ImmutableList.copyOf(builder.optionalLabels); this.openTelemetryMetricsModule = new OpenTelemetryMetricsModule( STOPWATCH_SUPPLIER, resource, optionalLabels, builder.plugins, - builder.targetAttributeFilter); + builder.targetFilter); this.openTelemetryTracingModule = new OpenTelemetryTracingModule(openTelemetrySdk); this.sink = new OpenTelemetryMetricSink(meter, enableMetrics, disableDefault, optionalLabels); } @@ -145,7 +145,7 @@ Tracer getTracer() { } @VisibleForTesting - Predicate getTargetAttributeFilter() { + TargetFilter getTargetAttributeFilter() { return this.openTelemetryMetricsModule.getTargetAttributeFilter(); } @@ -357,6 +357,13 @@ static boolean isMetricEnabled(String metricName, Map enableMet && !disableDefault; } + /** + * Internal interface to avoid storing a {@link java.util.function.Predicate} directly, ensuring + * compatibility with Android devices (API level < 24) that do not use library desugaring. + */ + interface TargetFilter { + boolean test(String target); + } /** * Builder for configuring {@link GrpcOpenTelemetry}. @@ -368,7 +375,7 @@ public static class Builder { private final Map enableMetrics = new HashMap<>(); private boolean disableAll; @Nullable - private Predicate targetAttributeFilter; + private TargetFilter targetFilter; private Builder() {} @@ -432,15 +439,21 @@ Builder enableTracing(boolean enable) { } /** - * Sets an optional filter to control recording of the {@code grpc.target} metric attribute. + * Sets an optional filter to control recording of the {@code grpc.target} metric + * attribute. * *

If the predicate returns {@code true}, the original target is recorded. Otherwise, * the target is recorded as {@code "other"} to limit metric cardinality. * *

If unset, all targets are recorded as-is. */ + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/12595") public Builder targetAttributeFilter(@Nullable Predicate filter) { - this.targetAttributeFilter = filter; + if (filter == null) { + this.targetFilter = null; + } else { + this.targetFilter = filter::test; + } return this; } diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java index c2cec825dba..b05884305dc 100644 --- a/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java @@ -45,6 +45,7 @@ import io.grpc.Status; import io.grpc.Status.Code; import io.grpc.StreamTracer; +import io.grpc.opentelemetry.GrpcOpenTelemetry.TargetFilter; import io.opentelemetry.api.baggage.Baggage; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; @@ -56,7 +57,6 @@ import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLongFieldUpdater; -import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -101,7 +101,7 @@ final class OpenTelemetryMetricsModule { private final boolean backendServiceEnabled; private final ImmutableList plugins; @Nullable - private final Predicate targetAttributeFilter; + private final TargetFilter targetAttributeFilter; OpenTelemetryMetricsModule(Supplier stopwatchSupplier, OpenTelemetryMetricsResource resource, @@ -112,7 +112,7 @@ final class OpenTelemetryMetricsModule { OpenTelemetryMetricsModule(Supplier stopwatchSupplier, OpenTelemetryMetricsResource resource, Collection optionalLabels, List plugins, - @Nullable Predicate targetAttributeFilter) { + @Nullable TargetFilter targetAttributeFilter) { this.resource = checkNotNull(resource, "resource"); this.stopwatchSupplier = checkNotNull(stopwatchSupplier, "stopwatchSupplier"); this.localityEnabled = optionalLabels.contains(LOCALITY_KEY.getKey()); @@ -122,7 +122,7 @@ final class OpenTelemetryMetricsModule { } @VisibleForTesting - Predicate getTargetAttributeFilter() { + TargetFilter getTargetAttributeFilter() { return targetAttributeFilter; } diff --git a/opentelemetry/src/test/java/io/grpc/opentelemetry/GrpcOpenTelemetryTest.java b/opentelemetry/src/test/java/io/grpc/opentelemetry/GrpcOpenTelemetryTest.java index 11c886f3b41..16cb02c61c2 100644 --- a/opentelemetry/src/test/java/io/grpc/opentelemetry/GrpcOpenTelemetryTest.java +++ b/opentelemetry/src/test/java/io/grpc/opentelemetry/GrpcOpenTelemetryTest.java @@ -29,13 +29,13 @@ import io.grpc.MetricSink; import io.grpc.ServerBuilder; import io.grpc.internal.GrpcUtil; +import io.grpc.opentelemetry.GrpcOpenTelemetry.TargetFilter; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; import io.opentelemetry.sdk.trace.SdkTracerProvider; import java.util.Arrays; -import java.util.function.Predicate; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -133,13 +133,14 @@ public void builderDefaults() { @Test public void builderTargetAttributeFilter() { - Predicate filter = t -> t.contains("allowed.com"); GrpcOpenTelemetry module = GrpcOpenTelemetry.newBuilder() - .targetAttributeFilter(filter) + .targetAttributeFilter(t -> t.contains("allowed.com")) .build(); - assertThat(module.getTargetAttributeFilter()) - .isSameInstanceAs(filter); + TargetFilter internalFilter = module.getTargetAttributeFilter(); + + assertThat(internalFilter.test("allowed.com")).isTrue(); + assertThat(internalFilter.test("example.com")).isFalse(); } @Test diff --git a/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricsModuleTest.java b/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricsModuleTest.java index be8ccbd6aea..19f82081373 100644 --- a/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricsModuleTest.java +++ b/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricsModuleTest.java @@ -51,6 +51,7 @@ import io.grpc.inprocess.InProcessChannelBuilder; import io.grpc.inprocess.InProcessServerBuilder; import io.grpc.internal.FakeClock; +import io.grpc.opentelemetry.GrpcOpenTelemetry.TargetFilter; import io.grpc.opentelemetry.OpenTelemetryMetricsModule.CallAttemptsTracerFactory; import io.grpc.opentelemetry.internal.OpenTelemetryConstants; import io.grpc.stub.MetadataUtils; @@ -75,7 +76,6 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Predicate; import javax.annotation.Nullable; import org.junit.After; import org.junit.Before; @@ -1715,7 +1715,12 @@ public void targetAttributeFilter_notSet_usesOriginalTarget() { public void targetAttributeFilter_allowsTarget_usesOriginalTarget() { // Test that when filter allows the target, the original target is used String target = "dns:///example.com"; - Predicate targetFilter = t -> t.contains("example.com"); + TargetFilter targetFilter = new TargetFilter() { + @Override + public boolean test(String target) { + return target.contains("example.com"); + } + }; OpenTelemetryMetricsResource resource = GrpcOpenTelemetry.createMetricInstruments(testMeter, enabledMetricsMap, disableDefaultMetrics); OpenTelemetryMetricsModule module = newOpenTelemetryMetricsModule(resource, targetFilter); @@ -1759,7 +1764,12 @@ public void targetAttributeFilter_allowsTarget_usesOriginalTarget() { public void targetAttributeFilter_rejectsTarget_mapsToOther() { // Test that when filter rejects the target, it is mapped to "other" String target = "dns:///example.com"; - Predicate targetFilter = t -> t.contains("allowed.com"); + TargetFilter targetFilter = new TargetFilter() { + @Override + public boolean test(String target) { + return target.contains("allowed.com"); + } + }; OpenTelemetryMetricsResource resource = GrpcOpenTelemetry.createMetricInstruments(testMeter, enabledMetricsMap, disableDefaultMetrics); OpenTelemetryMetricsModule module = newOpenTelemetryMetricsModule(resource, targetFilter); @@ -1806,7 +1816,7 @@ private OpenTelemetryMetricsModule newOpenTelemetryMetricsModule( } private OpenTelemetryMetricsModule newOpenTelemetryMetricsModule( - OpenTelemetryMetricsResource resource, Predicate filter) { + OpenTelemetryMetricsResource resource, TargetFilter filter) { return new OpenTelemetryMetricsModule( fakeClock.getStopwatchSupplier(), resource, emptyList(), emptyList(), filter); } From 8a7a7fff41b5cb82dcf17d8043819dbfa5506d3e Mon Sep 17 00:00:00 2001 From: becomeStar Date: Sat, 3 Jan 2026 17:07:06 +0900 Subject: [PATCH 08/11] opentelemetry: Avoid method reference for better Android compatibility --- .../main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java index 907dbc194cc..434b4812a9e 100644 --- a/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java @@ -452,7 +452,12 @@ public Builder targetAttributeFilter(@Nullable Predicate filter) { if (filter == null) { this.targetFilter = null; } else { - this.targetFilter = filter::test; + this.targetFilter = new TargetFilter() { + @Override + public boolean test(String target) { + return filter.test(target); + } + }; } return this; } From f8f68d478b8f484e825674df4dd7e3333ba9852a Mon Sep 17 00:00:00 2001 From: becomeStar Date: Sat, 3 Jan 2026 17:57:13 +0900 Subject: [PATCH 09/11] opentelemetry: Add @IgnoreJRERequirement for Android CI compatibility --- opentelemetry/build.gradle | 3 ++- .../src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/opentelemetry/build.gradle b/opentelemetry/build.gradle index c856ad8dcb9..5c801e23618 100644 --- a/opentelemetry/build.gradle +++ b/opentelemetry/build.gradle @@ -12,7 +12,8 @@ dependencies { implementation libraries.guava, project(':grpc-core'), libraries.opentelemetry.api, - libraries.auto.value.annotations + libraries.auto.value.annotations, + libraries.animalsniffer.annotations testImplementation project(':grpc-testing'), project(':grpc-testing-proto'), diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java index 434b4812a9e..9986257baaa 100644 --- a/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java @@ -50,6 +50,7 @@ import java.util.Map; import java.util.function.Predicate; import javax.annotation.Nullable; +import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; /** * The entrypoint for OpenTelemetry metrics functionality in gRPC. @@ -448,6 +449,7 @@ Builder enableTracing(boolean enable) { *

If unset, all targets are recorded as-is. */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/12595") + @IgnoreJRERequirement public Builder targetAttributeFilter(@Nullable Predicate filter) { if (filter == null) { this.targetFilter = null; From d227ef9bb3effbe0c42f6b771ef0df8f90b12989 Mon Sep 17 00:00:00 2001 From: becomeStar Date: Sat, 3 Jan 2026 19:18:36 +0900 Subject: [PATCH 10/11] opentelemetry: Rerun ci test From 3e4366f2570b329df0f43517e4ca66561f795498 Mon Sep 17 00:00:00 2001 From: becomeStar Date: Tue, 6 Jan 2026 12:15:48 +0900 Subject: [PATCH 11/11] opentelemetry: Replace anonymous classes with lambdas/method references for TargetFilter --- .../grpc/opentelemetry/GrpcOpenTelemetry.java | 7 +------ .../OpenTelemetryMetricsModuleTest.java | 18 ++++-------------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java index 9986257baaa..6904340ac74 100644 --- a/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java @@ -454,12 +454,7 @@ public Builder targetAttributeFilter(@Nullable Predicate filter) { if (filter == null) { this.targetFilter = null; } else { - this.targetFilter = new TargetFilter() { - @Override - public boolean test(String target) { - return filter.test(target); - } - }; + this.targetFilter = filter::test; } return this; } diff --git a/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricsModuleTest.java b/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricsModuleTest.java index 19f82081373..391f94cefea 100644 --- a/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricsModuleTest.java +++ b/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricsModuleTest.java @@ -1715,15 +1715,10 @@ public void targetAttributeFilter_notSet_usesOriginalTarget() { public void targetAttributeFilter_allowsTarget_usesOriginalTarget() { // Test that when filter allows the target, the original target is used String target = "dns:///example.com"; - TargetFilter targetFilter = new TargetFilter() { - @Override - public boolean test(String target) { - return target.contains("example.com"); - } - }; OpenTelemetryMetricsResource resource = GrpcOpenTelemetry.createMetricInstruments(testMeter, enabledMetricsMap, disableDefaultMetrics); - OpenTelemetryMetricsModule module = newOpenTelemetryMetricsModule(resource, targetFilter); + OpenTelemetryMetricsModule module = newOpenTelemetryMetricsModule(resource, + t -> t.contains("example.com")); Channel interceptedChannel = ClientInterceptors.intercept( @@ -1764,15 +1759,10 @@ public boolean test(String target) { public void targetAttributeFilter_rejectsTarget_mapsToOther() { // Test that when filter rejects the target, it is mapped to "other" String target = "dns:///example.com"; - TargetFilter targetFilter = new TargetFilter() { - @Override - public boolean test(String target) { - return target.contains("allowed.com"); - } - }; OpenTelemetryMetricsResource resource = GrpcOpenTelemetry.createMetricInstruments(testMeter, enabledMetricsMap, disableDefaultMetrics); - OpenTelemetryMetricsModule module = newOpenTelemetryMetricsModule(resource, targetFilter); + OpenTelemetryMetricsModule module = newOpenTelemetryMetricsModule(resource, + t -> t.contains("allowed.com")); Channel interceptedChannel = ClientInterceptors.intercept(