From 129c96ca66d0ae3e0336331c4f092df5b1d85b89 Mon Sep 17 00:00:00 2001 From: Dhriti Chopra Date: Tue, 4 Nov 2025 15:50:29 +0530 Subject: [PATCH 1/7] Adding initial otel for MPU --- .../cloud/storage/MultipartUploadClient.java | 6 +- .../OtelMultipartUploadClientDecorator.java | 161 ++++++++++++++++++ .../cloud/storage/OtelStorageDecorator.java | 6 +- .../cloud/storage/ITOpenTelemetryMPUTest.java | 143 ++++++++++++++++ 4 files changed, 311 insertions(+), 5 deletions(-) create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryMPUTest.java diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClient.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClient.java index 4f80a7083d..a58ba26242 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClient.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClient.java @@ -19,6 +19,7 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalExtensionOnly; import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadRequest; +import com.google.cloud.storage.TransportCompatibility.Transport; import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadResponse; import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadRequest; import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadResponse; @@ -122,9 +123,10 @@ public abstract ListMultipartUploadsResponse listMultipartUploads( @BetaApi public static MultipartUploadClient create(MultipartUploadSettings config) { HttpStorageOptions options = config.getOptions(); - return new MultipartUploadClientImpl( + MultipartUploadClient client = new MultipartUploadClientImpl( options.createRetrier(), - MultipartUploadHttpRequestManager.createFrom(options), + MultipartUploadHttpRequestManager.createFrom(options), options.getRetryAlgorithmManager()); + return OtelMultipartUploadClientDecorator.decorate(client, options.getOpenTelemetry(), Transport.HTTP); } } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java new file mode 100644 index 0000000000..5dc033eaf0 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java @@ -0,0 +1,161 @@ +/* + * Copyright 2025 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.BetaApi; +import com.google.cloud.storage.TransportCompatibility.Transport; +import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.ListPartsRequest; +import com.google.cloud.storage.multipartupload.model.ListPartsResponse; +import com.google.cloud.storage.multipartupload.model.UploadPartRequest; +import com.google.cloud.storage.multipartupload.model.UploadPartResponse; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Scope; +import java.util.Locale; + +@BetaApi +final class OtelMultipartUploadClientDecorator extends MultipartUploadClient { + + private final MultipartUploadClient delegate; + private final Tracer tracer; + + private OtelMultipartUploadClientDecorator( + MultipartUploadClient delegate, OpenTelemetry otel, Attributes baseAttributes) { + this.delegate = delegate; + this.tracer = + OtelStorageDecorator.TracerDecorator.decorate( + null, otel, baseAttributes, MultipartUploadClient.class.getName() + "/"); + } + + @Override + public CreateMultipartUploadResponse createMultipartUpload(CreateMultipartUploadRequest request) { + Span span = + tracer + .spanBuilder("createMultipartUpload") + .setAttribute("gsutil.uri", String.format("gs://%s/%s", request.bucket(), request.key())) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.createMultipartUpload(request); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public ListPartsResponse listParts(ListPartsRequest request) { + Span span = + tracer + .spanBuilder("listParts") + .setAttribute("gsutil.uri", String.format("gs://%s/%s", request.bucket(), request.key())) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.listParts(request); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public AbortMultipartUploadResponse abortMultipartUpload(AbortMultipartUploadRequest request) { + Span span = + tracer + .spanBuilder("abortMultipartUpload") + .setAttribute("gsutil.uri", String.format("gs://%s/%s", request.bucket(), request.key())) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.abortMultipartUpload(request); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public CompleteMultipartUploadResponse completeMultipartUpload( + CompleteMultipartUploadRequest request) { + Span span = + tracer + .spanBuilder("completeMultipartUpload") + .setAttribute("gsutil.uri", String.format("gs://%s/%s", request.bucket(), request.key())) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.completeMultipartUpload(request); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requestBody) { + Span span = + tracer + .spanBuilder("uploadPart") + .setAttribute("gsutil.uri", String.format("gs://%s/%s", request.bucket(), request.key())) + .setAttribute("partNumber", request.partNumber()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.uploadPart(request, requestBody); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + static MultipartUploadClient decorate( + MultipartUploadClient delegate, OpenTelemetry otel, Transport transport) { + if (otel == OpenTelemetry.noop()) { + return delegate; + } + Attributes baseAttributes = + Attributes.builder() + .put("gcp.client.service", "Storage") + .put("gcp.client.version", StorageOptions.getDefaultInstance().getLibraryVersion()) + .put("gcp.client.repo", "googleapis/java-storage") + .put("gcp.client.artifact", "com.google.cloud:google-cloud-storage") + .put("rpc.system", transport.toString().toLowerCase(Locale.ROOT)) + .put("service.name", "storage.googleapis.com") + .build(); + return new OtelMultipartUploadClientDecorator(delegate, otel, baseAttributes); + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelStorageDecorator.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelStorageDecorator.java index e418e5e106..291db00ae5 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelStorageDecorator.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelStorageDecorator.java @@ -1561,13 +1561,13 @@ static UnaryOperator retryContextDecorator(OpenTelemetry otel) { return String.format(Locale.US, "gs://%s/", bucket); } - private static final class TracerDecorator implements Tracer { + static final class TracerDecorator implements Tracer { @Nullable private final Context parentContextOverride; private final Tracer delegate; private final Attributes baseAttributes; private final String spanNamePrefix; - private TracerDecorator( + TracerDecorator( @Nullable Context parentContextOverride, Tracer delegate, Attributes baseAttributes, @@ -1578,7 +1578,7 @@ private TracerDecorator( this.spanNamePrefix = spanNamePrefix; } - private static TracerDecorator decorate( + static TracerDecorator decorate( @Nullable Context parentContextOverride, OpenTelemetry otel, Attributes baseAttributes, diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryMPUTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryMPUTest.java new file mode 100644 index 0000000000..a3a33bef98 --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryMPUTest.java @@ -0,0 +1,143 @@ +/* + * 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.cloud.storage.TestUtils.assertAll; +import static com.google.common.truth.Truth.assertThat; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +import com.google.cloud.storage.TransportCompatibility.Transport; +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.CrossRun; +import com.google.cloud.storage.it.runner.annotations.Inject; +import com.google.cloud.storage.it.runner.registry.Generator; +import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.CompletedMultipartUpload; +import com.google.cloud.storage.multipartupload.model.CompletedPart; +import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadRequest; +import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.UploadPartRequest; +import com.google.cloud.storage.multipartupload.model.UploadPartResponse; +import com.google.cloud.storage.otel.TestExporter; +import com.google.common.collect.ImmutableList; +import java.util.List; + + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(StorageITRunner.class) +@CrossRun( + backends = Backend.PROD, + transports = {Transport.HTTP}) +public final class ITOpenTelemetryMPUTest { + + @Inject public Storage storage; + + @Inject public BucketInfo bucket; + + @Inject public Generator generator; + @Inject public Transport transport; + + @Test + public void checkMPUInstrumentation() throws Exception { + TestExporter exporter = new TestExporter(); + + OpenTelemetrySdk openTelemetrySdk = + OpenTelemetrySdk.builder() + .setTracerProvider( + SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.create(exporter)) + .build()) + .build(); + + HttpStorageOptions httpStorageOptions = (HttpStorageOptions) storage.getOptions(); + StorageOptions storageOptions = + httpStorageOptions.toBuilder().setOpenTelemetry(openTelemetrySdk).build(); + + String objectName = generator.randomObjectName(); + + try (Storage storage = storageOptions.getService()) { + MultipartUploadClient mpuClient = + MultipartUploadClient.create( + MultipartUploadSettings.of((HttpStorageOptions) storage.getOptions())); + + CreateMultipartUploadResponse create = + mpuClient.createMultipartUpload( + CreateMultipartUploadRequest.builder() + .bucket(bucket.getName()) + .key(objectName) + .build()); + + byte[] data = "Hello, World!".getBytes(StandardCharsets.UTF_8); + RequestBody body = RequestBody.of(ByteBuffer.wrap(data)); + UploadPartResponse upload = + mpuClient.uploadPart( + UploadPartRequest.builder() + .bucket(bucket.getName()) + .key(objectName) + .uploadId(create.uploadId()) + .partNumber(1) + .build(), + body); + + mpuClient.completeMultipartUpload( + CompleteMultipartUploadRequest.builder() + .bucket(bucket.getName()) + .key(objectName) + .uploadId(create.uploadId()) + .multipartUpload( + CompletedMultipartUpload.builder() + .parts( + ImmutableList.of( + CompletedPart.builder().partNumber(1).eTag(upload.eTag()).build())) + .build()) + .build()); + } + + List spans = exporter.getExportedSpans(); + assertThat(spans).hasSize(3); + + SpanData createSpan = spans.get(0); + assertThat(createSpan.getName()) + .isEqualTo("com.google.cloud.storage.MultipartUploadClient/createMultipartUpload"); + assertThat(createSpan.getAttributes().get(AttributeKey.stringKey("gsutil.uri"))) + .isEqualTo(String.format("gs://%s/%s", bucket.getName(), objectName)); + + SpanData uploadSpan = spans.get(1); + assertThat(uploadSpan.getName()) + .isEqualTo("com.google.cloud.storage.MultipartUploadClient/uploadPart"); + assertThat(uploadSpan.getAttributes().get(AttributeKey.stringKey("gsutil.uri"))) + .isEqualTo(String.format("gs://%s/%s", bucket.getName(), objectName)); + assertThat(uploadSpan.getAttributes().get(AttributeKey.longKey("partNumber"))).isEqualTo(1); + + SpanData completeSpan = spans.get(2); + assertThat(completeSpan.getName()) + .isEqualTo("com.google.cloud.storage.MultipartUploadClient/completeMultipartUpload"); + assertThat(completeSpan.getAttributes().get(AttributeKey.stringKey("gsutil.uri"))) + .isEqualTo(String.format("gs://%s/%s", bucket.getName(), objectName)); + } +} \ No newline at end of file From da49b9e98822f640e15bc70f1f834086630f27f6 Mon Sep 17 00:00:00 2001 From: Dhriti Chopra Date: Wed, 3 Dec 2025 02:36:36 +0530 Subject: [PATCH 2/7] fix lint --- .../cloud/storage/MultipartUploadClient.java | 14 +- .../OtelMultipartUploadClientDecorator.java | 15 +- .../cloud/storage/ITOpenTelemetryMPUTest.java | 165 +++++++++--------- 3 files changed, 98 insertions(+), 96 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClient.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClient.java index a58ba26242..2085ea33a5 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClient.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadClient.java @@ -18,8 +18,8 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalExtensionOnly; -import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadRequest; import com.google.cloud.storage.TransportCompatibility.Transport; +import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadRequest; import com.google.cloud.storage.multipartupload.model.AbortMultipartUploadResponse; import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadRequest; import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadResponse; @@ -123,10 +123,12 @@ public abstract ListMultipartUploadsResponse listMultipartUploads( @BetaApi public static MultipartUploadClient create(MultipartUploadSettings config) { HttpStorageOptions options = config.getOptions(); - MultipartUploadClient client = new MultipartUploadClientImpl( - options.createRetrier(), - MultipartUploadHttpRequestManager.createFrom(options), - options.getRetryAlgorithmManager()); - return OtelMultipartUploadClientDecorator.decorate(client, options.getOpenTelemetry(), Transport.HTTP); + MultipartUploadClient client = + new MultipartUploadClientImpl( + options.createRetrier(), + MultipartUploadHttpRequestManager.createFrom(options), + options.getRetryAlgorithmManager()); + return OtelMultipartUploadClientDecorator.decorate( + client, options.getOpenTelemetry(), Transport.HTTP); } } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java index 5dc033eaf0..92e52e3a7d 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java @@ -55,7 +55,8 @@ public CreateMultipartUploadResponse createMultipartUpload(CreateMultipartUpload Span span = tracer .spanBuilder("createMultipartUpload") - .setAttribute("gsutil.uri", String.format("gs://%s/%s", request.bucket(), request.key())) + .setAttribute( + "gsutil.uri", String.format("gs://%s/%s", request.bucket(), request.key())) .startSpan(); try (Scope ignore = span.makeCurrent()) { return delegate.createMultipartUpload(request); @@ -73,7 +74,8 @@ public ListPartsResponse listParts(ListPartsRequest request) { Span span = tracer .spanBuilder("listParts") - .setAttribute("gsutil.uri", String.format("gs://%s/%s", request.bucket(), request.key())) + .setAttribute( + "gsutil.uri", String.format("gs://%s/%s", request.bucket(), request.key())) .startSpan(); try (Scope ignore = span.makeCurrent()) { return delegate.listParts(request); @@ -91,7 +93,8 @@ public AbortMultipartUploadResponse abortMultipartUpload(AbortMultipartUploadReq Span span = tracer .spanBuilder("abortMultipartUpload") - .setAttribute("gsutil.uri", String.format("gs://%s/%s", request.bucket(), request.key())) + .setAttribute( + "gsutil.uri", String.format("gs://%s/%s", request.bucket(), request.key())) .startSpan(); try (Scope ignore = span.makeCurrent()) { return delegate.abortMultipartUpload(request); @@ -110,7 +113,8 @@ public CompleteMultipartUploadResponse completeMultipartUpload( Span span = tracer .spanBuilder("completeMultipartUpload") - .setAttribute("gsutil.uri", String.format("gs://%s/%s", request.bucket(), request.key())) + .setAttribute( + "gsutil.uri", String.format("gs://%s/%s", request.bucket(), request.key())) .startSpan(); try (Scope ignore = span.makeCurrent()) { return delegate.completeMultipartUpload(request); @@ -128,7 +132,8 @@ public UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requ Span span = tracer .spanBuilder("uploadPart") - .setAttribute("gsutil.uri", String.format("gs://%s/%s", request.bucket(), request.key())) + .setAttribute( + "gsutil.uri", String.format("gs://%s/%s", request.bucket(), request.key())) .setAttribute("partNumber", request.partNumber()) .startSpan(); try (Scope ignore = span.makeCurrent()) { diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryMPUTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryMPUTest.java index a3a33bef98..42a4210ad8 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryMPUTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryMPUTest.java @@ -16,12 +16,8 @@ package com.google.cloud.storage; -import static com.google.cloud.storage.TestUtils.assertAll; import static com.google.common.truth.Truth.assertThat; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; - import com.google.cloud.storage.TransportCompatibility.Transport; import com.google.cloud.storage.it.runner.StorageITRunner; import com.google.cloud.storage.it.runner.annotations.Backend; @@ -37,15 +33,14 @@ import com.google.cloud.storage.multipartupload.model.UploadPartResponse; import com.google.cloud.storage.otel.TestExporter; import com.google.common.collect.ImmutableList; -import java.util.List; - - -import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; @@ -64,80 +59,80 @@ public final class ITOpenTelemetryMPUTest { @Test public void checkMPUInstrumentation() throws Exception { - TestExporter exporter = new TestExporter(); - - OpenTelemetrySdk openTelemetrySdk = - OpenTelemetrySdk.builder() - .setTracerProvider( - SdkTracerProvider.builder() - .addSpanProcessor(SimpleSpanProcessor.create(exporter)) - .build()) - .build(); - - HttpStorageOptions httpStorageOptions = (HttpStorageOptions) storage.getOptions(); - StorageOptions storageOptions = - httpStorageOptions.toBuilder().setOpenTelemetry(openTelemetrySdk).build(); - - String objectName = generator.randomObjectName(); - - try (Storage storage = storageOptions.getService()) { - MultipartUploadClient mpuClient = - MultipartUploadClient.create( - MultipartUploadSettings.of((HttpStorageOptions) storage.getOptions())); - - CreateMultipartUploadResponse create = - mpuClient.createMultipartUpload( - CreateMultipartUploadRequest.builder() - .bucket(bucket.getName()) - .key(objectName) - .build()); - - byte[] data = "Hello, World!".getBytes(StandardCharsets.UTF_8); - RequestBody body = RequestBody.of(ByteBuffer.wrap(data)); - UploadPartResponse upload = - mpuClient.uploadPart( - UploadPartRequest.builder() - .bucket(bucket.getName()) - .key(objectName) - .uploadId(create.uploadId()) - .partNumber(1) - .build(), - body); - - mpuClient.completeMultipartUpload( - CompleteMultipartUploadRequest.builder() - .bucket(bucket.getName()) - .key(objectName) - .uploadId(create.uploadId()) - .multipartUpload( - CompletedMultipartUpload.builder() - .parts( - ImmutableList.of( - CompletedPart.builder().partNumber(1).eTag(upload.eTag()).build())) - .build()) - .build()); - } - - List spans = exporter.getExportedSpans(); - assertThat(spans).hasSize(3); - - SpanData createSpan = spans.get(0); - assertThat(createSpan.getName()) - .isEqualTo("com.google.cloud.storage.MultipartUploadClient/createMultipartUpload"); - assertThat(createSpan.getAttributes().get(AttributeKey.stringKey("gsutil.uri"))) - .isEqualTo(String.format("gs://%s/%s", bucket.getName(), objectName)); - - SpanData uploadSpan = spans.get(1); - assertThat(uploadSpan.getName()) - .isEqualTo("com.google.cloud.storage.MultipartUploadClient/uploadPart"); - assertThat(uploadSpan.getAttributes().get(AttributeKey.stringKey("gsutil.uri"))) - .isEqualTo(String.format("gs://%s/%s", bucket.getName(), objectName)); - assertThat(uploadSpan.getAttributes().get(AttributeKey.longKey("partNumber"))).isEqualTo(1); - - SpanData completeSpan = spans.get(2); - assertThat(completeSpan.getName()) - .isEqualTo("com.google.cloud.storage.MultipartUploadClient/completeMultipartUpload"); - assertThat(completeSpan.getAttributes().get(AttributeKey.stringKey("gsutil.uri"))) - .isEqualTo(String.format("gs://%s/%s", bucket.getName(), objectName)); - } -} \ No newline at end of file + TestExporter exporter = new TestExporter(); + + OpenTelemetrySdk openTelemetrySdk = + OpenTelemetrySdk.builder() + .setTracerProvider( + SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.create(exporter)) + .build()) + .build(); + + HttpStorageOptions httpStorageOptions = (HttpStorageOptions) storage.getOptions(); + StorageOptions storageOptions = + httpStorageOptions.toBuilder().setOpenTelemetry(openTelemetrySdk).build(); + + String objectName = generator.randomObjectName(); + + try (Storage storage = storageOptions.getService()) { + MultipartUploadClient mpuClient = + MultipartUploadClient.create( + MultipartUploadSettings.of((HttpStorageOptions) storage.getOptions())); + + CreateMultipartUploadResponse create = + mpuClient.createMultipartUpload( + CreateMultipartUploadRequest.builder() + .bucket(bucket.getName()) + .key(objectName) + .build()); + + byte[] data = "Hello, World!".getBytes(StandardCharsets.UTF_8); + RequestBody body = RequestBody.of(ByteBuffer.wrap(data)); + UploadPartResponse upload = + mpuClient.uploadPart( + UploadPartRequest.builder() + .bucket(bucket.getName()) + .key(objectName) + .uploadId(create.uploadId()) + .partNumber(1) + .build(), + body); + + mpuClient.completeMultipartUpload( + CompleteMultipartUploadRequest.builder() + .bucket(bucket.getName()) + .key(objectName) + .uploadId(create.uploadId()) + .multipartUpload( + CompletedMultipartUpload.builder() + .parts( + ImmutableList.of( + CompletedPart.builder().partNumber(1).eTag(upload.eTag()).build())) + .build()) + .build()); + } + + List spans = exporter.getExportedSpans(); + assertThat(spans).hasSize(3); + + SpanData createSpan = spans.get(0); + assertThat(createSpan.getName()) + .isEqualTo("com.google.cloud.storage.MultipartUploadClient/createMultipartUpload"); + assertThat(createSpan.getAttributes().get(AttributeKey.stringKey("gsutil.uri"))) + .isEqualTo(String.format("gs://%s/%s", bucket.getName(), objectName)); + + SpanData uploadSpan = spans.get(1); + assertThat(uploadSpan.getName()) + .isEqualTo("com.google.cloud.storage.MultipartUploadClient/uploadPart"); + assertThat(uploadSpan.getAttributes().get(AttributeKey.stringKey("gsutil.uri"))) + .isEqualTo(String.format("gs://%s/%s", bucket.getName(), objectName)); + assertThat(uploadSpan.getAttributes().get(AttributeKey.longKey("partNumber"))).isEqualTo(1); + + SpanData completeSpan = spans.get(2); + assertThat(completeSpan.getName()) + .isEqualTo("com.google.cloud.storage.MultipartUploadClient/completeMultipartUpload"); + assertThat(completeSpan.getAttributes().get(AttributeKey.stringKey("gsutil.uri"))) + .isEqualTo(String.format("gs://%s/%s", bucket.getName(), objectName)); + } +} From 298a086954362b0ad620a074d7e3d35684397d59 Mon Sep 17 00:00:00 2001 From: Dhriti Chopra Date: Wed, 3 Dec 2025 02:44:42 +0530 Subject: [PATCH 3/7] Resolving comment --- .../cloud/storage/OtelMultipartUploadClientDecorator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java index 92e52e3a7d..c1101438f9 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java @@ -158,7 +158,7 @@ static MultipartUploadClient decorate( .put("gcp.client.version", StorageOptions.getDefaultInstance().getLibraryVersion()) .put("gcp.client.repo", "googleapis/java-storage") .put("gcp.client.artifact", "com.google.cloud:google-cloud-storage") - .put("rpc.system", transport.toString().toLowerCase(Locale.ROOT)) + .put("rpc.system", "XML") .put("service.name", "storage.googleapis.com") .build(); return new OtelMultipartUploadClientDecorator(delegate, otel, baseAttributes); From 92634f4e679b6631140bf347e65904bec9079c7c Mon Sep 17 00:00:00 2001 From: Dhriti Chopra Date: Fri, 5 Dec 2025 08:33:51 +0530 Subject: [PATCH 4/7] fix lint --- .../google/cloud/storage/OtelMultipartUploadClientDecorator.java | 1 - 1 file changed, 1 deletion(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java index c1101438f9..46ba25fbd2 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java @@ -34,7 +34,6 @@ import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Scope; -import java.util.Locale; @BetaApi final class OtelMultipartUploadClientDecorator extends MultipartUploadClient { From c785fc0625743a9ec03cc0dfca3d3b7cc25744db Mon Sep 17 00:00:00 2001 From: Dhriti Chopra Date: Thu, 18 Dec 2025 12:13:42 +0530 Subject: [PATCH 5/7] resolving comments --- .../cloud/storage/OtelMultipartUploadClientDecorator.java | 5 +++++ .../com/google/cloud/storage/ITOpenTelemetryMPUTest.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java index 46ba25fbd2..bcd854dcfd 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java @@ -35,6 +35,11 @@ import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Scope; +/** + * A decorator for {@link MultipartUploadClient} that adds OpenTelemetry tracing. + * + * @since 2.62.0 This new api is in preview and is subject to breaking changes. + */ @BetaApi final class OtelMultipartUploadClientDecorator extends MultipartUploadClient { diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryMPUTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryMPUTest.java index 42a4210ad8..576ab62eb1 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryMPUTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryMPUTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From fd9227da87ca215642bf1a63a7dcf10ab1906fba Mon Sep 17 00:00:00 2001 From: Dhriti Chopra Date: Thu, 18 Dec 2025 12:55:11 +0530 Subject: [PATCH 6/7] adding listMultipartUpload otel traces --- .../OtelMultipartUploadClientDecorator.java | 22 +++++++++++++++++++ .../cloud/storage/ITOpenTelemetryMPUTest.java | 12 +++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java index bcd854dcfd..4c60a79c18 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java @@ -24,10 +24,13 @@ import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadResponse; import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadRequest; import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.ListMultipartUploadsRequest; +import com.google.cloud.storage.multipartupload.model.ListMultipartUploadsResponse; import com.google.cloud.storage.multipartupload.model.ListPartsRequest; import com.google.cloud.storage.multipartupload.model.ListPartsResponse; import com.google.cloud.storage.multipartupload.model.UploadPartRequest; import com.google.cloud.storage.multipartupload.model.UploadPartResponse; + import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; @@ -151,6 +154,25 @@ public UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requ } } + @Override + public ListMultipartUploadsResponse listMultipartUploads(ListMultipartUploadsRequest request) { + Span span = + tracer + .spanBuilder("listMultipartUploads") + .setAttribute("gsutil.uri", String.format("gs://%s/", request.bucket())) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.listMultipartUploads(request); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + static MultipartUploadClient decorate( MultipartUploadClient delegate, OpenTelemetry otel, Transport transport) { if (otel == OpenTelemetry.noop()) { diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryMPUTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryMPUTest.java index 576ab62eb1..e1a83ba6eb 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryMPUTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryMPUTest.java @@ -29,6 +29,7 @@ import com.google.cloud.storage.multipartupload.model.CompletedPart; import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadRequest; import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadResponse; +import com.google.cloud.storage.multipartupload.model.ListMultipartUploadsRequest; import com.google.cloud.storage.multipartupload.model.UploadPartRequest; import com.google.cloud.storage.multipartupload.model.UploadPartResponse; import com.google.cloud.storage.otel.TestExporter; @@ -111,10 +112,13 @@ public void checkMPUInstrumentation() throws Exception { CompletedPart.builder().partNumber(1).eTag(upload.eTag()).build())) .build()) .build()); + + mpuClient.listMultipartUploads( + ListMultipartUploadsRequest.builder().bucket(bucket.getName()).build()); } List spans = exporter.getExportedSpans(); - assertThat(spans).hasSize(3); + assertThat(spans).hasSize(4); SpanData createSpan = spans.get(0); assertThat(createSpan.getName()) @@ -134,5 +138,11 @@ public void checkMPUInstrumentation() throws Exception { .isEqualTo("com.google.cloud.storage.MultipartUploadClient/completeMultipartUpload"); assertThat(completeSpan.getAttributes().get(AttributeKey.stringKey("gsutil.uri"))) .isEqualTo(String.format("gs://%s/%s", bucket.getName(), objectName)); + + SpanData listSpan = spans.get(3); + assertThat(listSpan.getName()) + .isEqualTo("com.google.cloud.storage.MultipartUploadClient/listMultipartUploads"); + assertThat(listSpan.getAttributes().get(AttributeKey.stringKey("gsutil.uri"))) + .isEqualTo(String.format("gs://%s/", bucket.getName())); } } From b12168acbf7af6edaf712eae23389167e9cc01a4 Mon Sep 17 00:00:00 2001 From: Dhriti Chopra Date: Thu, 18 Dec 2025 12:57:28 +0530 Subject: [PATCH 7/7] fix lint --- .../cloud/storage/OtelMultipartUploadClientDecorator.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java index 4c60a79c18..f5e7080fed 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelMultipartUploadClientDecorator.java @@ -30,7 +30,6 @@ import com.google.cloud.storage.multipartupload.model.ListPartsResponse; import com.google.cloud.storage.multipartupload.model.UploadPartRequest; import com.google.cloud.storage.multipartupload.model.UploadPartResponse; - import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; @@ -172,7 +171,6 @@ public ListMultipartUploadsResponse listMultipartUploads(ListMultipartUploadsReq } } - static MultipartUploadClient decorate( MultipartUploadClient delegate, OpenTelemetry otel, Transport transport) { if (otel == OpenTelemetry.noop()) {