• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #19459

16 Sep 2024 09:43PM UTC coverage: 84.566% (+0.01%) from 84.555%
#19459

push

github

web-flow
Otel server context interceptor (#11500)

Add opentelemetry tracing API, guarded by environmental variable(disabled by default).
Use server interceptor to explicitly propagate span to the application thread.

33627 of 39764 relevant lines covered (84.57%)

0.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.66
/../opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java
1
/*
2
 * Copyright 2023 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc.opentelemetry;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20
import static io.grpc.internal.GrpcUtil.IMPLEMENTATION_VERSION;
21
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.LATENCY_BUCKETS;
22
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.SIZE_BUCKETS;
23

24
import com.google.common.annotations.VisibleForTesting;
25
import com.google.common.base.Stopwatch;
26
import com.google.common.base.Supplier;
27
import com.google.common.collect.ImmutableList;
28
import com.google.common.collect.ImmutableMap;
29
import io.grpc.ExperimentalApi;
30
import io.grpc.InternalConfigurator;
31
import io.grpc.InternalConfiguratorRegistry;
32
import io.grpc.InternalManagedChannelBuilder;
33
import io.grpc.ManagedChannelBuilder;
34
import io.grpc.MetricSink;
35
import io.grpc.ServerBuilder;
36
import io.grpc.internal.GrpcUtil;
37
import io.grpc.opentelemetry.internal.OpenTelemetryConstants;
38
import io.opentelemetry.api.OpenTelemetry;
39
import io.opentelemetry.api.metrics.Meter;
40
import io.opentelemetry.api.metrics.MeterProvider;
41
import io.opentelemetry.api.trace.Tracer;
42
import java.util.ArrayList;
43
import java.util.Collection;
44
import java.util.Collections;
45
import java.util.HashMap;
46
import java.util.List;
47
import java.util.Map;
48

49
/**
50
 *  The entrypoint for OpenTelemetry metrics functionality in gRPC.
51
 *
52
 *  <p>GrpcOpenTelemetry uses {@link io.opentelemetry.api.OpenTelemetry} APIs for instrumentation.
53
 *  When no SDK is explicitly added no telemetry data will be collected. See
54
 *  {@code io.opentelemetry.sdk.OpenTelemetrySdk} for information on how to construct the SDK.
55
 *
56
 */
57
public final class GrpcOpenTelemetry {
58

59
  private static final Supplier<Stopwatch> STOPWATCH_SUPPLIER = new Supplier<Stopwatch>() {
1✔
60
    @Override
61
    public Stopwatch get() {
62
      return Stopwatch.createUnstarted();
1✔
63
    }
64
  };
65

66
  @VisibleForTesting
67
  static boolean ENABLE_OTEL_TRACING = GrpcUtil.getFlag("GRPC_EXPERIMENTAL_ENABLE_OTEL_TRACING",
1✔
68
      false);
69

70
  private final OpenTelemetry openTelemetrySdk;
71
  private final MeterProvider meterProvider;
72
  private final Meter meter;
73
  private final Map<String, Boolean> enableMetrics;
74
  private final boolean disableDefault;
75
  private final OpenTelemetryMetricsResource resource;
76
  private final OpenTelemetryMetricsModule openTelemetryMetricsModule;
77
  private final OpenTelemetryTracingModule openTelemetryTracingModule;
78
  private final List<String> optionalLabels;
79
  private final MetricSink sink;
80

81
  public static Builder newBuilder() {
82
    return new Builder();
1✔
83
  }
84

85
  private GrpcOpenTelemetry(Builder builder) {
1✔
86
    this.openTelemetrySdk = checkNotNull(builder.openTelemetrySdk, "openTelemetrySdk");
1✔
87
    this.meterProvider = checkNotNull(openTelemetrySdk.getMeterProvider(), "meterProvider");
1✔
88
    this.meter = this.meterProvider
1✔
89
        .meterBuilder(OpenTelemetryConstants.INSTRUMENTATION_SCOPE)
1✔
90
        .setInstrumentationVersion(IMPLEMENTATION_VERSION)
1✔
91
        .build();
1✔
92
    this.enableMetrics = ImmutableMap.copyOf(builder.enableMetrics);
1✔
93
    this.disableDefault = builder.disableAll;
1✔
94
    this.resource = createMetricInstruments(meter, enableMetrics, disableDefault);
1✔
95
    this.optionalLabels = ImmutableList.copyOf(builder.optionalLabels);
1✔
96
    this.openTelemetryMetricsModule = new OpenTelemetryMetricsModule(
1✔
97
        STOPWATCH_SUPPLIER, resource, optionalLabels, builder.plugins);
1✔
98
    this.openTelemetryTracingModule = new OpenTelemetryTracingModule(openTelemetrySdk);
1✔
99
    this.sink = new OpenTelemetryMetricSink(meter, enableMetrics, disableDefault, optionalLabels);
1✔
100
  }
1✔
101

102
  @VisibleForTesting
103
  OpenTelemetry getOpenTelemetryInstance() {
104
    return this.openTelemetrySdk;
1✔
105
  }
106

107
  @VisibleForTesting
108
  MeterProvider getMeterProvider() {
109
    return this.meterProvider;
1✔
110
  }
111

112
  @VisibleForTesting
113
  Meter getMeter() {
114
    return this.meter;
1✔
115
  }
116

117
  @VisibleForTesting
118
  OpenTelemetryMetricsResource getResource() {
119
    return this.resource;
×
120
  }
121

122
  @VisibleForTesting
123
  Map<String, Boolean> getEnableMetrics() {
124
    return this.enableMetrics;
1✔
125
  }
126

127
  @VisibleForTesting
128
  List<String> getOptionalLabels() {
129
    return optionalLabels;
1✔
130
  }
131

132
  MetricSink getSink() {
133
    return sink;
1✔
134
  }
135

136
  @VisibleForTesting
137
  Tracer getTracer() {
138
    return this.openTelemetryTracingModule.getTracer();
1✔
139
  }
140

141
  /**
142
   * Registers GrpcOpenTelemetry globally, applying its configuration to all subsequently created
143
   * gRPC channels and servers.
144
   */
145
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/10591")
146
  public void registerGlobal() {
147
    InternalConfiguratorRegistry.setConfigurators(Collections.singletonList(
×
148
        new InternalConfigurator() {
×
149
          @Override
150
          public void configureChannelBuilder(ManagedChannelBuilder<?> channelBuilder) {
151
            GrpcOpenTelemetry.this.configureChannelBuilder(channelBuilder);
×
152
          }
×
153

154
          @Override
155
          public void configureServerBuilder(ServerBuilder<?> serverBuilder) {
156
            GrpcOpenTelemetry.this.configureServerBuilder(serverBuilder);
×
157
          }
×
158
        }));
159
  }
×
160

161
  /**
162
   * Configures the given {@link ManagedChannelBuilder} with OpenTelemetry metrics instrumentation.
163
   */
164
  public void configureChannelBuilder(ManagedChannelBuilder<?> builder) {
165
    InternalManagedChannelBuilder.addMetricSink(builder, sink);
1✔
166
    InternalManagedChannelBuilder.interceptWithTarget(
1✔
167
        builder, openTelemetryMetricsModule::getClientInterceptor);
168
    if (ENABLE_OTEL_TRACING) {
1✔
169
      builder.intercept(openTelemetryTracingModule.getClientInterceptor());
1✔
170
    }
171
  }
1✔
172

173
  /**
174
   * Configures the given {@link ServerBuilder} with OpenTelemetry metrics instrumentation.
175
   *
176
   * @param serverBuilder the server builder to configure
177
   */
178
  public void configureServerBuilder(ServerBuilder<?> serverBuilder) {
179
    serverBuilder.addStreamTracerFactory(openTelemetryMetricsModule.getServerTracerFactory());
1✔
180
    if (ENABLE_OTEL_TRACING) {
1✔
181
      serverBuilder.addStreamTracerFactory(
1✔
182
          openTelemetryTracingModule.getServerTracerFactory());
1✔
183
      serverBuilder.intercept(openTelemetryTracingModule.getServerSpanPropagationInterceptor());
1✔
184
    }
185
  }
1✔
186

187
  @VisibleForTesting
188
  static OpenTelemetryMetricsResource createMetricInstruments(Meter meter,
189
      Map<String, Boolean> enableMetrics, boolean disableDefault) {
190
    OpenTelemetryMetricsResource.Builder builder = OpenTelemetryMetricsResource.builder();
1✔
191

192
    if (isMetricEnabled("grpc.client.call.duration", enableMetrics, disableDefault)) {
1✔
193
      builder.clientCallDurationCounter(
1✔
194
          meter.histogramBuilder("grpc.client.call.duration")
1✔
195
              .setUnit("s")
1✔
196
              .setDescription(
1✔
197
                  "Time taken by gRPC to complete an RPC from application's perspective")
198
              .setExplicitBucketBoundariesAdvice(LATENCY_BUCKETS)
1✔
199
              .build());
1✔
200
    }
201

202
    if (isMetricEnabled("grpc.client.attempt.started", enableMetrics, disableDefault)) {
1✔
203
      builder.clientAttemptCountCounter(
1✔
204
          meter.counterBuilder("grpc.client.attempt.started")
1✔
205
              .setUnit("{attempt}")
1✔
206
              .setDescription("Number of client call attempts started")
1✔
207
              .build());
1✔
208
    }
209

210
    if (isMetricEnabled("grpc.client.attempt.duration", enableMetrics, disableDefault)) {
1✔
211
      builder.clientAttemptDurationCounter(
1✔
212
          meter.histogramBuilder(
1✔
213
                  "grpc.client.attempt.duration")
214
              .setUnit("s")
1✔
215
              .setDescription("Time taken to complete a client call attempt")
1✔
216
              .setExplicitBucketBoundariesAdvice(LATENCY_BUCKETS)
1✔
217
              .build());
1✔
218
    }
219

220
    if (isMetricEnabled("grpc.client.attempt.sent_total_compressed_message_size", enableMetrics,
1✔
221
        disableDefault)) {
222
      builder.clientTotalSentCompressedMessageSizeCounter(
1✔
223
          meter.histogramBuilder(
1✔
224
                  "grpc.client.attempt.sent_total_compressed_message_size")
225
              .setUnit("By")
1✔
226
              .setDescription("Compressed message bytes sent per client call attempt")
1✔
227
              .ofLongs()
1✔
228
              .setExplicitBucketBoundariesAdvice(SIZE_BUCKETS)
1✔
229
              .build());
1✔
230
    }
231

232
    if (isMetricEnabled("grpc.client.attempt.rcvd_total_compressed_message_size", enableMetrics,
1✔
233
        disableDefault)) {
234
      builder.clientTotalReceivedCompressedMessageSizeCounter(
1✔
235
          meter.histogramBuilder(
1✔
236
                  "grpc.client.attempt.rcvd_total_compressed_message_size")
237
              .setUnit("By")
1✔
238
              .setDescription("Compressed message bytes received per call attempt")
1✔
239
              .ofLongs()
1✔
240
              .setExplicitBucketBoundariesAdvice(SIZE_BUCKETS)
1✔
241
              .build());
1✔
242
    }
243

244
    if (isMetricEnabled("grpc.server.call.started", enableMetrics, disableDefault)) {
1✔
245
      builder.serverCallCountCounter(
1✔
246
          meter.counterBuilder("grpc.server.call.started")
1✔
247
              .setUnit("{call}")
1✔
248
              .setDescription("Number of server calls started")
1✔
249
              .build());
1✔
250
    }
251

252
    if (isMetricEnabled("grpc.server.call.duration", enableMetrics, disableDefault)) {
1✔
253
      builder.serverCallDurationCounter(
1✔
254
          meter.histogramBuilder("grpc.server.call.duration")
1✔
255
              .setUnit("s")
1✔
256
              .setDescription(
1✔
257
                  "Time taken to complete a call from server transport's perspective")
258
              .setExplicitBucketBoundariesAdvice(LATENCY_BUCKETS)
1✔
259
              .build());
1✔
260
    }
261

262
    if (isMetricEnabled("grpc.server.call.sent_total_compressed_message_size", enableMetrics,
1✔
263
        disableDefault)) {
264
      builder.serverTotalSentCompressedMessageSizeCounter(
1✔
265
          meter.histogramBuilder(
1✔
266
                  "grpc.server.call.sent_total_compressed_message_size")
267
              .setUnit("By")
1✔
268
              .setDescription("Compressed message bytes sent per server call")
1✔
269
              .ofLongs()
1✔
270
              .setExplicitBucketBoundariesAdvice(SIZE_BUCKETS)
1✔
271
              .build());
1✔
272
    }
273

274
    if (isMetricEnabled("grpc.server.call.rcvd_total_compressed_message_size", enableMetrics,
1✔
275
        disableDefault)) {
276
      builder.serverTotalReceivedCompressedMessageSizeCounter(
1✔
277
          meter.histogramBuilder(
1✔
278
                  "grpc.server.call.rcvd_total_compressed_message_size")
279
              .setUnit("By")
1✔
280
              .setDescription("Compressed message bytes received per server call")
1✔
281
              .ofLongs()
1✔
282
              .setExplicitBucketBoundariesAdvice(SIZE_BUCKETS)
1✔
283
              .build());
1✔
284
    }
285

286
    return builder.build();
1✔
287
  }
288

289
  static boolean isMetricEnabled(String metricName, Map<String, Boolean> enableMetrics,
290
      boolean disableDefault) {
291
    Boolean explicitlyEnabled = enableMetrics.get(metricName);
1✔
292
    if (explicitlyEnabled != null) {
1✔
293
      return explicitlyEnabled;
×
294
    }
295
    return OpenTelemetryMetricsModule.DEFAULT_PER_CALL_METRICS_SET.contains(metricName)
1✔
296
        && !disableDefault;
297
  }
298

299

300
  /**
301
   * Builder for configuring {@link GrpcOpenTelemetry}.
302
   */
303
  public static class Builder {
304
    private OpenTelemetry openTelemetrySdk = OpenTelemetry.noop();
1✔
305
    private final List<OpenTelemetryPlugin> plugins = new ArrayList<>();
1✔
306
    private final Collection<String> optionalLabels = new ArrayList<>();
1✔
307
    private final Map<String, Boolean> enableMetrics = new HashMap<>();
1✔
308
    private boolean disableAll;
309

310
    private Builder() {}
1✔
311

312
    /**
313
     * Sets the {@link io.opentelemetry.api.OpenTelemetry} entrypoint to use. This can be used to
314
     * configure OpenTelemetry by returning the instance created by a
315
     * {@code io.opentelemetry.sdk.OpenTelemetrySdkBuilder}.
316
     */
317
    public Builder sdk(OpenTelemetry sdk) {
318
      this.openTelemetrySdk = sdk;
1✔
319
      return this;
1✔
320
    }
321

322
    Builder plugin(OpenTelemetryPlugin plugin) {
323
      plugins.add(checkNotNull(plugin, "plugin"));
1✔
324
      return this;
1✔
325
    }
326

327
    /**
328
     * Adds optionalLabelKey to all the metrics that can provide value for the
329
     * optionalLabelKey.
330
     */
331
    public Builder addOptionalLabel(String optionalLabelKey) {
332
      this.optionalLabels.add(optionalLabelKey);
1✔
333
      return this;
1✔
334
    }
335

336
    /**
337
     * Enables the specified metrics for collection and export. By default, only a subset of
338
     * metrics are enabled.
339
     */
340
    public Builder enableMetrics(Collection<String> enableMetrics) {
341
      for (String metric : enableMetrics) {
1✔
342
        this.enableMetrics.put(metric, true);
1✔
343
      }
1✔
344
      return this;
1✔
345
    }
346

347
    /**
348
     * Disables the specified metrics from being collected and exported.
349
     */
350
    public Builder disableMetrics(Collection<String> disableMetrics) {
351
      for (String metric : disableMetrics) {
1✔
352
        this.enableMetrics.put(metric, false);
1✔
353
      }
1✔
354
      return this;
1✔
355
    }
356

357
    /**
358
     * Disable all metrics. If set to true all metrics must be explicitly enabled.
359
     */
360
    public Builder disableAllMetrics() {
361
      this.enableMetrics.clear();
1✔
362
      this.disableAll = true;
1✔
363
      return this;
1✔
364
    }
365

366
    Builder enableTracing(boolean enable) {
367
      ENABLE_OTEL_TRACING = enable;
1✔
368
      return this;
1✔
369
    }
370

371
    /**
372
     * Returns a new {@link GrpcOpenTelemetry} built with the configuration of this {@link
373
     * Builder}.
374
     */
375
    public GrpcOpenTelemetry build() {
376
      return new GrpcOpenTelemetry(this);
1✔
377
    }
378
  }
379
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc