diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index b6bc155e3d..839cc39559 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -124,6 +124,53 @@ com.google.api.grpc gapic-google-cloud-storage-v2 + + + io.opentelemetry + opentelemetry-sdk + + + io.grpc + grpc-opentelemetry + + + io.opentelemetry + opentelemetry-api + + + + io.opentelemetry + opentelemetry-sdk-metrics + + + io.opentelemetry + opentelemetry-sdk-common + + + + io.opentelemetry + opentelemetry-sdk-extension-autoconfigure-spi + + + + io.opentelemetry.semconv + opentelemetry-semconv + + + + com.google.cloud.opentelemetry + exporter-metrics + + + + io.opentelemetry.contrib + opentelemetry-gcp-resources + + + + org.checkerframework + checker-qual + @@ -159,10 +206,7 @@ grpc-googleapis runtime - - org.checkerframework - checker-qual - + org.junit.vintage:junit-vintage-engine + + + io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi + io.opentelemetry.semconv:opentelemetry-semconv diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java index 92081e03e0..64376cc107 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java @@ -116,6 +116,9 @@ public final class GrpcStorageOptions extends StorageOptions private final GrpcRetryAlgorithmManager retryAlgorithmManager; private final Duration terminationAwaitDuration; private final boolean attemptDirectPath; + private final boolean enableGrpcClientMetrics; + + private final boolean grpcClientMetricsManuallyEnabled; private final GrpcInterceptorProvider grpcInterceptorProvider; private final BlobWriteSessionConfig blobWriteSessionConfig; @@ -129,6 +132,8 @@ private GrpcStorageOptions(Builder builder, GrpcStorageDefaults serviceDefaults) MoreObjects.firstNonNull( builder.terminationAwaitDuration, serviceDefaults.getTerminationAwaitDuration()); this.attemptDirectPath = builder.attemptDirectPath; + this.enableGrpcClientMetrics = builder.enableGrpcClientMetrics; + this.grpcClientMetricsManuallyEnabled = builder.grpcMetricsManuallyEnabled; this.grpcInterceptorProvider = builder.grpcInterceptorProvider; this.blobWriteSessionConfig = builder.blobWriteSessionConfig; } @@ -287,6 +292,16 @@ private Tuple> resolveSettingsAndOpts() throw if (scheme.equals("http")) { channelProviderBuilder.setChannelConfigurator(ManagedChannelBuilder::usePlaintext); } + + if (enableGrpcClientMetrics) { + OpenTelemetryBootstrappingUtils.enableGrpcMetrics( + channelProviderBuilder, + endpoint, + this.getProjectId(), + this.getUniverseDomain(), + !grpcClientMetricsManuallyEnabled); + } + builder.setTransportChannelProvider(channelProviderBuilder.build()); RetrySettings baseRetrySettings = getRetrySettings(); RetrySettings readRetrySettings = @@ -350,6 +365,7 @@ public int hashCode() { retryAlgorithmManager, terminationAwaitDuration, attemptDirectPath, + enableGrpcClientMetrics, grpcInterceptorProvider, blobWriteSessionConfig, baseHashCode()); @@ -365,6 +381,7 @@ public boolean equals(Object o) { } GrpcStorageOptions that = (GrpcStorageOptions) o; return attemptDirectPath == that.attemptDirectPath + && enableGrpcClientMetrics == that.enableGrpcClientMetrics && Objects.equals(retryAlgorithmManager, that.retryAlgorithmManager) && Objects.equals(terminationAwaitDuration, that.terminationAwaitDuration) && Objects.equals(grpcInterceptorProvider, that.grpcInterceptorProvider) @@ -408,11 +425,15 @@ public static final class Builder extends StorageOptions.Builder { private StorageRetryStrategy storageRetryStrategy; private Duration terminationAwaitDuration; private boolean attemptDirectPath = GrpcStorageDefaults.INSTANCE.isAttemptDirectPath(); + private boolean enableGrpcClientMetrics = + GrpcStorageDefaults.INSTANCE.isEnableGrpcClientMetrics(); private GrpcInterceptorProvider grpcInterceptorProvider = GrpcStorageDefaults.INSTANCE.grpcInterceptorProvider(); private BlobWriteSessionConfig blobWriteSessionConfig = GrpcStorageDefaults.INSTANCE.getDefaultStorageWriterConfig(); + private boolean grpcMetricsManuallyEnabled = false; + Builder() {} Builder(StorageOptions options) { @@ -421,6 +442,7 @@ public static final class Builder extends StorageOptions.Builder { this.storageRetryStrategy = gso.getRetryAlgorithmManager().retryStrategy; this.terminationAwaitDuration = gso.getTerminationAwaitDuration(); this.attemptDirectPath = gso.attemptDirectPath; + this.enableGrpcClientMetrics = gso.enableGrpcClientMetrics; this.grpcInterceptorProvider = gso.grpcInterceptorProvider; this.blobWriteSessionConfig = gso.blobWriteSessionConfig; } @@ -454,6 +476,21 @@ public GrpcStorageOptions.Builder setAttemptDirectPath(boolean attemptDirectPath this.attemptDirectPath = attemptDirectPath; return this; } + /** + * Option for whether this client should emit internal gRPC client internal metrics to Cloud + * Monitoring. To disable metric reporting, set this to false. True by default. Emitting metrics + * is free and requires minimal CPU and memory. + * + * @since 2.41.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public GrpcStorageOptions.Builder setEnableGrpcClientMetrics(boolean enableGrpcClientMetrics) { + this.enableGrpcClientMetrics = enableGrpcClientMetrics; + if (enableGrpcClientMetrics) { + grpcMetricsManuallyEnabled = true; + } + return this; + } /** @since 2.14.0 This new api is in preview and is subject to breaking changes. */ @BetaApi @@ -660,6 +697,12 @@ public boolean isAttemptDirectPath() { return false; } + /** @since 2.41.0 This new api is in preview and is subject to breaking changes. */ + @BetaApi + public boolean isEnableGrpcClientMetrics() { + return true; + } + /** @since 2.22.3 This new api is in preview and is subject to breaking changes. */ @BetaApi public GrpcInterceptorProvider grpcInterceptorProvider() { diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/OpenTelemetryBootstrappingUtils.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/OpenTelemetryBootstrappingUtils.java new file mode 100644 index 0000000000..ed0ba544e5 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/OpenTelemetryBootstrappingUtils.java @@ -0,0 +1,382 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.storage; + +import com.google.api.core.ApiFunction; +import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; +import com.google.api.gax.rpc.PermissionDeniedException; +import com.google.api.gax.rpc.UnavailableException; +import com.google.cloud.opentelemetry.metric.GoogleCloudMetricExporter; +import com.google.cloud.opentelemetry.metric.MetricConfiguration; +import com.google.cloud.opentelemetry.metric.MonitoredResourceDescription; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import io.grpc.ManagedChannelBuilder; +import io.grpc.opentelemetry.GrpcOpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.internal.StringUtils; +import io.opentelemetry.contrib.gcp.resource.GCPResourceProvider; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.common.export.MemoryMode; +import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.InstrumentSelector; +import io.opentelemetry.sdk.metrics.InstrumentType; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; +import io.opentelemetry.sdk.metrics.View; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector; +import io.opentelemetry.sdk.metrics.export.MetricExporter; +import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; +import io.opentelemetry.sdk.resources.Resource; +import java.math.BigDecimal; +import java.math.MathContext; +import java.net.NoRouteToHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +final class OpenTelemetryBootstrappingUtils { + private static final Collection METRICS_TO_ENABLE = + ImmutableList.of( + "grpc.lb.wrr.rr_fallback", + "grpc.lb.wrr.endpoint_weight_not_yet_usable", + "grpc.lb.wrr.endpoint_weight_stale", + "grpc.lb.wrr.endpoint_weights", + "grpc.lb.rls.cache_entries", + "grpc.lb.rls.cache_size", + "grpc.lb.rls.default_target_picks", + "grpc.lb.rls.target_picks", + "grpc.lb.rls.failed_picks", + "grpc.xds_client.connected", + "grpc.xds_client.server_failure", + "grpc.xds_client.resource_updates_valid", + "grpc.xds_client.resource_updates_invalid", + "grpc.xds_client.resources"); + + private static final Collection METRICS_ENABLED_BY_DEFAULT = + ImmutableList.of( + "grpc.client.attempt.sent_total_compressed_message_size", + "grpc.client.attempt.rcvd_total_compressed_message_size", + "grpc.client.attempt.started", + "grpc.client.attempt.duration", + "grpc.client.call.duration"); + + static final Logger log = Logger.getLogger(OpenTelemetryBootstrappingUtils.class.getName()); + + static void enableGrpcMetrics( + InstantiatingGrpcChannelProvider.Builder channelProviderBuilder, + String endpoint, + String projectId, + String universeDomain, + boolean shouldSuppressExceptions) { + String metricServiceEndpoint = getCloudMonitoringEndpoint(endpoint, universeDomain); + SdkMeterProvider provider = + createMeterProvider(metricServiceEndpoint, projectId, shouldSuppressExceptions); + + OpenTelemetrySdk openTelemetrySdk = + OpenTelemetrySdk.builder().setMeterProvider(provider).build(); + GrpcOpenTelemetry grpcOpenTelemetry = + GrpcOpenTelemetry.newBuilder() + .sdk(openTelemetrySdk) + .enableMetrics(METRICS_TO_ENABLE) + .build(); + ApiFunction channelConfigurator = + channelProviderBuilder.getChannelConfigurator(); + channelProviderBuilder.setChannelConfigurator( + b -> { + grpcOpenTelemetry.configureChannelBuilder(b); + if (channelConfigurator != null) { + return channelConfigurator.apply(b); + } + return b; + }); + } + + @VisibleForTesting + static String getCloudMonitoringEndpoint(String endpoint, String universeDomain) { + String metricServiceEndpoint = "monitoring.googleapis.com"; + + // use contains instead of equals because endpoint has a port in it + if (universeDomain != null && endpoint.contains("storage." + universeDomain)) { + metricServiceEndpoint = "monitoring." + universeDomain; + } else if (!endpoint.contains("storage.googleapis.com")) { + String canonicalEndpoint = "storage.googleapis.com"; + String privateEndpoint = "private.googleapis.com"; + String restrictedEndpoint = "restricted.googleapis.com"; + if (universeDomain != null) { + canonicalEndpoint = "storage." + universeDomain; + privateEndpoint = "private." + universeDomain; + restrictedEndpoint = "restricted." + universeDomain; + } + String match = + ImmutableList.of(canonicalEndpoint, privateEndpoint, restrictedEndpoint).stream() + .filter(s -> endpoint.contains(s) || endpoint.contains("google-c2p:///" + s)) + .collect(Collectors.joining()); + if (!StringUtils.isNullOrEmpty(match)) { + metricServiceEndpoint = match; + } + } + return metricServiceEndpoint + ":" + endpoint.split(":")[1]; + } + + @VisibleForTesting + static SdkMeterProvider createMeterProvider( + String metricServiceEndpoint, String projectId, boolean shouldSuppressExceptions) { + GCPResourceProvider resourceProvider = new GCPResourceProvider(); + Attributes detectedAttributes = resourceProvider.getAttributes(); + + String detectedProjectId = detectedAttributes.get(AttributeKey.stringKey("cloud.account.id")); + String projectIdToUse = detectedProjectId == null ? projectId : detectedProjectId; + + if (!projectIdToUse.equals(projectId)) { + log.warning( + "The Project ID configured for metrics is " + + projectIdToUse + + ", but the Project ID of the storage client is " + + projectId + + ". Make sure that the service account in use has the required metric writing role " + + "(roles/monitoring.metricWriter) in the project " + + projectIdToUse + + ", or metrics will not be written."); + } + + MonitoredResourceDescription monitoredResourceDescription = + new MonitoredResourceDescription( + "storage.googleapis.com/Client", + ImmutableSet.of( + "project_id", "location", "cloud_platform", "host_id", "instance_id", "api")); + + MetricExporter cloudMonitoringExporter = + GoogleCloudMetricExporter.createWithConfiguration( + MetricConfiguration.builder() + .setMonitoredResourceDescription(monitoredResourceDescription) + .setInstrumentationLibraryLabelsEnabled(false) + .setMetricServiceEndpoint(metricServiceEndpoint) + .setPrefix("storage.googleapis.com/client") + .setUseServiceTimeSeries(true) + .setProjectId(projectIdToUse) + .build()); + + SdkMeterProviderBuilder providerBuilder = SdkMeterProvider.builder(); + + // This replaces the dots with slashes in each metric, which is the format needed for this + // monitored resource + for (String metric : + ImmutableList.copyOf(Iterables.concat(METRICS_TO_ENABLE, METRICS_ENABLED_BY_DEFAULT))) { + providerBuilder.registerView( + InstrumentSelector.builder().setName(metric).build(), + View.builder().setName(metric.replace(".", "/")).build()); + } + MetricExporter exporter = + shouldSuppressExceptions + ? new PermissionDeniedSingleReportMetricsExporter(cloudMonitoringExporter) + : cloudMonitoringExporter; + providerBuilder + .registerMetricReader( + PeriodicMetricReader.builder(exporter) + .setInterval(java.time.Duration.ofSeconds(60)) + .build()) + .setResource( + Resource.create( + Attributes.builder() + .put("gcp.resource_type", "storage.googleapis.com/Client") + .put("location", detectedAttributes.get(AttributeKey.stringKey("cloud.region"))) + .put("project_id", projectIdToUse) + .put( + "cloud_platform", + detectedAttributes.get(AttributeKey.stringKey("cloud.platform"))) + .put("host_id", detectedAttributes.get(AttributeKey.stringKey("host.id"))) + .put("instance_id", UUID.randomUUID().toString()) + .put("api", "grpc") + .build())); + + addHistogramView( + providerBuilder, latencyHistogramBoundaries(), "grpc/client/attempt/duration", "s"); + addHistogramView( + providerBuilder, + sizeHistogramBoundaries(), + "grpc/client/attempt/rcvd_total_compressed_message_size", + "By"); + addHistogramView( + providerBuilder, + sizeHistogramBoundaries(), + "grpc/client/attempt/sent_total_compressed_message_size", + "By"); + + return providerBuilder.build(); + } + + private static void addHistogramView( + SdkMeterProviderBuilder provider, List boundaries, String name, String unit) { + InstrumentSelector instrumentSelector = + InstrumentSelector.builder() + .setType(InstrumentType.HISTOGRAM) + .setUnit(unit) + .setName(name) + .setMeterName("grpc-java") + .setMeterSchemaUrl("") + .build(); + View view = + View.builder() + .setName(name) + .setDescription( + "A view of " + + name + + " with histogram boundaries more appropriate for Google Cloud Storage RPCs") + .setAggregation(Aggregation.explicitBucketHistogram(boundaries)) + .build(); + provider.registerView(instrumentSelector, view); + } + + private static List latencyHistogramBoundaries() { + List boundaries = new ArrayList<>(); + BigDecimal boundary = new BigDecimal(0, MathContext.UNLIMITED); + BigDecimal increment = new BigDecimal("0.002", MathContext.UNLIMITED); // 2ms + + // 2ms buckets for the first 100ms, so we can have higher resolution for uploads and downloads + // in the 100 KiB range + for (int i = 0; i != 50; i++) { + boundaries.add(boundary.doubleValue()); + boundary = boundary.add(increment); + } + + // For the remaining buckets do 10 10ms, 10 20ms, and so on, up until 5 minutes + increment = new BigDecimal("0.01", MathContext.UNLIMITED); // 10 ms + for (int i = 0; i != 150 && boundary.compareTo(new BigDecimal(300)) < 1; i++) { + boundaries.add(boundary.doubleValue()); + if (i != 0 && i % 10 == 0) { + increment = increment.multiply(new BigDecimal(2)); + } + boundary = boundary.add(increment); + } + + return boundaries; + } + + private static List sizeHistogramBoundaries() { + long kb = 1024; + long mb = 1024 * kb; + long gb = 1024 * mb; + + List boundaries = new ArrayList<>(); + long boundary = 0; + long increment = 128 * kb; + + // 128 KiB increments up to 4MiB, then exponential growth + while (boundaries.size() < 200 && boundary <= 16 * gb) { + boundaries.add((double) boundary); + boundary += increment; + if (boundary >= 4 * mb) { + increment *= 2; + } + } + return boundaries; + } + + private static final class PermissionDeniedSingleReportMetricsExporter implements MetricExporter { + private final MetricExporter delegate; + private final AtomicBoolean seenPermissionDenied = new AtomicBoolean(false); + private final AtomicBoolean seenNoRouteToHost = new AtomicBoolean(false); + + private PermissionDeniedSingleReportMetricsExporter(MetricExporter delegate) { + this.delegate = delegate; + } + + @Override + public CompletableResultCode export(Collection metrics) { + if (seenPermissionDenied.get() && seenNoRouteToHost.get()) { + return CompletableResultCode.ofFailure(); + } + + try { + return delegate.export(metrics); + } catch (PermissionDeniedException e) { + if (!seenPermissionDenied.get()) { + seenPermissionDenied.set(true); + throw e; + } + return CompletableResultCode.ofFailure(); + } catch (UnavailableException e) { + if (seenPermissionDenied.get() + && !seenNoRouteToHost.get() + && ultimateCause(e, NoRouteToHostException.class)) { + seenNoRouteToHost.set(true); + throw e; + } + return CompletableResultCode.ofFailure(); + } + } + + @Override + public Aggregation getDefaultAggregation(InstrumentType instrumentType) { + return delegate.getDefaultAggregation(instrumentType); + } + + @Override + public MemoryMode getMemoryMode() { + return delegate.getMemoryMode(); + } + + @Override + public CompletableResultCode flush() { + return delegate.flush(); + } + + @Override + public CompletableResultCode shutdown() { + return delegate.shutdown(); + } + + @Override + public void close() { + delegate.close(); + } + + @Override + public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) { + return delegate.getAggregationTemporality(instrumentType); + } + + @Override + public DefaultAggregationSelector with(InstrumentType instrumentType, Aggregation aggregation) { + return delegate.with(instrumentType, aggregation); + } + + private static boolean ultimateCause(Throwable t, Class c) { + if (t == null) { + return false; + } + + Throwable cause = t.getCause(); + if (cause != null && c.isAssignableFrom(cause.getClass())) { + return true; + } else { + return ultimateCause(cause, c); + } + } + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcMetricsTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcMetricsTest.java new file mode 100644 index 0000000000..f1a406ca6c --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcMetricsTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.storage; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.storage.it.runner.StorageITRunner; +import com.google.cloud.storage.it.runner.annotations.Backend; +import com.google.cloud.storage.it.runner.annotations.SingleBackend; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(StorageITRunner.class) +@SingleBackend(Backend.PROD) +public class ITGrpcMetricsTest { + @Test + public void testGrpcMetrics() { + GrpcStorageOptions grpcStorageOptions = StorageOptions.grpc().build(); + assertThat( + OpenTelemetryBootstrappingUtils.getCloudMonitoringEndpoint( + "storage.googleapis.com:443", "storage.googleapis.com")) + .isEqualTo("monitoring.googleapis.com:443"); + + SdkMeterProvider provider = + OpenTelemetryBootstrappingUtils.createMeterProvider( + "monitoring.googleapis.com:443", grpcStorageOptions.getProjectId(), false); + + /* + * SDKMeterProvider doesn't expose the relevant fields we want to test, but they are present in + * the String representation, so we'll check that instead. Most of the resources are auto-set, + * and will depend on environment, which could cause flakes to check. We're only responsible for + * setting the project ID, endpoint, and Histogram boundaries, so we'll just check those + */ + String result = provider.toString(); + + // What the project ID will be will depend on the environment, so we just make sure it's present + // and not null/empty + assertThat(result.contains("project_id")); + assertThat(result).doesNotContain("project_id=\"\""); + assertThat(result).doesNotContain("project_id=null"); + + // This is the check for the Seconds histogram boundary. We can't practically check for every + // boundary, + // but if *any* are present, that means they're different from the results and we successfully + // set them + assertThat(result).contains("1.2"); + + // This is the check for the Size boundary + assertThat(result).contains("131072"); + } + + @Test + public void testGrpcMetrics_universeDomain() { + assertThat("monitoring.my-universe-domain.com:443") + .isEqualTo( + OpenTelemetryBootstrappingUtils.getCloudMonitoringEndpoint( + "storage.my-universe-domain.com:443", "my-universe-domain.com")); + } + + @Test + public void testGrpcMetrics_private() { + assertThat("private.googleapis.com:443") + .isEqualTo( + OpenTelemetryBootstrappingUtils.getCloudMonitoringEndpoint( + "private.googleapis.com:443", null)); + } + + @Test + public void testGrpcMetrics_restricted() { + assertThat("restricted.googleapis.com:443") + .isEqualTo( + OpenTelemetryBootstrappingUtils.getCloudMonitoringEndpoint( + "restricted.googleapis.com:443", null)); + } +} diff --git a/pom.xml b/pom.xml index 6087fd2918..4778468747 100644 --- a/pom.xml +++ b/pom.xml @@ -54,7 +54,7 @@ UTF-8 github google-cloud-storage-parent - 3.25.0 + 3.31.0 @@ -65,10 +65,61 @@ ${google-cloud-shared-dependencies.version} pom import + + + + io.opentelemetry.semconv + opentelemetry-semconv + 1.25.0-alpha + + + com.google.cloud.opentelemetry + exporter-metrics + 0.31.0 + + + io.opentelemetry.semconv + opentelemetry-semconv + + + + + com.google.cloud.opentelemetry + detector-resources + 0.27.0-alpha + + + io.opentelemetry.semconv + opentelemetry-semconv + + + + + io.opentelemetry.instrumentation + opentelemetry-resources + 2.6.0-alpha + + + io.opentelemetry.semconv + opentelemetry-semconv + + + + + io.opentelemetry.contrib + opentelemetry-gcp-resources + 1.37.0-alpha - io.grpc - * + io.opentelemetry.semconv + opentelemetry-semconv diff --git a/renovate.json b/renovate.json index c7467c6b72..42380a2b69 100644 --- a/renovate.json +++ b/renovate.json @@ -111,6 +111,16 @@ ], "semanticCommitType": "test", "semanticCommitScope": "deps" + }, + { + "groupName": "OpenTelemetry extended dependencies", + "packagePatterns": [ + "^io.opentelemetry.semconv:", + "^io.opentelemetry.instrumentation:", + "^io.opentelemetry.contrib:", + "^com.google.cloud.opentelemetry:", + "$com.google.cloud.opentelemetry:shared-resourcemapping" + ] } ], "semanticCommits": true,