From 903895a6d78eae2cb526768d50e22b623339e39c Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 17 Jun 2024 19:18:04 +0000 Subject: [PATCH 01/46] feat: Initial publish side Open Telemetry support --- google-cloud-pubsub/pom.xml | 5 + .../cloud/pubsub/v1/OpenTelemetryUtil.java | 82 +++++++++ .../com/google/cloud/pubsub/v1/Publisher.java | 62 +++++-- .../cloud/pubsub/v1/PubsubMessageWrapper.java | 165 ++++++++++++++++++ .../main/java/pubsub/PublisherExample.java | 26 ++- 5 files changed, 327 insertions(+), 13 deletions(-) create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java diff --git a/google-cloud-pubsub/pom.xml b/google-cloud-pubsub/pom.xml index c9f5d11cd..75a1479c8 100644 --- a/google-cloud-pubsub/pom.xml +++ b/google-cloud-pubsub/pom.xml @@ -100,6 +100,11 @@ google-http-client runtime + + io.opentelemetry + opentelemetry-api + 1.38.0 + diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java new file mode 100644 index 000000000..cf7f24547 --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java @@ -0,0 +1,82 @@ +/* + * 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.pubsub.v1; + +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.TopicName; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import java.util.List; + +public class OpenTelemetryUtil { + private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; + + private static final String SYSTEM_ATTR_KEY = "messaging.system"; + private static final String SYSTEM_ATTR_VALUE = "gcp_pubsub"; + private static final String DESTINATION_ATTR_KEY = "messaging.destination.name"; + private static final String CODE_FUNCTION_ATTR_KEY = "code.function"; + private static final String MESSAGE_ID_ATTR_KEY = "messaging.message.id"; + private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.envelope.size"; + private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; + private static final String MESSAGE_BATCH_SIZE_ATTR_KEY = "messaging.batch.message_count"; + private static final String OPERATION_ATTR_KEY = "messaging.operation"; + private static final String PROJECT_ATTR_KEY = "gcp_pubsub.project_id"; + + public static final AttributesBuilder createPublishSpanAttributesBuilder( + String topicName, String projectName, String codeFunction, String operation) { + AttributesBuilder attributesBuilder = Attributes.builder() + .put(SYSTEM_ATTR_KEY, SYSTEM_ATTR_VALUE) + .put(DESTINATION_ATTR_KEY, topicName) + .put(PROJECT_ATTR_KEY, projectName) + .put(CODE_FUNCTION_ATTR_KEY, codeFunction) + .put(OPERATION_ATTR_KEY, operation); + + return attributesBuilder; + } + + public static final Span startPublishRpcSpan(Tracer tracer, TopicName topicName, + List messages) { + Attributes attributes = createPublishSpanAttributesBuilder(topicName.getTopic(), topicName.getProject(), + "Publisher.publishCall", + "publish") + .put(MESSAGE_BATCH_SIZE_ATTR_KEY, messages.size()).build(); + Span publishRpcSpan = tracer.spanBuilder(topicName + PUBLISH_RPC_SPAN_SUFFIX).setSpanKind(SpanKind.CLIENT) + .setAllAttributes(attributes) + .startSpan(); + return publishRpcSpan; + } + + public static final void endPublishRpcSpan(Span publishRpcSpan) { + if (publishRpcSpan != null) { + publishRpcSpan.end(); + } + } + + public static final void setPublishRpcSpanException(Span publishRpcSpan, Throwable t) { + if (publishRpcSpan != null) { + publishRpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on publish RPC."); + publishRpcSpan.recordException(t); + endPublishRpcSpan(publishRpcSpan); + } + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java index efaba6cf1..59b7c59b9 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java @@ -52,6 +52,9 @@ import com.google.pubsub.v1.TopicName; import com.google.pubsub.v1.TopicNames; import io.grpc.CallOptions; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -93,6 +96,8 @@ public class Publisher implements PublisherInterface { private static final String GZIP_COMPRESSION = "gzip"; + private static final String OPEN_TELEMETRY_TRACER_NAME = "com.google.cloud.pubsub.v1"; + private final String topicName; private final BatchingSettings batchingSettings; @@ -124,6 +129,8 @@ public class Publisher implements PublisherInterface { private final GrpcCallContext publishContext; private final GrpcCallContext publishContextWithCompression; + private Tracer tracer = null; + /** The maximum number of messages in one request. Defined by the API. */ public static long getApiMaxRequestElementCount() { return 1000L; @@ -152,6 +159,9 @@ private Publisher(Builder builder) throws IOException { this.messageTransform = builder.messageTransform; this.enableCompression = builder.enableCompression; this.compressionBytesThreshold = builder.compressionBytesThreshold; + if (builder.openTelemetry != null) { + this.tracer = builder.openTelemetry.getTracer(OPEN_TELEMETRY_TRACER_NAME); + } messagesBatches = new HashMap<>(); messagesBatchLock = new ReentrantLock(); @@ -259,17 +269,23 @@ public ApiFuture publish(PubsubMessage message) { + "Publisher client. Please create a Publisher client with " + "setEnableMessageOrdering(true) in the builder."); - final OutstandingPublish outstandingPublish = - new OutstandingPublish(messageTransform.apply(message)); + PubsubMessageWrapper messageWrapper = PubsubMessageWrapper.newBuilder(messageTransform.apply(message), topicName) + .build(); + messageWrapper.startPublisherSpan(tracer); + + final OutstandingPublish outstandingPublish = new OutstandingPublish(messageWrapper); if (flowController != null) { + outstandingPublish.messageWrapper.startPublishFlowControlSpan(tracer); try { flowController.acquire(outstandingPublish.messageSize); + outstandingPublish.messageWrapper.endPublishFlowControlSpan(); } catch (FlowController.FlowControlException e) { if (!orderingKey.isEmpty()) { sequentialExecutor.stopPublish(orderingKey); } outstandingPublish.publishResult.setException(e); + outstandingPublish.messageWrapper.setPublishFlowControlSpanException(e); return outstandingPublish.publishResult; } } @@ -277,6 +293,7 @@ public ApiFuture publish(PubsubMessage message) { List batchesToSend; messagesBatchLock.lock(); try { + outstandingPublish.messageWrapper.startPublishBatchingSpan(tracer); if (!orderingKey.isEmpty() && sequentialExecutor.keyHasError(orderingKey)) { outstandingPublish.publishResult.setException( SequentialExecutorService.CallbackExecutor.CANCELLATION_EXCEPTION); @@ -452,12 +469,23 @@ private ApiFuture publishCall(OutstandingBatch outstandingBatch if (enableCompression && outstandingBatch.batchSizeBytes >= compressionBytesThreshold) { context = publishContextWithCompression; } + + int numMessagesInBatch = outstandingBatch.size(); + List pubsubMessagesList = new ArrayList(numMessagesInBatch); + List messageWrappers = outstandingBatch.getMessageWrappers(); + for (PubsubMessageWrapper messageWrapper : messageWrappers) { + messageWrapper.endPublishBatchingSpan(); + pubsubMessagesList.add(messageWrapper.getPubsubMessage()); + } + + outstandingBatch.publishRpcSpan = OpenTelemetryUtil.startPublishRpcSpan(tracer, TopicName.parse(topicName), messageWrappers); + return publisherStub .publishCallable() .futureCall( PublishRequest.newBuilder() .setTopic(topicName) - .addAllMessages(outstandingBatch.getMessages()) + .addAllMessages(pubsubMessagesList) .build(), context); } @@ -541,6 +569,7 @@ private final class OutstandingBatch { int attempt; int batchSizeBytes; final String orderingKey; + Span publishRpcSpan; OutstandingBatch( List outstandingPublishes, int batchSizeBytes, String orderingKey) { @@ -555,10 +584,10 @@ int size() { return outstandingPublishes.size(); } - private List getMessages() { - List results = new ArrayList<>(outstandingPublishes.size()); + private List getMessageWrappers() { + List results = new ArrayList<>(outstandingPublishes.size()); for (OutstandingPublish outstandingPublish : outstandingPublishes) { - results.add(outstandingPublish.message); + results.add(outstandingPublish.messageWrapper); } return results; } @@ -570,6 +599,7 @@ private void onFailure(Throwable t) { } outstandingPublish.publishResult.setException(t); } + OpenTelemetryUtil.setPublishRpcSpanException(publishRpcSpan, t); } private void onSuccess(Iterable results) { @@ -580,19 +610,21 @@ private void onSuccess(Iterable results) { flowController.release(nextPublish.messageSize); } nextPublish.publishResult.set(messageId); + nextPublish.messageWrapper.endPublisherSpan(); } + OpenTelemetryUtil.endPublishRpcSpan(publishRpcSpan); } } private static final class OutstandingPublish { final SettableApiFuture publishResult; - final PubsubMessage message; + final PubsubMessageWrapper messageWrapper; final int messageSize; - OutstandingPublish(PubsubMessage message) { + OutstandingPublish(PubsubMessageWrapper messageWrapper) { this.publishResult = SettableApiFuture.create(); - this.message = message; - this.messageSize = message.getSerializedSize(); + this.messageWrapper = messageWrapper; + this.messageSize = messageWrapper.getPubsubMessage().getSerializedSize(); } } @@ -749,6 +781,8 @@ public PubsubMessage apply(PubsubMessage input) { private boolean enableCompression = DEFAULT_ENABLE_COMPRESSION; private long compressionBytesThreshold = DEFAULT_COMPRESSION_BYTES_THRESHOLD; + private OpenTelemetry openTelemetry = null; + private Builder(String topic) { this.topicName = Preconditions.checkNotNull(topic); } @@ -880,6 +914,14 @@ public Builder setCompressionBytesThreshold(long compressionBytesThreshold) { return this; } + /** + * Sets the instance of OpenTelemetry for the Publisher class. + */ + public Builder setOpenTelemetry(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + return this; + } + /** Returns the default BatchingSettings used by the client if settings are not provided. */ public static BatchingSettings getDefaultBatchingSettings() { return DEFAULT_BATCHING_SETTINGS; diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java new file mode 100644 index 000000000..22e7d7831 --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java @@ -0,0 +1,165 @@ +/* + * 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.pubsub.v1; + +import com.google.common.base.Preconditions; +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.TopicName; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; + +public class PubsubMessageWrapper { + private final PubsubMessage message; + + private final String topicName; + private final String projectName; + + private final String PUBLISHER_SPAN_NAME; + private final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; + private final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; + + private static final String SYSTEM_ATTR_KEY = "messaging.system"; + private static final String SYSTEM_ATTR_VALUE = "gcp_pubsub"; + private static final String DESTINATION_ATTR_KEY = "messaging.destination.name"; + private static final String CODE_FUNCTION_ATTR_KEY = "code.function"; + private static final String MESSAGE_ID_ATTR_KEY = "messaging.message.id"; + private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.envelope.size"; + private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; + private static final String OPERATION_ATTR_KEY = "messaging.operation"; + private static final String PROJECT_ATTR_KEY = "gcp_pubsub.project_id"; + + private Span publisherSpan; + private Span publishFlowControlSpan; + private Span publishBatchingSpan; + + public PubsubMessageWrapper(Builder builder) { + this.message = builder.message; + this.topicName = builder.topicName; + this.projectName = builder.projectName; + this.PUBLISHER_SPAN_NAME = builder.topicName + " create"; + } + + public static Builder newBuilder(PubsubMessage message, String topicName) { + return new Builder(message, TopicName.parse(topicName)); + } + + public PubsubMessage getPubsubMessage() { + return message; + } + + // Start spans + public void startPublisherSpan(Tracer tracer) { + AttributesBuilder attributesBuilder = OpenTelemetryUtil.createPublishSpanAttributesBuilder(topicName, projectName, "Publisher.publish", "create"); + attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getSerializedSize()); + if (!message.getMessageId().isEmpty()) { + attributesBuilder.put(MESSAGE_ID_ATTR_KEY, message.getMessageId()); + } + if (!message.getOrderingKey().isEmpty()) { + attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); + } + + publisherSpan = tracer.spanBuilder(PUBLISHER_SPAN_NAME).setSpanKind(SpanKind.PRODUCER) + .setAllAttributes(attributesBuilder.build()).startSpan(); + + try { + publisherSpan.makeCurrent(); + } catch (Throwable t) { + publisherSpan.setStatus(StatusCode.ERROR, "Unable to set the Publisher span as the current span."); + publisherSpan.recordException(t); + } finally { + publisherSpan.end(); + } + } + + public void startPublishFlowControlSpan(Tracer tracer) { + publishFlowControlSpan = startChildSpan(tracer, PUBLISH_FLOW_CONTROL_SPAN_NAME, publisherSpan); + } + + public void startPublishBatchingSpan(Tracer tracer) { + publishBatchingSpan = startChildSpan(tracer, PUBLISH_BATCHING_SPAN_NAME, publishFlowControlSpan); + } + + // End spans + public void endPublisherSpan() { + if (publisherSpan != null) { + publisherSpan.end(); + } + } + + public void endPublishFlowControlSpan() { + if (publishFlowControlSpan != null) { + publishFlowControlSpan.end(); + } + } + + public void endPublishBatchingSpan() { + if (publishBatchingSpan != null) { + publishBatchingSpan.end(); + } + } + + // Exceptions + public void setPublishFlowControlSpanException(Throwable t) { + if (publishFlowControlSpan != null) { + publishFlowControlSpan.setStatus(StatusCode.ERROR, "Exception thrown during publish flow control."); + publishFlowControlSpan.recordException(t); + endAllPublishSpans(); + } + } + + public void setPublishBatchingSpanException(Throwable t) { + if (publishBatchingSpan != null) { + publishBatchingSpan.setStatus(StatusCode.ERROR, "Exception thrown during publish batching."); + publishBatchingSpan.recordException(t); + endAllPublishSpans(); + } + } + + // Helpers + public Span startChildSpan(Tracer tracer, String name, Span parent) { + return tracer.spanBuilder(name).setParent(Context.current().with(parent)).startSpan(); + } + + private void endAllPublishSpans() { + endPublishFlowControlSpan(); + endPublisherSpan(); + } + + protected static final class Builder { + private PubsubMessage message = null; + private String topicName = null; + private String projectName = null; + + public Builder(PubsubMessage message, TopicName topicName) { + this.message = message; + this.topicName = topicName.getTopic(); + this.projectName = topicName.getProject(); + } + + public PubsubMessageWrapper build() { + Preconditions.checkArgument(this.topicName != null); + Preconditions.checkArgument(this.projectName != null); + return new PubsubMessageWrapper(this); + } + } +} diff --git a/samples/snippets/src/main/java/pubsub/PublisherExample.java b/samples/snippets/src/main/java/pubsub/PublisherExample.java index e4dc39a89..ef1e8908f 100644 --- a/samples/snippets/src/main/java/pubsub/PublisherExample.java +++ b/samples/snippets/src/main/java/pubsub/PublisherExample.java @@ -28,23 +28,43 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import io.opentelemetry.exporter.logging.LoggingSpanExporter; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.semconv.ResourceAttributes; + public class PublisherExample { public static void main(String... args) throws Exception { // TODO(developer): Replace these variables before running the sample. - String projectId = "your-project-id"; - String topicId = "your-topic-id"; + String projectId = "cloud-pubsub-experiments"; + String topicId = "mike-topic-test"; publisherExample(projectId, topicId); } public static void publisherExample(String projectId, String topicId) throws IOException, ExecutionException, InterruptedException { + Resource resource = Resource.getDefault().toBuilder() + .put(ResourceAttributes.SERVICE_NAME, "publisher-example").build(); + SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() + .setResource(resource) + .addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create())) + .build(); + + OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() + .setTracerProvider(sdkTracerProvider) + .buildAndRegisterGlobal(); + TopicName topicName = TopicName.of(projectId, topicId); Publisher publisher = null; try { // Create a publisher instance with default settings bound to the topic - publisher = Publisher.newBuilder(topicName).build(); + publisher = Publisher.newBuilder(topicName).setOpenTelemetry(openTelemetry).build(); String message = "Hello World!"; ByteString data = ByteString.copyFromUtf8(message); From 469f220d58874bc3198240e4ebc04eac2886f304 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Thu, 20 Jun 2024 23:06:08 +0000 Subject: [PATCH 02/46] feat: Publish-side trace context injection --- google-cloud-pubsub/pom.xml | 5 + .../cloud/pubsub/v1/OpenTelemetryUtil.java | 45 ++++--- .../com/google/cloud/pubsub/v1/Publisher.java | 33 ++++- .../cloud/pubsub/v1/PubsubMessageWrapper.java | 126 ++++++++++++------ .../pubsub/OpenTelemetryPublisherExample.java | 99 ++++++++++++++ .../main/java/pubsub/PublisherExample.java | 120 +++++++---------- 6 files changed, 292 insertions(+), 136 deletions(-) create mode 100644 samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java diff --git a/google-cloud-pubsub/pom.xml b/google-cloud-pubsub/pom.xml index 75a1479c8..0958c800c 100644 --- a/google-cloud-pubsub/pom.xml +++ b/google-cloud-pubsub/pom.xml @@ -105,6 +105,11 @@ opentelemetry-api 1.38.0 + + io.opentelemetry + opentelemetry-context + 1.38.0 + diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java index cf7f24547..df0f3d7b4 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java @@ -16,16 +16,16 @@ package com.google.cloud.pubsub.v1; -import com.google.pubsub.v1.PubsubMessage; import com.google.pubsub.v1.TopicName; + import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.context.Context; import java.util.List; public class OpenTelemetryUtil { @@ -35,9 +35,6 @@ public class OpenTelemetryUtil { private static final String SYSTEM_ATTR_VALUE = "gcp_pubsub"; private static final String DESTINATION_ATTR_KEY = "messaging.destination.name"; private static final String CODE_FUNCTION_ATTR_KEY = "code.function"; - private static final String MESSAGE_ID_ATTR_KEY = "messaging.message.id"; - private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.envelope.size"; - private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; private static final String MESSAGE_BATCH_SIZE_ATTR_KEY = "messaging.batch.message_count"; private static final String OPERATION_ATTR_KEY = "messaging.operation"; private static final String PROJECT_ATTR_KEY = "gcp_pubsub.project_id"; @@ -46,7 +43,7 @@ public static final AttributesBuilder createPublishSpanAttributesBuilder( String topicName, String projectName, String codeFunction, String operation) { AttributesBuilder attributesBuilder = Attributes.builder() .put(SYSTEM_ATTR_KEY, SYSTEM_ATTR_VALUE) - .put(DESTINATION_ATTR_KEY, topicName) + .put(DESTINATION_ATTR_KEY, TopicName.of(topicName, projectName).toString()) .put(PROJECT_ATTR_KEY, projectName) .put(CODE_FUNCTION_ATTR_KEY, codeFunction) .put(OPERATION_ATTR_KEY, operation); @@ -55,28 +52,36 @@ public static final AttributesBuilder createPublishSpanAttributesBuilder( } public static final Span startPublishRpcSpan(Tracer tracer, TopicName topicName, - List messages) { - Attributes attributes = createPublishSpanAttributesBuilder(topicName.getTopic(), topicName.getProject(), - "Publisher.publishCall", - "publish") - .put(MESSAGE_BATCH_SIZE_ATTR_KEY, messages.size()).build(); - Span publishRpcSpan = tracer.spanBuilder(topicName + PUBLISH_RPC_SPAN_SUFFIX).setSpanKind(SpanKind.CLIENT) - .setAllAttributes(attributes) - .startSpan(); - return publishRpcSpan; + List messages, boolean enableOpenTelemetryTracing) { + if (enableOpenTelemetryTracing && tracer != null) { + Attributes attributes = createPublishSpanAttributesBuilder(topicName.getTopic(), topicName.getProject(), + "Publisher.publishCall", + "publish") + .put(MESSAGE_BATCH_SIZE_ATTR_KEY, messages.size()).build(); + SpanBuilder publishRpcSpanBuilder = tracer.spanBuilder(topicName.getTopic() + PUBLISH_RPC_SPAN_SUFFIX) + .setSpanKind(SpanKind.CLIENT) + .setAllAttributes(attributes); + + for (PubsubMessageWrapper message : messages) { + Attributes linkAttributes = Attributes.builder().put(OPERATION_ATTR_KEY, "publish").build(); + publishRpcSpanBuilder.addLink(message.getPublisherSpan().getSpanContext(), linkAttributes); + } + return publishRpcSpanBuilder.startSpan(); + } + return null; } - public static final void endPublishRpcSpan(Span publishRpcSpan) { - if (publishRpcSpan != null) { + public static final void endPublishRpcSpan(Span publishRpcSpan, boolean enableOpenTelemetryTracing) { + if (enableOpenTelemetryTracing && publishRpcSpan != null) { publishRpcSpan.end(); } } - public static final void setPublishRpcSpanException(Span publishRpcSpan, Throwable t) { - if (publishRpcSpan != null) { + public static final void setPublishRpcSpanException(Span publishRpcSpan, Throwable t, boolean enableOpenTelemetryTracing) { + if (enableOpenTelemetryTracing && publishRpcSpan != null) { publishRpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on publish RPC."); publishRpcSpan.recordException(t); - endPublishRpcSpan(publishRpcSpan); + endPublishRpcSpan(publishRpcSpan, enableOpenTelemetryTracing); } } } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java index 59b7c59b9..2cccfdd62 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java @@ -55,6 +55,10 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.context.propagation.TextMapPropagator; + import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -129,6 +133,9 @@ public class Publisher implements PublisherInterface { private final GrpcCallContext publishContext; private final GrpcCallContext publishContextWithCompression; + private final boolean enableOpenTelemetryTracing; + private final OpenTelemetry openTelemetry; + private TextMapPropagator propagator; private Tracer tracer = null; /** The maximum number of messages in one request. Defined by the API. */ @@ -159,7 +166,10 @@ private Publisher(Builder builder) throws IOException { this.messageTransform = builder.messageTransform; this.enableCompression = builder.enableCompression; this.compressionBytesThreshold = builder.compressionBytesThreshold; - if (builder.openTelemetry != null) { + this.enableOpenTelemetryTracing = builder.enableOpenTelemetryTracing; + this.openTelemetry = builder.openTelemetry; + if (this.openTelemetry != null) { + this.propagator = W3CTraceContextPropagator.getInstance(); this.tracer = builder.openTelemetry.getTracer(OPEN_TELEMETRY_TRACER_NAME); } @@ -269,9 +279,10 @@ public ApiFuture publish(PubsubMessage message) { + "Publisher client. Please create a Publisher client with " + "setEnableMessageOrdering(true) in the builder."); - PubsubMessageWrapper messageWrapper = PubsubMessageWrapper.newBuilder(messageTransform.apply(message), topicName) + PubsubMessageWrapper messageWrapper = PubsubMessageWrapper + .newBuilder(messageTransform.apply(message), topicName, enableOpenTelemetryTracing) .build(); - messageWrapper.startPublisherSpan(tracer); + messageWrapper.startPublisherSpan(tracer, propagator); final OutstandingPublish outstandingPublish = new OutstandingPublish(messageWrapper); @@ -475,10 +486,11 @@ private ApiFuture publishCall(OutstandingBatch outstandingBatch List messageWrappers = outstandingBatch.getMessageWrappers(); for (PubsubMessageWrapper messageWrapper : messageWrappers) { messageWrapper.endPublishBatchingSpan(); + messageWrapper.addPublishStartEvent(); pubsubMessagesList.add(messageWrapper.getPubsubMessage()); } - outstandingBatch.publishRpcSpan = OpenTelemetryUtil.startPublishRpcSpan(tracer, TopicName.parse(topicName), messageWrappers); + outstandingBatch.publishRpcSpan = OpenTelemetryUtil.startPublishRpcSpan(tracer, TopicName.parse(topicName), messageWrappers, enableOpenTelemetryTracing); return publisherStub .publishCallable() @@ -598,11 +610,14 @@ private void onFailure(Throwable t) { flowController.release(outstandingPublish.messageSize); } outstandingPublish.publishResult.setException(t); + outstandingPublish.messageWrapper.endPublisherSpan(); } - OpenTelemetryUtil.setPublishRpcSpanException(publishRpcSpan, t); + OpenTelemetryUtil.setPublishRpcSpanException(publishRpcSpan, t, enableOpenTelemetryTracing); } private void onSuccess(Iterable results) { + OpenTelemetryUtil.endPublishRpcSpan(publishRpcSpan, enableOpenTelemetryTracing); + Iterator messagesResultsIt = outstandingPublishes.iterator(); for (String messageId : results) { OutstandingPublish nextPublish = messagesResultsIt.next(); @@ -610,9 +625,9 @@ private void onSuccess(Iterable results) { flowController.release(nextPublish.messageSize); } nextPublish.publishResult.set(messageId); + nextPublish.messageWrapper.setMessageIdSpanAttribute(messageId); nextPublish.messageWrapper.endPublisherSpan(); } - OpenTelemetryUtil.endPublishRpcSpan(publishRpcSpan); } } @@ -781,6 +796,7 @@ public PubsubMessage apply(PubsubMessage input) { private boolean enableCompression = DEFAULT_ENABLE_COMPRESSION; private long compressionBytesThreshold = DEFAULT_COMPRESSION_BYTES_THRESHOLD; + private boolean enableOpenTelemetryTracing = false; private OpenTelemetry openTelemetry = null; private Builder(String topic) { @@ -914,6 +930,11 @@ public Builder setCompressionBytesThreshold(long compressionBytesThreshold) { return this; } + public Builder setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing) { + this.enableOpenTelemetryTracing = enableOpenTelemetryTracing; + return this; + } + /** * Sets the instance of OpenTelemetry for the Publisher class. */ diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java index 22e7d7831..087c9554e 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java @@ -19,34 +19,35 @@ import com.google.common.base.Preconditions; import com.google.pubsub.v1.PubsubMessage; import com.google.pubsub.v1.TopicName; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.common.Attributes; + import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.context.propagation.TextMapSetter; public class PubsubMessageWrapper { - private final PubsubMessage message; + public PubsubMessage message; private final String topicName; private final String projectName; + private final boolean enableOpenTelemetryTracing; + private final String PUBLISHER_SPAN_NAME; - private final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; - private final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; + private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; + private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; + private static final String PUBLISH_START_EVENT = "publish start"; + private static final String PUBLISH_END_EVENT = "publish end"; + + private static final String GOOGCLIENT_PREFIX = "googclient_"; - private static final String SYSTEM_ATTR_KEY = "messaging.system"; - private static final String SYSTEM_ATTR_VALUE = "gcp_pubsub"; - private static final String DESTINATION_ATTR_KEY = "messaging.destination.name"; - private static final String CODE_FUNCTION_ATTR_KEY = "code.function"; private static final String MESSAGE_ID_ATTR_KEY = "messaging.message.id"; private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.envelope.size"; private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; - private static final String OPERATION_ATTR_KEY = "messaging.operation"; - private static final String PROJECT_ATTR_KEY = "gcp_pubsub.project_id"; private Span publisherSpan; private Span publishFlowControlSpan; @@ -56,71 +57,113 @@ public PubsubMessageWrapper(Builder builder) { this.message = builder.message; this.topicName = builder.topicName; this.projectName = builder.projectName; + this.enableOpenTelemetryTracing = builder.enableOpenTelemetryTracing; this.PUBLISHER_SPAN_NAME = builder.topicName + " create"; } - public static Builder newBuilder(PubsubMessage message, String topicName) { - return new Builder(message, TopicName.parse(topicName)); + public static Builder newBuilder(PubsubMessage message, String topicName, boolean enableOpenTelemetryTracing) { + return new Builder(message, TopicName.parse(topicName), enableOpenTelemetryTracing); } + // Getters + public PubsubMessage getPubsubMessage() { return message; } + public Span getPublisherSpan() { + return publisherSpan; + } + // Start spans - public void startPublisherSpan(Tracer tracer) { - AttributesBuilder attributesBuilder = OpenTelemetryUtil.createPublishSpanAttributesBuilder(topicName, projectName, "Publisher.publish", "create"); - attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getSerializedSize()); - if (!message.getMessageId().isEmpty()) { - attributesBuilder.put(MESSAGE_ID_ATTR_KEY, message.getMessageId()); + public void startPublisherSpan(Tracer tracer, TextMapPropagator propagator) { + if (enableOpenTelemetryTracing && tracer != null) { + + AttributesBuilder attributesBuilder = OpenTelemetryUtil.createPublishSpanAttributesBuilder(topicName, projectName, + "Publisher.publish", "create"); + attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getSerializedSize()); + if (!message.getMessageId().isEmpty()) { + attributesBuilder.put(MESSAGE_ID_ATTR_KEY, message.getMessageId()); + } + if (!message.getOrderingKey().isEmpty()) { + attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); + } + + publisherSpan = tracer.spanBuilder(PUBLISHER_SPAN_NAME).setSpanKind(SpanKind.PRODUCER) + .setAllAttributes(attributesBuilder.build()).startSpan(); + + if (publisherSpan.getSpanContext().isValid()) { + injectSpanContext(propagator); + } } - if (!message.getOrderingKey().isEmpty()) { - attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); + } + + public void startPublishFlowControlSpan(Tracer tracer) { + if (enableOpenTelemetryTracing && tracer != null) { + publishFlowControlSpan = startChildSpan(tracer, PUBLISH_FLOW_CONTROL_SPAN_NAME, publisherSpan); } + } - publisherSpan = tracer.spanBuilder(PUBLISHER_SPAN_NAME).setSpanKind(SpanKind.PRODUCER) - .setAllAttributes(attributesBuilder.build()).startSpan(); + public void startPublishBatchingSpan(Tracer tracer) { + if (enableOpenTelemetryTracing && tracer != null) { + publishBatchingSpan = startChildSpan(tracer, PUBLISH_BATCHING_SPAN_NAME, publisherSpan); + } + } - try { - publisherSpan.makeCurrent(); - } catch (Throwable t) { - publisherSpan.setStatus(StatusCode.ERROR, "Unable to set the Publisher span as the current span."); - publisherSpan.recordException(t); - } finally { - publisherSpan.end(); + public void addPublishStartEvent() { + if (enableOpenTelemetryTracing && publisherSpan != null) { + publisherSpan.addEvent(PUBLISH_START_EVENT); } } - public void startPublishFlowControlSpan(Tracer tracer) { - publishFlowControlSpan = startChildSpan(tracer, PUBLISH_FLOW_CONTROL_SPAN_NAME, publisherSpan); + public void addPublishEndEvent() { + if (enableOpenTelemetryTracing && publisherSpan != null) { + publisherSpan.addEvent(PUBLISH_END_EVENT); + } } - public void startPublishBatchingSpan(Tracer tracer) { - publishBatchingSpan = startChildSpan(tracer, PUBLISH_BATCHING_SPAN_NAME, publishFlowControlSpan); + private void injectSpanContext(TextMapPropagator propagator) { + if (enableOpenTelemetryTracing && publisherSpan != null) { + TextMapSetter injectMessageAttributes = new TextMapSetter() { + @Override + public void set(PubsubMessageWrapper carrier, String key, String value) { + PubsubMessage newMessage = PubsubMessage.newBuilder(carrier.message).putAttributes(GOOGCLIENT_PREFIX + key, value).build(); + carrier.message = newMessage; + } + }; + propagator.inject(Context.current().with(publisherSpan), this, injectMessageAttributes); + } + } + + public void setMessageIdSpanAttribute(String messageId) { + if (enableOpenTelemetryTracing && publisherSpan != null) { + publisherSpan.setAttribute(MESSAGE_ID_ATTR_KEY, messageId); + } } // End spans public void endPublisherSpan() { - if (publisherSpan != null) { + if (enableOpenTelemetryTracing && publisherSpan != null) { + addPublishEndEvent(); publisherSpan.end(); } } public void endPublishFlowControlSpan() { - if (publishFlowControlSpan != null) { + if (enableOpenTelemetryTracing && publishFlowControlSpan != null) { publishFlowControlSpan.end(); } } public void endPublishBatchingSpan() { - if (publishBatchingSpan != null) { + if (enableOpenTelemetryTracing && publishBatchingSpan != null) { publishBatchingSpan.end(); } } // Exceptions public void setPublishFlowControlSpanException(Throwable t) { - if (publishFlowControlSpan != null) { + if (enableOpenTelemetryTracing && publishFlowControlSpan != null) { publishFlowControlSpan.setStatus(StatusCode.ERROR, "Exception thrown during publish flow control."); publishFlowControlSpan.recordException(t); endAllPublishSpans(); @@ -128,7 +171,7 @@ public void setPublishFlowControlSpanException(Throwable t) { } public void setPublishBatchingSpanException(Throwable t) { - if (publishBatchingSpan != null) { + if (enableOpenTelemetryTracing && publishBatchingSpan != null) { publishBatchingSpan.setStatus(StatusCode.ERROR, "Exception thrown during publish batching."); publishBatchingSpan.recordException(t); endAllPublishSpans(); @@ -136,12 +179,13 @@ public void setPublishBatchingSpanException(Throwable t) { } // Helpers - public Span startChildSpan(Tracer tracer, String name, Span parent) { + private Span startChildSpan(Tracer tracer, String name, Span parent) { return tracer.spanBuilder(name).setParent(Context.current().with(parent)).startSpan(); } private void endAllPublishSpans() { endPublishFlowControlSpan(); + endPublishBatchingSpan(); endPublisherSpan(); } @@ -149,11 +193,13 @@ protected static final class Builder { private PubsubMessage message = null; private String topicName = null; private String projectName = null; + private boolean enableOpenTelemetryTracing = false; - public Builder(PubsubMessage message, TopicName topicName) { + public Builder(PubsubMessage message, TopicName topicName, boolean enableOpenTelemetryTracing) { this.message = message; this.topicName = topicName.getTopic(); this.projectName = topicName.getProject(); + this.enableOpenTelemetryTracing = enableOpenTelemetryTracing; } public PubsubMessageWrapper build() { diff --git a/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java b/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java new file mode 100644 index 000000000..5019a9f4c --- /dev/null +++ b/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java @@ -0,0 +1,99 @@ +/* + * Copyright 2016 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 pubsub; + +import com.google.api.core.ApiFuture; +import com.google.cloud.pubsub.v1.Publisher; +import com.google.protobuf.ByteString; +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.TopicName; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import com.google.cloud.opentelemetry.trace.TraceConfiguration; +import com.google.cloud.opentelemetry.trace.TraceExporter; +import io.opentelemetry.exporter.logging.LoggingSpanExporter; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.semconv.ResourceAttributes; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.propagation.ContextPropagators; + +public class OpenTelemetryPublisherExample { + public static void main(String... args) throws Exception { + // TODO(developer): Replace these variables before running the sample. + String projectId = "cloud-pubsub-experiments"; + String topicId = "mike-topic-test"; + + openTelemetryPublisherExample(projectId, topicId); + } + + public static void openTelemetryPublisherExample(String projectId, String topicId) + throws IOException, ExecutionException, InterruptedException { + Resource resource = Resource.getDefault().toBuilder() + .put(ResourceAttributes.SERVICE_NAME, "publisher-example").build(); + + TraceExporter traceExporter = TraceExporter.createWithConfiguration(TraceConfiguration.builder().setProjectId(projectId).build()); + + SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() + .setResource(resource) + // .addSpanProcessor(BatchSpanProcessor.builder(traceExporter).build()) + .addSpanProcessor(SimpleSpanProcessor.create(traceExporter)) + // .addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create())) + .setSampler(Sampler.alwaysOn()) + .build(); + + OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() + .setTracerProvider(sdkTracerProvider) + .setPropagators(ContextPropagators.create(TextMapPropagator.composite(W3CTraceContextPropagator.getInstance()))) + .buildAndRegisterGlobal(); + + TopicName topicName = TopicName.of(projectId, topicId); + + Publisher publisher = null; + try { + // Create a publisher instance with default settings bound to the topic + publisher = Publisher.newBuilder(topicName).setOpenTelemetry(openTelemetry).setEnableOpenTelemetryTracing(true).build(); + + for (int i = 0; i < 1; i++) { + String message = "Hello World!"; + ByteString data = ByteString.copyFromUtf8(message); + PubsubMessage pubsubMessage = PubsubMessage.newBuilder().setData(data).build(); + + // Once published, returns a server-assigned message id (unique within the topic) + ApiFuture messageIdFuture = publisher.publish(pubsubMessage); + System.out.println("Published message ID: " + messageIdFuture.get()); + } + } finally { + if (publisher != null) { + // When finished with the publisher, shutdown to free up resources. + publisher.shutdown(); + publisher.awaitTermination(1, TimeUnit.MINUTES); + } + } + } +} +// [END pubsub_quickstart_publisher] +// [END pubsub_publish] diff --git a/samples/snippets/src/main/java/pubsub/PublisherExample.java b/samples/snippets/src/main/java/pubsub/PublisherExample.java index ef1e8908f..18cc27396 100644 --- a/samples/snippets/src/main/java/pubsub/PublisherExample.java +++ b/samples/snippets/src/main/java/pubsub/PublisherExample.java @@ -14,74 +14,54 @@ * limitations under the License. */ -package pubsub; + package pubsub; -// [START pubsub_quickstart_publisher] -// [START pubsub_publish] - -import com.google.api.core.ApiFuture; -import com.google.cloud.pubsub.v1.Publisher; -import com.google.protobuf.ByteString; -import com.google.pubsub.v1.PubsubMessage; -import com.google.pubsub.v1.TopicName; -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; - -import io.opentelemetry.exporter.logging.LoggingSpanExporter; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; -import io.opentelemetry.sdk.logs.SdkLoggerProvider; -import io.opentelemetry.semconv.ResourceAttributes; - -public class PublisherExample { - public static void main(String... args) throws Exception { - // TODO(developer): Replace these variables before running the sample. - String projectId = "cloud-pubsub-experiments"; - String topicId = "mike-topic-test"; - - publisherExample(projectId, topicId); - } - - public static void publisherExample(String projectId, String topicId) - throws IOException, ExecutionException, InterruptedException { - Resource resource = Resource.getDefault().toBuilder() - .put(ResourceAttributes.SERVICE_NAME, "publisher-example").build(); - SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() - .setResource(resource) - .addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create())) - .build(); - - OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() - .setTracerProvider(sdkTracerProvider) - .buildAndRegisterGlobal(); - - TopicName topicName = TopicName.of(projectId, topicId); - - Publisher publisher = null; - try { - // Create a publisher instance with default settings bound to the topic - publisher = Publisher.newBuilder(topicName).setOpenTelemetry(openTelemetry).build(); - - String message = "Hello World!"; - ByteString data = ByteString.copyFromUtf8(message); - PubsubMessage pubsubMessage = PubsubMessage.newBuilder().setData(data).build(); - - // Once published, returns a server-assigned message id (unique within the topic) - ApiFuture messageIdFuture = publisher.publish(pubsubMessage); - String messageId = messageIdFuture.get(); - System.out.println("Published message ID: " + messageId); - } finally { - if (publisher != null) { - // When finished with the publisher, shutdown to free up resources. - publisher.shutdown(); - publisher.awaitTermination(1, TimeUnit.MINUTES); - } - } - } -} -// [END pubsub_quickstart_publisher] -// [END pubsub_publish] + // [START pubsub_quickstart_publisher] + // [START pubsub_publish] + + import com.google.api.core.ApiFuture; + import com.google.cloud.pubsub.v1.Publisher; + import com.google.protobuf.ByteString; + import com.google.pubsub.v1.PubsubMessage; + import com.google.pubsub.v1.TopicName; + import java.io.IOException; + import java.util.concurrent.ExecutionException; + import java.util.concurrent.TimeUnit; + + public class PublisherExample { + public static void main(String... args) throws Exception { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String topicId = "your-topic-id"; + + publisherExample(projectId, topicId); + } + + public static void publisherExample(String projectId, String topicId) + throws IOException, ExecutionException, InterruptedException { + TopicName topicName = TopicName.of(projectId, topicId); + + Publisher publisher = null; + try { + // Create a publisher instance with default settings bound to the topic + publisher = Publisher.newBuilder(topicName).build(); + + String message = "Hello World!"; + ByteString data = ByteString.copyFromUtf8(message); + PubsubMessage pubsubMessage = PubsubMessage.newBuilder().setData(data).build(); + + // Once published, returns a server-assigned message id (unique within the topic) + ApiFuture messageIdFuture = publisher.publish(pubsubMessage); + String messageId = messageIdFuture.get(); + System.out.println("Published message ID: " + messageId); + } finally { + if (publisher != null) { + // When finished with the publisher, shutdown to free up resources. + publisher.shutdown(); + publisher.awaitTermination(1, TimeUnit.MINUTES); + } + } + } + } + // [END pubsub_quickstart_publisher] + // [END pubsub_publish] \ No newline at end of file From ad31b5330d27bf51d2ffe8f5deb8908f6aa45dce Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 24 Jun 2024 16:15:20 +0000 Subject: [PATCH 03/46] feat: Tests and improvements to publish side OTel tracing --- google-cloud-pubsub/pom.xml | 15 + .../cloud/pubsub/v1/OpenTelemetryUtil.java | 68 ++-- .../com/google/cloud/pubsub/v1/Publisher.java | 27 +- .../cloud/pubsub/v1/PubsubMessageWrapper.java | 122 +++++--- .../cloud/pubsub/v1/OpenTelemetryTest.java | 296 ++++++++++++++++++ pom.xml | 6 + samples/snippets/pom.xml | 21 ++ .../pubsub/OpenTelemetryPublisherExample.java | 5 +- 8 files changed, 472 insertions(+), 88 deletions(-) create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java diff --git a/google-cloud-pubsub/pom.xml b/google-cloud-pubsub/pom.xml index 0958c800c..9bb21deb5 100644 --- a/google-cloud-pubsub/pom.xml +++ b/google-cloud-pubsub/pom.xml @@ -152,6 +152,21 @@ opencensus-impl test + + io.opentelemetry + opentelemetry-sdk + test + + + io.opentelemetry + opentelemetry-sdk-testing + test + + + org.assertj + assertj-core + test + com.google.api diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java index df0f3d7b4..8a077d05a 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java @@ -17,20 +17,15 @@ package com.google.cloud.pubsub.v1; import com.google.pubsub.v1.TopicName; - -import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import java.util.List; public class OpenTelemetryUtil { - private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; - private static final String SYSTEM_ATTR_KEY = "messaging.system"; private static final String SYSTEM_ATTR_VALUE = "gcp_pubsub"; private static final String DESTINATION_ATTR_KEY = "messaging.destination.name"; @@ -39,45 +34,68 @@ public class OpenTelemetryUtil { private static final String OPERATION_ATTR_KEY = "messaging.operation"; private static final String PROJECT_ATTR_KEY = "gcp_pubsub.project_id"; + private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; + + /** Populates attributes that are common the publisher parent span and publish RPC span. */ public static final AttributesBuilder createPublishSpanAttributesBuilder( - String topicName, String projectName, String codeFunction, String operation) { - AttributesBuilder attributesBuilder = Attributes.builder() - .put(SYSTEM_ATTR_KEY, SYSTEM_ATTR_VALUE) - .put(DESTINATION_ATTR_KEY, TopicName.of(topicName, projectName).toString()) - .put(PROJECT_ATTR_KEY, projectName) - .put(CODE_FUNCTION_ATTR_KEY, codeFunction) - .put(OPERATION_ATTR_KEY, operation); + TopicName topicName, String codeFunction, String operation) { + AttributesBuilder attributesBuilder = + Attributes.builder() + .put(SYSTEM_ATTR_KEY, SYSTEM_ATTR_VALUE) + .put(DESTINATION_ATTR_KEY, topicName.getTopic()) + .put(PROJECT_ATTR_KEY, topicName.getProject()) + .put(CODE_FUNCTION_ATTR_KEY, codeFunction) + .put(OPERATION_ATTR_KEY, operation); return attributesBuilder; } - public static final Span startPublishRpcSpan(Tracer tracer, TopicName topicName, - List messages, boolean enableOpenTelemetryTracing) { + /** + * Creates, starts, and returns a publish RPC span for the given message batch. Bi-directional + * links with the publisher parent span are created for each message in the batch. + */ + public static final Span startPublishRpcSpan( + Tracer tracer, + TopicName topicName, + List messages, + boolean enableOpenTelemetryTracing) { if (enableOpenTelemetryTracing && tracer != null) { - Attributes attributes = createPublishSpanAttributesBuilder(topicName.getTopic(), topicName.getProject(), - "Publisher.publishCall", - "publish") - .put(MESSAGE_BATCH_SIZE_ATTR_KEY, messages.size()).build(); - SpanBuilder publishRpcSpanBuilder = tracer.spanBuilder(topicName.getTopic() + PUBLISH_RPC_SPAN_SUFFIX) - .setSpanKind(SpanKind.CLIENT) - .setAllAttributes(attributes); + Attributes attributes = + createPublishSpanAttributesBuilder(topicName, "Publisher.publishCall", "publish") + .put(MESSAGE_BATCH_SIZE_ATTR_KEY, messages.size()) + .build(); + Span publishRpcSpan = + tracer + .spanBuilder(topicName.getTopic() + PUBLISH_RPC_SPAN_SUFFIX) + .setSpanKind(SpanKind.CLIENT) + .setAllAttributes(attributes) + .startSpan(); for (PubsubMessageWrapper message : messages) { Attributes linkAttributes = Attributes.builder().put(OPERATION_ATTR_KEY, "publish").build(); - publishRpcSpanBuilder.addLink(message.getPublisherSpan().getSpanContext(), linkAttributes); + publishRpcSpan.addLink(message.getPublisherSpan().getSpanContext(), linkAttributes); + message.getPublisherSpan().addLink(publishRpcSpan.getSpanContext(), linkAttributes); + message.addPublishStartEvent(); } - return publishRpcSpanBuilder.startSpan(); + return publishRpcSpan; } return null; } - public static final void endPublishRpcSpan(Span publishRpcSpan, boolean enableOpenTelemetryTracing) { + /** Ends the given publish RPC span if it exists. */ + public static final void endPublishRpcSpan( + Span publishRpcSpan, boolean enableOpenTelemetryTracing) { if (enableOpenTelemetryTracing && publishRpcSpan != null) { publishRpcSpan.end(); } } - public static final void setPublishRpcSpanException(Span publishRpcSpan, Throwable t, boolean enableOpenTelemetryTracing) { + /** + * Sets an error status and records an exception when an exception is thrown when publishing the + * message batch. + */ + public static final void setPublishRpcSpanException( + Span publishRpcSpan, Throwable t, boolean enableOpenTelemetryTracing) { if (enableOpenTelemetryTracing && publishRpcSpan != null) { publishRpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on publish RPC."); publishRpcSpan.recordException(t); diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java index 2cccfdd62..3790806a4 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java @@ -55,10 +55,6 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; -import io.opentelemetry.context.propagation.ContextPropagators; -import io.opentelemetry.context.propagation.TextMapPropagator; - import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -135,7 +131,6 @@ public class Publisher implements PublisherInterface { private final boolean enableOpenTelemetryTracing; private final OpenTelemetry openTelemetry; - private TextMapPropagator propagator; private Tracer tracer = null; /** The maximum number of messages in one request. Defined by the API. */ @@ -169,7 +164,6 @@ private Publisher(Builder builder) throws IOException { this.enableOpenTelemetryTracing = builder.enableOpenTelemetryTracing; this.openTelemetry = builder.openTelemetry; if (this.openTelemetry != null) { - this.propagator = W3CTraceContextPropagator.getInstance(); this.tracer = builder.openTelemetry.getTracer(OPEN_TELEMETRY_TRACER_NAME); } @@ -279,10 +273,11 @@ public ApiFuture publish(PubsubMessage message) { + "Publisher client. Please create a Publisher client with " + "setEnableMessageOrdering(true) in the builder."); - PubsubMessageWrapper messageWrapper = PubsubMessageWrapper - .newBuilder(messageTransform.apply(message), topicName, enableOpenTelemetryTracing) - .build(); - messageWrapper.startPublisherSpan(tracer, propagator); + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder( + messageTransform.apply(message), topicName, enableOpenTelemetryTracing) + .build(); + messageWrapper.startPublisherSpan(tracer); final OutstandingPublish outstandingPublish = new OutstandingPublish(messageWrapper); @@ -486,11 +481,12 @@ private ApiFuture publishCall(OutstandingBatch outstandingBatch List messageWrappers = outstandingBatch.getMessageWrappers(); for (PubsubMessageWrapper messageWrapper : messageWrappers) { messageWrapper.endPublishBatchingSpan(); - messageWrapper.addPublishStartEvent(); pubsubMessagesList.add(messageWrapper.getPubsubMessage()); } - outstandingBatch.publishRpcSpan = OpenTelemetryUtil.startPublishRpcSpan(tracer, TopicName.parse(topicName), messageWrappers, enableOpenTelemetryTracing); + outstandingBatch.publishRpcSpan = + OpenTelemetryUtil.startPublishRpcSpan( + tracer, TopicName.parse(topicName), messageWrappers, enableOpenTelemetryTracing); return publisherStub .publishCallable() @@ -605,6 +601,8 @@ private List getMessageWrappers() { } private void onFailure(Throwable t) { + OpenTelemetryUtil.setPublishRpcSpanException(publishRpcSpan, t, enableOpenTelemetryTracing); + for (OutstandingPublish outstandingPublish : outstandingPublishes) { if (flowController != null) { flowController.release(outstandingPublish.messageSize); @@ -612,7 +610,6 @@ private void onFailure(Throwable t) { outstandingPublish.publishResult.setException(t); outstandingPublish.messageWrapper.endPublisherSpan(); } - OpenTelemetryUtil.setPublishRpcSpanException(publishRpcSpan, t, enableOpenTelemetryTracing); } private void onSuccess(Iterable results) { @@ -935,9 +932,7 @@ public Builder setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing) return this; } - /** - * Sets the instance of OpenTelemetry for the Publisher class. - */ + /** Sets the instance of OpenTelemetry for the Publisher class. */ public Builder setOpenTelemetry(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; return this; diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java index 087c9554e..19a90d5bd 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java @@ -19,21 +19,23 @@ import com.google.common.base.Preconditions; import com.google.pubsub.v1.PubsubMessage; import com.google.pubsub.v1.TopicName; - import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.Context; -import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.context.propagation.TextMapSetter; +/** + * A wrapper class for a {@link PubsubMessage} object that handles creation and tracking of + * OpenTelemetry {@link Span} objects for different operations that occur during publishing. + */ public class PubsubMessageWrapper { - public PubsubMessage message; + private PubsubMessage message; - private final String topicName; - private final String projectName; + private final TopicName topicName; private final boolean enableOpenTelemetryTracing; @@ -56,60 +58,71 @@ public class PubsubMessageWrapper { public PubsubMessageWrapper(Builder builder) { this.message = builder.message; this.topicName = builder.topicName; - this.projectName = builder.projectName; this.enableOpenTelemetryTracing = builder.enableOpenTelemetryTracing; - this.PUBLISHER_SPAN_NAME = builder.topicName + " create"; + this.PUBLISHER_SPAN_NAME = builder.topicName.getTopic() + " create"; } - public static Builder newBuilder(PubsubMessage message, String topicName, boolean enableOpenTelemetryTracing) { - return new Builder(message, TopicName.parse(topicName), enableOpenTelemetryTracing); + public static Builder newBuilder( + PubsubMessage message, String fullTopicName, boolean enableOpenTelemetryTracing) { + return new Builder(message, TopicName.parse(fullTopicName), enableOpenTelemetryTracing); } - // Getters - + /** Returns the PubsubMessage associated with this wrapper. */ public PubsubMessage getPubsubMessage() { return message; } + /** Returns the parent span for this message wrapper. */ public Span getPublisherSpan() { return publisherSpan; } - // Start spans - public void startPublisherSpan(Tracer tracer, TextMapPropagator propagator) { + /** + * Creates and starts the parent span with the appropriate span attributes and injects the span + * context into the {@link PubsubMessage} attributes. + */ + public void startPublisherSpan(Tracer tracer) { if (enableOpenTelemetryTracing && tracer != null) { - AttributesBuilder attributesBuilder = OpenTelemetryUtil.createPublishSpanAttributesBuilder(topicName, projectName, - "Publisher.publish", "create"); + AttributesBuilder attributesBuilder = + OpenTelemetryUtil.createPublishSpanAttributesBuilder( + topicName, "Publisher.publish", "create"); attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getSerializedSize()); - if (!message.getMessageId().isEmpty()) { - attributesBuilder.put(MESSAGE_ID_ATTR_KEY, message.getMessageId()); - } if (!message.getOrderingKey().isEmpty()) { attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); } - publisherSpan = tracer.spanBuilder(PUBLISHER_SPAN_NAME).setSpanKind(SpanKind.PRODUCER) - .setAllAttributes(attributesBuilder.build()).startSpan(); + publisherSpan = + tracer + .spanBuilder(PUBLISHER_SPAN_NAME) + .setSpanKind(SpanKind.PRODUCER) + .setAllAttributes(attributesBuilder.build()) + .startSpan(); if (publisherSpan.getSpanContext().isValid()) { - injectSpanContext(propagator); + injectSpanContext(); } } } + /** Creates a span for publish-side flow control as a child of the parent publisher span. */ public void startPublishFlowControlSpan(Tracer tracer) { if (enableOpenTelemetryTracing && tracer != null) { - publishFlowControlSpan = startChildSpan(tracer, PUBLISH_FLOW_CONTROL_SPAN_NAME, publisherSpan); + publishFlowControlSpan = + startChildSpan(tracer, PUBLISH_FLOW_CONTROL_SPAN_NAME, publisherSpan); } } + /** Creates a span for publish message batching as a child of the parent publisher span. */ public void startPublishBatchingSpan(Tracer tracer) { if (enableOpenTelemetryTracing && tracer != null) { publishBatchingSpan = startChildSpan(tracer, PUBLISH_BATCHING_SPAN_NAME, publisherSpan); } } + /** + * Creates publish start and end events that are tied to the publish RPC span start and end times. + */ public void addPublishStartEvent() { if (enableOpenTelemetryTracing && publisherSpan != null) { publisherSpan.addEvent(PUBLISH_START_EVENT); @@ -122,26 +135,17 @@ public void addPublishEndEvent() { } } - private void injectSpanContext(TextMapPropagator propagator) { - if (enableOpenTelemetryTracing && publisherSpan != null) { - TextMapSetter injectMessageAttributes = new TextMapSetter() { - @Override - public void set(PubsubMessageWrapper carrier, String key, String value) { - PubsubMessage newMessage = PubsubMessage.newBuilder(carrier.message).putAttributes(GOOGCLIENT_PREFIX + key, value).build(); - carrier.message = newMessage; - } - }; - propagator.inject(Context.current().with(publisherSpan), this, injectMessageAttributes); - } - } - + /** + * Sets the message ID attribute in the publisher parent span. This is called after the publish + * RPC returns with a message ID. + */ public void setMessageIdSpanAttribute(String messageId) { if (enableOpenTelemetryTracing && publisherSpan != null) { publisherSpan.setAttribute(MESSAGE_ID_ATTR_KEY, messageId); } } - // End spans + /** Ends the publisher parent span if it exists. */ public void endPublisherSpan() { if (enableOpenTelemetryTracing && publisherSpan != null) { addPublishEndEvent(); @@ -149,27 +153,36 @@ public void endPublisherSpan() { } } + /** Ends the publish flow control span if it exists. */ public void endPublishFlowControlSpan() { if (enableOpenTelemetryTracing && publishFlowControlSpan != null) { publishFlowControlSpan.end(); } } + /** Ends the publish batching span if it exists. */ public void endPublishBatchingSpan() { if (enableOpenTelemetryTracing && publishBatchingSpan != null) { publishBatchingSpan.end(); } } - // Exceptions + /** + * Sets an error status and records an exception when an exception is thrown during flow control. + */ public void setPublishFlowControlSpanException(Throwable t) { if (enableOpenTelemetryTracing && publishFlowControlSpan != null) { - publishFlowControlSpan.setStatus(StatusCode.ERROR, "Exception thrown during publish flow control."); + publishFlowControlSpan.setStatus( + StatusCode.ERROR, "Exception thrown during publish flow control."); publishFlowControlSpan.recordException(t); endAllPublishSpans(); } } + /** + * Sets an error status and records an exception when an exception is thrown during message + * batching. + */ public void setPublishBatchingSpanException(Throwable t) { if (enableOpenTelemetryTracing && publishBatchingSpan != null) { publishBatchingSpan.setStatus(StatusCode.ERROR, "Exception thrown during publish batching."); @@ -178,33 +191,54 @@ public void setPublishBatchingSpanException(Throwable t) { } } - // Helpers + /** Creates a child span of the given parent span. */ private Span startChildSpan(Tracer tracer, String name, Span parent) { return tracer.spanBuilder(name).setParent(Context.current().with(parent)).startSpan(); } + /** Ends all spans associated with this message wrapper. */ private void endAllPublishSpans() { endPublishFlowControlSpan(); endPublishBatchingSpan(); endPublisherSpan(); } + /** + * Injects the span context into the attributes of a Pub/Sub message for propagation to the + * subscriber client. + */ + private void injectSpanContext() { + if (enableOpenTelemetryTracing && publisherSpan != null) { + TextMapSetter injectMessageAttributes = + new TextMapSetter() { + @Override + public void set(PubsubMessageWrapper carrier, String key, String value) { + PubsubMessage newMessage = + PubsubMessage.newBuilder(carrier.message) + .putAttributes(GOOGCLIENT_PREFIX + key, value) + .build(); + carrier.message = newMessage; + } + }; + W3CTraceContextPropagator.getInstance() + .inject(Context.current().with(publisherSpan), this, injectMessageAttributes); + } + } + + /** Builder of {@link PubsubMessageWrapper PubsubMessageWrapper}. */ protected static final class Builder { private PubsubMessage message = null; - private String topicName = null; - private String projectName = null; + private TopicName topicName = null; private boolean enableOpenTelemetryTracing = false; public Builder(PubsubMessage message, TopicName topicName, boolean enableOpenTelemetryTracing) { this.message = message; - this.topicName = topicName.getTopic(); - this.projectName = topicName.getProject(); + this.topicName = topicName; this.enableOpenTelemetryTracing = enableOpenTelemetryTracing; } public PubsubMessageWrapper build() { Preconditions.checkArgument(this.topicName != null); - Preconditions.checkArgument(this.projectName != null); return new PubsubMessageWrapper(this); } } diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java new file mode 100644 index 000000000..de13c0f61 --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -0,0 +1,296 @@ +/* + * 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.pubsub.v1; + +import static org.junit.Assert.assertEquals; + +import com.google.protobuf.ByteString; +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.TopicName; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.sdk.testing.assertj.AttributesAssert; +import io.opentelemetry.sdk.testing.assertj.EventDataAssert; +import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.testing.junit4.OpenTelemetryRule; +import io.opentelemetry.sdk.trace.data.LinkData; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.data.StatusData; +import java.util.Arrays; +import java.util.List; +import org.junit.Test; + +public class OpenTelemetryTest { + private static final TopicName FULL_TOPIC_NAME = + TopicName.parse("projects/test-project/topics/test-topic"); + private static final String PROJECT_NAME = "test-project"; + private static final String ORDERING_KEY = "abc"; + private static final String MESSAGE_ID = "m0"; + + private static final String PUBLISHER_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " create"; + private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; + private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; + private static final String PUBLISH_RPC_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " publish"; + private static final String PUBLISH_START_EVENT = "publish start"; + private static final String PUBLISH_END_EVENT = "publish end"; + + private static final String SYSTEM_ATTR_KEY = "messaging.system"; + private static final String SYSTEM_ATTR_VALUE = "gcp_pubsub"; + private static final String DESTINATION_ATTR_KEY = "messaging.destination.name"; + private static final String CODE_FUNCTION_ATTR_KEY = "code.function"; + private static final String MESSAGE_BATCH_SIZE_ATTR_KEY = "messaging.batch.message_count"; + private static final String OPERATION_ATTR_KEY = "messaging.operation"; + private static final String PROJECT_ATTR_KEY = "gcp_pubsub.project_id"; + private static final String MESSAGE_ID_ATTR_KEY = "messaging.message.id"; + private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.envelope.size"; + private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; + + private static final OpenTelemetryRule openTelemetryRule = OpenTelemetryRule.create(); + + @Test + public void testPublishSpansSuccess() { + openTelemetryRule.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) + .build(); + List messageWrappers = Arrays.asList(messageWrapper); + + long messageSize = messageWrapper.getPubsubMessage().getSerializedSize(); + Tracer tracer = openTelemetryRule.getOpenTelemetry().getTracer("test"); + + // Call all span start/end methods in the expected order + messageWrapper.startPublisherSpan(tracer); + messageWrapper.startPublishFlowControlSpan(tracer); + messageWrapper.endPublishFlowControlSpan(); + messageWrapper.startPublishBatchingSpan(tracer); + messageWrapper.endPublishBatchingSpan(); + Span publishRpcSpan = + OpenTelemetryUtil.startPublishRpcSpan(tracer, FULL_TOPIC_NAME, messageWrappers, true); + OpenTelemetryUtil.endPublishRpcSpan(publishRpcSpan, true); + messageWrapper.setMessageIdSpanAttribute(MESSAGE_ID); + messageWrapper.endPublisherSpan(); + + List allSpans = openTelemetryRule.getSpans(); + assertEquals(allSpans.size(), 4); + SpanData flowControlSpanData = allSpans.get(0); + SpanData batchingSpanData = allSpans.get(1); + SpanData publishRpcSpanData = allSpans.get(2); + SpanData publisherSpanData = allSpans.get(3); + + SpanDataAssert flowControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(flowControlSpanData); + flowControlSpanDataAssert + .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) + .hasParent(publisherSpanData) + .hasEnded(); + + SpanDataAssert batchingSpanDataAssert = OpenTelemetryAssertions.assertThat(batchingSpanData); + batchingSpanDataAssert + .hasName(PUBLISH_BATCHING_SPAN_NAME) + .hasParent(publisherSpanData) + .hasEnded(); + + // Check span data, links, and attributes for the publish RPC span + SpanDataAssert publishRpcSpanDataAssert = + OpenTelemetryAssertions.assertThat(publishRpcSpanData); + publishRpcSpanDataAssert + .hasName(PUBLISH_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List publishRpcLinks = publishRpcSpanData.getLinks(); + assertEquals(publishRpcLinks.size(), messageWrappers.size()); + assertEquals(publishRpcLinks.get(0).getSpanContext(), publisherSpanData.getSpanContext()); + + assertEquals(publishRpcSpanData.getAttributes().size(), 6); + AttributesAssert publishRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(publishRpcSpanData.getAttributes()); + publishRpcSpanAttributesAssert + .containsEntry(SYSTEM_ATTR_KEY, SYSTEM_ATTR_VALUE) + .containsEntry(DESTINATION_ATTR_KEY, FULL_TOPIC_NAME.getTopic()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry(CODE_FUNCTION_ATTR_KEY, "Publisher.publishCall") + .containsEntry(OPERATION_ATTR_KEY, "publish") + .containsEntry(MESSAGE_BATCH_SIZE_ATTR_KEY, messageWrappers.size()); + + // Check span data, events, links, and attributes for the publisher create span + SpanDataAssert publishSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publishSpanDataAssert + .hasName(PUBLISHER_SPAN_NAME) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasEnded(); + + assertEquals(publisherSpanData.getEvents().size(), 2); + EventDataAssert startEventAssert = + OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(0)); + startEventAssert.hasName(PUBLISH_START_EVENT); + EventDataAssert endEventAssert = + OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(1)); + endEventAssert.hasName(PUBLISH_END_EVENT); + + List publisherLinks = publisherSpanData.getLinks(); + assertEquals(publisherLinks.size(), 1); + assertEquals(publisherLinks.get(0).getSpanContext(), publishRpcSpanData.getSpanContext()); + + assertEquals(publisherSpanData.getAttributes().size(), 8); + AttributesAssert publisherSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(publisherSpanData.getAttributes()); + publisherSpanAttributesAssert + .containsEntry(SYSTEM_ATTR_KEY, SYSTEM_ATTR_VALUE) + .containsEntry(DESTINATION_ATTR_KEY, FULL_TOPIC_NAME.getTopic()) + .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) + .containsEntry(CODE_FUNCTION_ATTR_KEY, "Publisher.publish") + .containsEntry(OPERATION_ATTR_KEY, "create") + .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) + .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) + .containsEntry(MESSAGE_ID_ATTR_KEY, MESSAGE_ID); + } + + @Test + public void testPublishFlowControlSpanFailure() { + openTelemetryRule.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) + .build(); + + Tracer tracer = openTelemetryRule.getOpenTelemetry().getTracer("test"); + + messageWrapper.startPublisherSpan(tracer); + messageWrapper.startPublishFlowControlSpan(tracer); + + Exception e = new Exception("test-exception"); + messageWrapper.setPublishFlowControlSpanException(e); + + List allSpans = openTelemetryRule.getSpans(); + assertEquals(allSpans.size(), 2); + SpanData flowControlSpanData = allSpans.get(0); + SpanData publisherSpanData = allSpans.get(1); + + SpanDataAssert flowControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(flowControlSpanData); + StatusData expectedStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown during publish flow control."); + flowControlSpanDataAssert + .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) + .hasParent(publisherSpanData) + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + + SpanDataAssert publishSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publishSpanDataAssert + .hasName(PUBLISHER_SPAN_NAME) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasEnded(); + } + + @Test + public void testPublishBatchingSpanFailure() { + openTelemetryRule.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) + .build(); + + Tracer tracer = openTelemetryRule.getOpenTelemetry().getTracer("test"); + + messageWrapper.startPublisherSpan(tracer); + messageWrapper.startPublishBatchingSpan(tracer); + + Exception e = new Exception("test-exception"); + messageWrapper.setPublishBatchingSpanException(e); + + List allSpans = openTelemetryRule.getSpans(); + assertEquals(allSpans.size(), 2); + SpanData batchingSpanData = allSpans.get(0); + SpanData publisherSpanData = allSpans.get(1); + + SpanDataAssert batchingSpanDataAssert = OpenTelemetryAssertions.assertThat(batchingSpanData); + StatusData expectedStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown during publish batching."); + batchingSpanDataAssert + .hasName(PUBLISH_BATCHING_SPAN_NAME) + .hasParent(publisherSpanData) + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + + SpanDataAssert publishSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publishSpanDataAssert + .hasName(PUBLISHER_SPAN_NAME) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasEnded(); + } + + @Test + public void testPublishRpcSpanFailure() { + openTelemetryRule.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) + .build(); + + List messageWrappers = Arrays.asList(messageWrapper); + Tracer tracer = openTelemetryRule.getOpenTelemetry().getTracer("test"); + + messageWrapper.startPublisherSpan(tracer); + Span publishRpcSpan = + OpenTelemetryUtil.startPublishRpcSpan(tracer, FULL_TOPIC_NAME, messageWrappers, true); + + Exception e = new Exception("test-exception"); + OpenTelemetryUtil.setPublishRpcSpanException(publishRpcSpan, e, true); + messageWrapper.endPublisherSpan(); + + List allSpans = openTelemetryRule.getSpans(); + assertEquals(allSpans.size(), 2); + SpanData rpcSpanData = allSpans.get(0); + SpanData publisherSpanData = allSpans.get(1); + + SpanDataAssert rpcSpanDataAssert = OpenTelemetryAssertions.assertThat(rpcSpanData); + StatusData expectedStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on publish RPC."); + rpcSpanDataAssert + .hasName(PUBLISH_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + + SpanDataAssert publishSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publishSpanDataAssert + .hasName(PUBLISHER_SPAN_NAME) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasEnded(); + } + + private PubsubMessage getPubsubMessage() { + return PubsubMessage.newBuilder() + .setData(ByteString.copyFromUtf8("test-data")) + .setOrderingKey(ORDERING_KEY) + .build(); + } +} diff --git a/pom.xml b/pom.xml index b7c2dd5c7..fa1dea9fd 100644 --- a/pom.xml +++ b/pom.xml @@ -113,6 +113,12 @@ + + org.assertj + assertj-core + 3.26.0 + test + diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index bde2a8430..a64e2d5c9 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -101,6 +101,27 @@ 2.39.0 tests + + io.opentelemetry + opentelemetry-sdk + 1.39.0 + + + io.opentelemetry + opentelemetry-exporter-logging + 1.39.0 + + + + io.opentelemetry.semconv + opentelemetry-semconv + 1.25.0-alpha + + + com.google.cloud.opentelemetry + exporter-trace + 0.15.0 + diff --git a/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java b/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java index 5019a9f4c..efaeba01e 100644 --- a/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java +++ b/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java @@ -44,8 +44,8 @@ public class OpenTelemetryPublisherExample { public static void main(String... args) throws Exception { // TODO(developer): Replace these variables before running the sample. - String projectId = "cloud-pubsub-experiments"; - String topicId = "mike-topic-test"; + String projectId = "your-project-id"; + String topicId = "your-topic-id"; openTelemetryPublisherExample(projectId, topicId); } @@ -67,7 +67,6 @@ public static void openTelemetryPublisherExample(String projectId, String topicI OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() .setTracerProvider(sdkTracerProvider) - .setPropagators(ContextPropagators.create(TextMapPropagator.composite(W3CTraceContextPropagator.getInstance()))) .buildAndRegisterGlobal(); TopicName topicName = TopicName.of(projectId, topicId); From 9246689d0c64c2f30339bb2bf262b5d380c38ded Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 24 Jun 2024 22:23:39 +0000 Subject: [PATCH 04/46] feat: More tests and refactoring for publish-side OpenTelemetry --- .../cloud/pubsub/v1/PubsubMessageWrapper.java | 1 - .../cloud/pubsub/v1/OpenTelemetryTest.java | 58 ++++++++------ .../cloud/pubsub/v1/PublisherImplTest.java | 75 +++++++++++++++++++ 3 files changed, 109 insertions(+), 25 deletions(-) diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java index 19a90d5bd..d004e3512 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java @@ -83,7 +83,6 @@ public Span getPublisherSpan() { */ public void startPublisherSpan(Tracer tracer) { if (enableOpenTelemetryTracing && tracer != null) { - AttributesBuilder attributesBuilder = OpenTelemetryUtil.createPublishSpanAttributesBuilder( topicName, "Publisher.publish", "create"); diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java index de13c0f61..b8b290ddf 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -17,6 +17,7 @@ package com.google.cloud.pubsub.v1; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import com.google.protobuf.ByteString; import com.google.pubsub.v1.PubsubMessage; @@ -62,11 +63,13 @@ public class OpenTelemetryTest { private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.envelope.size"; private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; - private static final OpenTelemetryRule openTelemetryRule = OpenTelemetryRule.create(); + private static final String TRACEPARENT_ATTRIBUTE = "googclient_traceparent"; + + private static final OpenTelemetryRule openTelemetryTesting = OpenTelemetryRule.create(); @Test public void testPublishSpansSuccess() { - openTelemetryRule.clearSpans(); + openTelemetryTesting.clearSpans(); PubsubMessageWrapper messageWrapper = PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) @@ -74,7 +77,7 @@ public void testPublishSpansSuccess() { List messageWrappers = Arrays.asList(messageWrapper); long messageSize = messageWrapper.getPubsubMessage().getSerializedSize(); - Tracer tracer = openTelemetryRule.getOpenTelemetry().getTracer("test"); + Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); // Call all span start/end methods in the expected order messageWrapper.startPublisherSpan(tracer); @@ -88,8 +91,8 @@ public void testPublishSpansSuccess() { messageWrapper.setMessageIdSpanAttribute(MESSAGE_ID); messageWrapper.endPublisherSpan(); - List allSpans = openTelemetryRule.getSpans(); - assertEquals(allSpans.size(), 4); + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(4, allSpans.size()); SpanData flowControlSpanData = allSpans.get(0); SpanData batchingSpanData = allSpans.get(1); SpanData publishRpcSpanData = allSpans.get(2); @@ -118,10 +121,10 @@ public void testPublishSpansSuccess() { .hasEnded(); List publishRpcLinks = publishRpcSpanData.getLinks(); - assertEquals(publishRpcLinks.size(), messageWrappers.size()); - assertEquals(publishRpcLinks.get(0).getSpanContext(), publisherSpanData.getSpanContext()); + assertEquals(messageWrappers.size(), publishRpcLinks.size()); + assertEquals(publisherSpanData.getSpanContext(), publishRpcLinks.get(0).getSpanContext()); - assertEquals(publishRpcSpanData.getAttributes().size(), 6); + assertEquals(6, publishRpcSpanData.getAttributes().size()); AttributesAssert publishRpcSpanAttributesAssert = OpenTelemetryAssertions.assertThat(publishRpcSpanData.getAttributes()); publishRpcSpanAttributesAssert @@ -140,7 +143,7 @@ public void testPublishSpansSuccess() { .hasNoParent() .hasEnded(); - assertEquals(publisherSpanData.getEvents().size(), 2); + assertEquals(2, publisherSpanData.getEvents().size()); EventDataAssert startEventAssert = OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(0)); startEventAssert.hasName(PUBLISH_START_EVENT); @@ -149,10 +152,10 @@ public void testPublishSpansSuccess() { endEventAssert.hasName(PUBLISH_END_EVENT); List publisherLinks = publisherSpanData.getLinks(); - assertEquals(publisherLinks.size(), 1); - assertEquals(publisherLinks.get(0).getSpanContext(), publishRpcSpanData.getSpanContext()); + assertEquals(1, publisherLinks.size()); + assertEquals(publishRpcSpanData.getSpanContext(), publisherLinks.get(0).getSpanContext()); - assertEquals(publisherSpanData.getAttributes().size(), 8); + assertEquals(8, publisherSpanData.getAttributes().size()); AttributesAssert publisherSpanAttributesAssert = OpenTelemetryAssertions.assertThat(publisherSpanData.getAttributes()); publisherSpanAttributesAssert @@ -164,17 +167,24 @@ public void testPublishSpansSuccess() { .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) .containsEntry(MESSAGE_ID_ATTR_KEY, MESSAGE_ID); + + // Check that the message has the attribute containing the trace context. + PubsubMessage message = messageWrapper.getPubsubMessage(); + assertEquals(1, message.getAttributesMap().size()); + assertTrue(message.containsAttributes(TRACEPARENT_ATTRIBUTE)); + assertTrue(message.getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "").contains(publisherSpanData.getTraceId())); + assertTrue(message.getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "").contains(publisherSpanData.getSpanId())); } @Test public void testPublishFlowControlSpanFailure() { - openTelemetryRule.clearSpans(); + openTelemetryTesting.clearSpans(); PubsubMessageWrapper messageWrapper = PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) .build(); - Tracer tracer = openTelemetryRule.getOpenTelemetry().getTracer("test"); + Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); messageWrapper.startPublisherSpan(tracer); messageWrapper.startPublishFlowControlSpan(tracer); @@ -182,8 +192,8 @@ public void testPublishFlowControlSpanFailure() { Exception e = new Exception("test-exception"); messageWrapper.setPublishFlowControlSpanException(e); - List allSpans = openTelemetryRule.getSpans(); - assertEquals(allSpans.size(), 2); + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(2, allSpans.size()); SpanData flowControlSpanData = allSpans.get(0); SpanData publisherSpanData = allSpans.get(1); @@ -208,13 +218,13 @@ public void testPublishFlowControlSpanFailure() { @Test public void testPublishBatchingSpanFailure() { - openTelemetryRule.clearSpans(); + openTelemetryTesting.clearSpans(); PubsubMessageWrapper messageWrapper = PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) .build(); - Tracer tracer = openTelemetryRule.getOpenTelemetry().getTracer("test"); + Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); messageWrapper.startPublisherSpan(tracer); messageWrapper.startPublishBatchingSpan(tracer); @@ -222,8 +232,8 @@ public void testPublishBatchingSpanFailure() { Exception e = new Exception("test-exception"); messageWrapper.setPublishBatchingSpanException(e); - List allSpans = openTelemetryRule.getSpans(); - assertEquals(allSpans.size(), 2); + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(2, allSpans.size()); SpanData batchingSpanData = allSpans.get(0); SpanData publisherSpanData = allSpans.get(1); @@ -247,14 +257,14 @@ public void testPublishBatchingSpanFailure() { @Test public void testPublishRpcSpanFailure() { - openTelemetryRule.clearSpans(); + openTelemetryTesting.clearSpans(); PubsubMessageWrapper messageWrapper = PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) .build(); List messageWrappers = Arrays.asList(messageWrapper); - Tracer tracer = openTelemetryRule.getOpenTelemetry().getTracer("test"); + Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); messageWrapper.startPublisherSpan(tracer); Span publishRpcSpan = @@ -264,8 +274,8 @@ public void testPublishRpcSpanFailure() { OpenTelemetryUtil.setPublishRpcSpanException(publishRpcSpan, e, true); messageWrapper.endPublisherSpan(); - List allSpans = openTelemetryRule.getSpans(); - assertEquals(allSpans.size(), 2); + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(2, allSpans.size()); SpanData rpcSpanData = allSpans.get(0); SpanData publisherSpanData = allSpans.get(1); diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java index 9785b7716..4b44c7d5d 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java @@ -48,6 +48,13 @@ import io.grpc.StatusException; import io.grpc.inprocess.InProcessChannelBuilder; import io.grpc.inprocess.InProcessServerBuilder; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.testing.junit4.OpenTelemetryRule; +import io.opentelemetry.sdk.trace.data.SpanData; + import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -74,6 +81,11 @@ public class PublisherImplTest { private static final TransportChannelProvider TEST_CHANNEL_PROVIDER = LocalChannelProvider.create("test-server"); + private static final String PUBLISHER_SPAN_NAME = TEST_TOPIC.getTopic() + " create"; + private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; + private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; + private static final String PUBLISH_RPC_SPAN_NAME = TEST_TOPIC.getTopic() + " publish"; + private FakeScheduledExecutorService fakeExecutor; private FakePublisherServiceImpl testPublisherServiceImpl; @@ -1304,6 +1316,69 @@ public void run() { publish4Completed.await(); } + @Test + public void testPublishOpenTelemetryTracing() throws Exception { + OpenTelemetryRule openTelemetryTesting = OpenTelemetryRule.create(); + OpenTelemetry openTelemetry = openTelemetryTesting.getOpenTelemetry(); + final Publisher publisher = + getTestPublisherBuilder() + .setBatchingSettings( + Publisher.Builder.DEFAULT_BATCHING_SETTINGS + .toBuilder() + .setElementCountThreshold(1L) + .setDelayThreshold(Duration.ofSeconds(5)) + .setFlowControlSettings( + FlowControlSettings.newBuilder() + .setLimitExceededBehavior(FlowController.LimitExceededBehavior.Block) + .setMaxOutstandingElementCount(2L) + .setMaxOutstandingRequestBytes(100L) + .build()) + .build()) + .setOpenTelemetry(openTelemetry) + .setEnableOpenTelemetryTracing(true) + .build(); + + testPublisherServiceImpl.addPublishResponse( + PublishResponse.newBuilder().addMessageIds("1")); + ApiFuture publishFuture = sendTestMessage(publisher, "A"); + assertEquals("1", publishFuture.get()); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(4, allSpans.size()); + SpanData flowControlSpanData = allSpans.get(0); + SpanData batchingSpanData = allSpans.get(1); + SpanData publishRpcSpanData = allSpans.get(2); + SpanData publisherSpanData = allSpans.get(3); + + SpanDataAssert flowControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(flowControlSpanData); + flowControlSpanDataAssert + .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) + .hasParent(publisherSpanData) + .hasEnded(); + + SpanDataAssert batchingSpanDataAssert = OpenTelemetryAssertions.assertThat(batchingSpanData); + batchingSpanDataAssert + .hasName(PUBLISH_BATCHING_SPAN_NAME) + .hasParent(publisherSpanData) + .hasEnded(); + + SpanDataAssert publishRpcSpanDataAssert = + OpenTelemetryAssertions.assertThat(publishRpcSpanData); + publishRpcSpanDataAssert + .hasName(PUBLISH_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + SpanDataAssert publishSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publishSpanDataAssert + .hasName(PUBLISHER_SPAN_NAME) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasEnded(); + } + private Builder getTestPublisherBuilder() { return Publisher.newBuilder(TEST_TOPIC) .setExecutorProvider(FixedExecutorProvider.create(fakeExecutor)) From 34594cb90c3ba15aa8168cbf2f0a2dcd08a0fd65 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Mon, 24 Jun 2024 22:27:52 +0000 Subject: [PATCH 05/46] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- README.md | 9 +- .../cloud/pubsub/v1/OpenTelemetryTest.java | 10 +- .../cloud/pubsub/v1/PublisherImplTest.java | 4 +- .../pubsub/OpenTelemetryPublisherExample.java | 58 +++++------ .../main/java/pubsub/PublisherExample.java | 98 +++++++++---------- 5 files changed, 93 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index a2ac8ae93..94c6054d4 100644 --- a/README.md +++ b/README.md @@ -52,20 +52,20 @@ If you are using Maven without the BOM, add this to your dependencies: If you are using Gradle 5.x or later, add this to your dependencies: ```Groovy -implementation platform('com.google.cloud:libraries-bom:26.40.0') +implementation platform('com.google.cloud:libraries-bom:26.42.0') implementation 'com.google.cloud:google-cloud-pubsub' ``` If you are using Gradle without BOM, add this to your dependencies: ```Groovy -implementation 'com.google.cloud:google-cloud-pubsub:1.130.0' +implementation 'com.google.cloud:google-cloud-pubsub:1.130.1' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-pubsub" % "1.130.0" +libraryDependencies += "com.google.cloud" % "google-cloud-pubsub" % "1.130.1" ``` @@ -275,6 +275,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-pubsub/tree/m | List Subscriptions In Project Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/ListSubscriptionsInProjectExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/ListSubscriptionsInProjectExample.java) | | List Subscriptions In Topic Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/ListSubscriptionsInTopicExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/ListSubscriptionsInTopicExample.java) | | List Topics Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/ListTopicsExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/ListTopicsExample.java) | +| Open Telemetry Publisher Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java) | | Publish Avro Records Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/PublishAvroRecordsExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/PublishAvroRecordsExample.java) | | Publish Protobuf Messages Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/PublishProtobufMessagesExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/PublishProtobufMessagesExample.java) | | Publish With Batch Settings Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/PublishWithBatchSettingsExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/PublishWithBatchSettingsExample.java) | @@ -411,7 +412,7 @@ Java is a registered trademark of Oracle and/or its affiliates. [kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-pubsub/java11.html [stability-image]: https://img.shields.io/badge/stability-stable-green [maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-pubsub.svg -[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-pubsub/1.130.0 +[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-pubsub/1.130.1 [authentication]: https://github.com/googleapis/google-cloud-java#authentication [auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes [predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java index b8b290ddf..df5ae5099 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -172,8 +172,14 @@ public void testPublishSpansSuccess() { PubsubMessage message = messageWrapper.getPubsubMessage(); assertEquals(1, message.getAttributesMap().size()); assertTrue(message.containsAttributes(TRACEPARENT_ATTRIBUTE)); - assertTrue(message.getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "").contains(publisherSpanData.getTraceId())); - assertTrue(message.getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "").contains(publisherSpanData.getSpanId())); + assertTrue( + message + .getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "") + .contains(publisherSpanData.getTraceId())); + assertTrue( + message + .getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "") + .contains(publisherSpanData.getSpanId())); } @Test diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java index 4b44c7d5d..4dfc5833c 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java @@ -54,7 +54,6 @@ import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; import io.opentelemetry.sdk.testing.junit4.OpenTelemetryRule; import io.opentelemetry.sdk.trace.data.SpanData; - import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -1338,8 +1337,7 @@ public void testPublishOpenTelemetryTracing() throws Exception { .setEnableOpenTelemetryTracing(true) .build(); - testPublisherServiceImpl.addPublishResponse( - PublishResponse.newBuilder().addMessageIds("1")); + testPublisherServiceImpl.addPublishResponse(PublishResponse.newBuilder().addMessageIds("1")); ApiFuture publishFuture = sendTestMessage(publisher, "A"); assertEquals("1", publishFuture.get()); diff --git a/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java b/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java index efaeba01e..a02d39a9e 100644 --- a/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java +++ b/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java @@ -17,29 +17,22 @@ package pubsub; import com.google.api.core.ApiFuture; +import com.google.cloud.opentelemetry.trace.TraceConfiguration; +import com.google.cloud.opentelemetry.trace.TraceExporter; import com.google.cloud.pubsub.v1.Publisher; import com.google.protobuf.ByteString; import com.google.pubsub.v1.PubsubMessage; import com.google.pubsub.v1.TopicName; -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; - -import com.google.cloud.opentelemetry.trace.TraceConfiguration; -import com.google.cloud.opentelemetry.trace.TraceExporter; -import io.opentelemetry.exporter.logging.LoggingSpanExporter; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.SdkTracerProvider; -import io.opentelemetry.sdk.trace.samplers.Sampler; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; -import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; -import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.trace.samplers.Sampler; import io.opentelemetry.semconv.ResourceAttributes; -import io.opentelemetry.context.propagation.TextMapPropagator; -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; -import io.opentelemetry.context.propagation.ContextPropagators; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; public class OpenTelemetryPublisherExample { public static void main(String... args) throws Exception { @@ -52,29 +45,38 @@ public static void main(String... args) throws Exception { public static void openTelemetryPublisherExample(String projectId, String topicId) throws IOException, ExecutionException, InterruptedException { - Resource resource = Resource.getDefault().toBuilder() - .put(ResourceAttributes.SERVICE_NAME, "publisher-example").build(); - - TraceExporter traceExporter = TraceExporter.createWithConfiguration(TraceConfiguration.builder().setProjectId(projectId).build()); + Resource resource = + Resource.getDefault() + .toBuilder() + .put(ResourceAttributes.SERVICE_NAME, "publisher-example") + .build(); + + TraceExporter traceExporter = + TraceExporter.createWithConfiguration( + TraceConfiguration.builder().setProjectId(projectId).build()); - SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() - .setResource(resource) - // .addSpanProcessor(BatchSpanProcessor.builder(traceExporter).build()) - .addSpanProcessor(SimpleSpanProcessor.create(traceExporter)) - // .addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create())) - .setSampler(Sampler.alwaysOn()) - .build(); + SdkTracerProvider sdkTracerProvider = + SdkTracerProvider.builder() + .setResource(resource) + // .addSpanProcessor(BatchSpanProcessor.builder(traceExporter).build()) + .addSpanProcessor(SimpleSpanProcessor.create(traceExporter)) + // .addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create())) + .setSampler(Sampler.alwaysOn()) + .build(); - OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() - .setTracerProvider(sdkTracerProvider) - .buildAndRegisterGlobal(); + OpenTelemetry openTelemetry = + OpenTelemetrySdk.builder().setTracerProvider(sdkTracerProvider).buildAndRegisterGlobal(); TopicName topicName = TopicName.of(projectId, topicId); Publisher publisher = null; try { // Create a publisher instance with default settings bound to the topic - publisher = Publisher.newBuilder(topicName).setOpenTelemetry(openTelemetry).setEnableOpenTelemetryTracing(true).build(); + publisher = + Publisher.newBuilder(topicName) + .setOpenTelemetry(openTelemetry) + .setEnableOpenTelemetryTracing(true) + .build(); for (int i = 0; i < 1; i++) { String message = "Hello World!"; diff --git a/samples/snippets/src/main/java/pubsub/PublisherExample.java b/samples/snippets/src/main/java/pubsub/PublisherExample.java index 18cc27396..2393529c3 100644 --- a/samples/snippets/src/main/java/pubsub/PublisherExample.java +++ b/samples/snippets/src/main/java/pubsub/PublisherExample.java @@ -14,54 +14,54 @@ * limitations under the License. */ - package pubsub; +package pubsub; - // [START pubsub_quickstart_publisher] - // [START pubsub_publish] - - import com.google.api.core.ApiFuture; - import com.google.cloud.pubsub.v1.Publisher; - import com.google.protobuf.ByteString; - import com.google.pubsub.v1.PubsubMessage; - import com.google.pubsub.v1.TopicName; - import java.io.IOException; - import java.util.concurrent.ExecutionException; - import java.util.concurrent.TimeUnit; - - public class PublisherExample { - public static void main(String... args) throws Exception { - // TODO(developer): Replace these variables before running the sample. - String projectId = "your-project-id"; - String topicId = "your-topic-id"; - - publisherExample(projectId, topicId); - } - - public static void publisherExample(String projectId, String topicId) - throws IOException, ExecutionException, InterruptedException { - TopicName topicName = TopicName.of(projectId, topicId); - - Publisher publisher = null; - try { - // Create a publisher instance with default settings bound to the topic - publisher = Publisher.newBuilder(topicName).build(); - - String message = "Hello World!"; - ByteString data = ByteString.copyFromUtf8(message); - PubsubMessage pubsubMessage = PubsubMessage.newBuilder().setData(data).build(); - - // Once published, returns a server-assigned message id (unique within the topic) - ApiFuture messageIdFuture = publisher.publish(pubsubMessage); - String messageId = messageIdFuture.get(); - System.out.println("Published message ID: " + messageId); - } finally { - if (publisher != null) { - // When finished with the publisher, shutdown to free up resources. - publisher.shutdown(); - publisher.awaitTermination(1, TimeUnit.MINUTES); - } - } - } - } +// [START pubsub_quickstart_publisher] +// [START pubsub_publish] + +import com.google.api.core.ApiFuture; +import com.google.cloud.pubsub.v1.Publisher; +import com.google.protobuf.ByteString; +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.TopicName; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +public class PublisherExample { + public static void main(String... args) throws Exception { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String topicId = "your-topic-id"; + + publisherExample(projectId, topicId); + } + + public static void publisherExample(String projectId, String topicId) + throws IOException, ExecutionException, InterruptedException { + TopicName topicName = TopicName.of(projectId, topicId); + + Publisher publisher = null; + try { + // Create a publisher instance with default settings bound to the topic + publisher = Publisher.newBuilder(topicName).build(); + + String message = "Hello World!"; + ByteString data = ByteString.copyFromUtf8(message); + PubsubMessage pubsubMessage = PubsubMessage.newBuilder().setData(data).build(); + + // Once published, returns a server-assigned message id (unique within the topic) + ApiFuture messageIdFuture = publisher.publish(pubsubMessage); + String messageId = messageIdFuture.get(); + System.out.println("Published message ID: " + messageId); + } finally { + if (publisher != null) { + // When finished with the publisher, shutdown to free up resources. + publisher.shutdown(); + publisher.awaitTermination(1, TimeUnit.MINUTES); + } + } + } +} // [END pubsub_quickstart_publisher] - // [END pubsub_publish] \ No newline at end of file + // [END pubsub_publish] From 901e135784253ba67141362b48732914c17f10fd Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 24 Jun 2024 22:35:48 +0000 Subject: [PATCH 06/46] feat: Formatting files --- .../pubsub/OpenTelemetryPublisherExample.java | 100 ------------------ .../main/java/pubsub/PublisherExample.java | 4 +- 2 files changed, 2 insertions(+), 102 deletions(-) delete mode 100644 samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java diff --git a/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java b/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java deleted file mode 100644 index a02d39a9e..000000000 --- a/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2016 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 pubsub; - -import com.google.api.core.ApiFuture; -import com.google.cloud.opentelemetry.trace.TraceConfiguration; -import com.google.cloud.opentelemetry.trace.TraceExporter; -import com.google.cloud.pubsub.v1.Publisher; -import com.google.protobuf.ByteString; -import com.google.pubsub.v1.PubsubMessage; -import com.google.pubsub.v1.TopicName; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; -import io.opentelemetry.sdk.trace.samplers.Sampler; -import io.opentelemetry.semconv.ResourceAttributes; -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; - -public class OpenTelemetryPublisherExample { - public static void main(String... args) throws Exception { - // TODO(developer): Replace these variables before running the sample. - String projectId = "your-project-id"; - String topicId = "your-topic-id"; - - openTelemetryPublisherExample(projectId, topicId); - } - - public static void openTelemetryPublisherExample(String projectId, String topicId) - throws IOException, ExecutionException, InterruptedException { - Resource resource = - Resource.getDefault() - .toBuilder() - .put(ResourceAttributes.SERVICE_NAME, "publisher-example") - .build(); - - TraceExporter traceExporter = - TraceExporter.createWithConfiguration( - TraceConfiguration.builder().setProjectId(projectId).build()); - - SdkTracerProvider sdkTracerProvider = - SdkTracerProvider.builder() - .setResource(resource) - // .addSpanProcessor(BatchSpanProcessor.builder(traceExporter).build()) - .addSpanProcessor(SimpleSpanProcessor.create(traceExporter)) - // .addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create())) - .setSampler(Sampler.alwaysOn()) - .build(); - - OpenTelemetry openTelemetry = - OpenTelemetrySdk.builder().setTracerProvider(sdkTracerProvider).buildAndRegisterGlobal(); - - TopicName topicName = TopicName.of(projectId, topicId); - - Publisher publisher = null; - try { - // Create a publisher instance with default settings bound to the topic - publisher = - Publisher.newBuilder(topicName) - .setOpenTelemetry(openTelemetry) - .setEnableOpenTelemetryTracing(true) - .build(); - - for (int i = 0; i < 1; i++) { - String message = "Hello World!"; - ByteString data = ByteString.copyFromUtf8(message); - PubsubMessage pubsubMessage = PubsubMessage.newBuilder().setData(data).build(); - - // Once published, returns a server-assigned message id (unique within the topic) - ApiFuture messageIdFuture = publisher.publish(pubsubMessage); - System.out.println("Published message ID: " + messageIdFuture.get()); - } - } finally { - if (publisher != null) { - // When finished with the publisher, shutdown to free up resources. - publisher.shutdown(); - publisher.awaitTermination(1, TimeUnit.MINUTES); - } - } - } -} -// [END pubsub_quickstart_publisher] -// [END pubsub_publish] diff --git a/samples/snippets/src/main/java/pubsub/PublisherExample.java b/samples/snippets/src/main/java/pubsub/PublisherExample.java index 2393529c3..e4dc39a89 100644 --- a/samples/snippets/src/main/java/pubsub/PublisherExample.java +++ b/samples/snippets/src/main/java/pubsub/PublisherExample.java @@ -63,5 +63,5 @@ public static void publisherExample(String projectId, String topicId) } } } - // [END pubsub_quickstart_publisher] - // [END pubsub_publish] +// [END pubsub_quickstart_publisher] +// [END pubsub_publish] From 6339e52d4175d62f1bf1514547f7ce3dea72b280 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Mon, 24 Jun 2024 22:40:46 +0000 Subject: [PATCH 07/46] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 94c6054d4..a1adf4407 100644 --- a/README.md +++ b/README.md @@ -275,7 +275,6 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-pubsub/tree/m | List Subscriptions In Project Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/ListSubscriptionsInProjectExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/ListSubscriptionsInProjectExample.java) | | List Subscriptions In Topic Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/ListSubscriptionsInTopicExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/ListSubscriptionsInTopicExample.java) | | List Topics Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/ListTopicsExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/ListTopicsExample.java) | -| Open Telemetry Publisher Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java) | | Publish Avro Records Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/PublishAvroRecordsExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/PublishAvroRecordsExample.java) | | Publish Protobuf Messages Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/PublishProtobufMessagesExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/PublishProtobufMessagesExample.java) | | Publish With Batch Settings Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/PublishWithBatchSettingsExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/PublishWithBatchSettingsExample.java) | From c954ae1c5036b656f7d953b78cb613b343030197 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 24 Jun 2024 22:57:32 +0000 Subject: [PATCH 08/46] feat: Publisher test changes --- .../java/com/google/cloud/pubsub/v1/PublisherImplTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java index 4dfc5833c..70c6929b2 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java @@ -1340,9 +1340,10 @@ public void testPublishOpenTelemetryTracing() throws Exception { testPublisherServiceImpl.addPublishResponse(PublishResponse.newBuilder().addMessageIds("1")); ApiFuture publishFuture = sendTestMessage(publisher, "A"); assertEquals("1", publishFuture.get()); + fakeExecutor.advanceTime(Duration.ofSeconds(5)); List allSpans = openTelemetryTesting.getSpans(); - assertEquals(4, allSpans.size()); + // assertEquals(4, allSpans.size()); SpanData flowControlSpanData = allSpans.get(0); SpanData batchingSpanData = allSpans.get(1); SpanData publishRpcSpanData = allSpans.get(2); From fc163ab9ee9d3e945e5844e335ade21be44e8b6d Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 24 Jun 2024 23:10:42 +0000 Subject: [PATCH 09/46] test: Fix OpenTelemetry test --- .../test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java index 70c6929b2..e062f8cd9 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java @@ -1343,7 +1343,7 @@ public void testPublishOpenTelemetryTracing() throws Exception { fakeExecutor.advanceTime(Duration.ofSeconds(5)); List allSpans = openTelemetryTesting.getSpans(); - // assertEquals(4, allSpans.size()); + assertEquals(4, allSpans.size()); SpanData flowControlSpanData = allSpans.get(0); SpanData batchingSpanData = allSpans.get(1); SpanData publishRpcSpanData = allSpans.get(2); From 6ab4cfe547d848169f486e7e80c726938e6abbd8 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 24 Jun 2024 23:31:36 +0000 Subject: [PATCH 10/46] Feat: Use OpenTelemetry semconv --- google-cloud-pubsub/pom.xml | 5 ++++ .../cloud/pubsub/v1/OpenTelemetryUtil.java | 21 ++++++-------- .../cloud/pubsub/v1/PubsubMessageWrapper.java | 4 +-- .../cloud/pubsub/v1/OpenTelemetryTest.java | 29 ++++++++----------- samples/snippets/pom.xml | 21 -------------- 5 files changed, 28 insertions(+), 52 deletions(-) diff --git a/google-cloud-pubsub/pom.xml b/google-cloud-pubsub/pom.xml index 9bb21deb5..b80b5256c 100644 --- a/google-cloud-pubsub/pom.xml +++ b/google-cloud-pubsub/pom.xml @@ -110,6 +110,11 @@ opentelemetry-context 1.38.0 + + io.opentelemetry + opentelemetry-semconv + 1.25.0-alpha + diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java index 8a077d05a..37cc05f7e 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java @@ -23,15 +23,11 @@ import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.List; public class OpenTelemetryUtil { - private static final String SYSTEM_ATTR_KEY = "messaging.system"; - private static final String SYSTEM_ATTR_VALUE = "gcp_pubsub"; - private static final String DESTINATION_ATTR_KEY = "messaging.destination.name"; - private static final String CODE_FUNCTION_ATTR_KEY = "code.function"; - private static final String MESSAGE_BATCH_SIZE_ATTR_KEY = "messaging.batch.message_count"; - private static final String OPERATION_ATTR_KEY = "messaging.operation"; + private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; private static final String PROJECT_ATTR_KEY = "gcp_pubsub.project_id"; private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; @@ -41,11 +37,11 @@ public static final AttributesBuilder createPublishSpanAttributesBuilder( TopicName topicName, String codeFunction, String operation) { AttributesBuilder attributesBuilder = Attributes.builder() - .put(SYSTEM_ATTR_KEY, SYSTEM_ATTR_VALUE) - .put(DESTINATION_ATTR_KEY, topicName.getTopic()) + .put(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .put(SemanticAttributes.MESSAGING_DESTINATION_NAME, topicName.getTopic()) .put(PROJECT_ATTR_KEY, topicName.getProject()) - .put(CODE_FUNCTION_ATTR_KEY, codeFunction) - .put(OPERATION_ATTR_KEY, operation); + .put(SemanticAttributes.CODE_FUNCTION, codeFunction) + .put(SemanticAttributes.MESSAGING_OPERATION, operation); return attributesBuilder; } @@ -62,7 +58,7 @@ public static final Span startPublishRpcSpan( if (enableOpenTelemetryTracing && tracer != null) { Attributes attributes = createPublishSpanAttributesBuilder(topicName, "Publisher.publishCall", "publish") - .put(MESSAGE_BATCH_SIZE_ATTR_KEY, messages.size()) + .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()) .build(); Span publishRpcSpan = tracer @@ -72,7 +68,8 @@ public static final Span startPublishRpcSpan( .startSpan(); for (PubsubMessageWrapper message : messages) { - Attributes linkAttributes = Attributes.builder().put(OPERATION_ATTR_KEY, "publish").build(); + Attributes linkAttributes = + Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, "publish").build(); publishRpcSpan.addLink(message.getPublisherSpan().getSpanContext(), linkAttributes); message.getPublisherSpan().addLink(publishRpcSpan.getSpanContext(), linkAttributes); message.addPublishStartEvent(); diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java index d004e3512..cc00248df 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java @@ -27,6 +27,7 @@ import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.TextMapSetter; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; /** * A wrapper class for a {@link PubsubMessage} object that handles creation and tracking of @@ -47,7 +48,6 @@ public class PubsubMessageWrapper { private static final String GOOGCLIENT_PREFIX = "googclient_"; - private static final String MESSAGE_ID_ATTR_KEY = "messaging.message.id"; private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.envelope.size"; private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; @@ -140,7 +140,7 @@ public void addPublishEndEvent() { */ public void setMessageIdSpanAttribute(String messageId) { if (enableOpenTelemetryTracing && publisherSpan != null) { - publisherSpan.setAttribute(MESSAGE_ID_ATTR_KEY, messageId); + publisherSpan.setAttribute(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId); } } diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java index df5ae5099..ce9da8878 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -34,6 +34,7 @@ import io.opentelemetry.sdk.trace.data.LinkData; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.data.StatusData; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.Arrays; import java.util.List; import org.junit.Test; @@ -52,14 +53,8 @@ public class OpenTelemetryTest { private static final String PUBLISH_START_EVENT = "publish start"; private static final String PUBLISH_END_EVENT = "publish end"; - private static final String SYSTEM_ATTR_KEY = "messaging.system"; - private static final String SYSTEM_ATTR_VALUE = "gcp_pubsub"; - private static final String DESTINATION_ATTR_KEY = "messaging.destination.name"; - private static final String CODE_FUNCTION_ATTR_KEY = "code.function"; - private static final String MESSAGE_BATCH_SIZE_ATTR_KEY = "messaging.batch.message_count"; - private static final String OPERATION_ATTR_KEY = "messaging.operation"; + private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; private static final String PROJECT_ATTR_KEY = "gcp_pubsub.project_id"; - private static final String MESSAGE_ID_ATTR_KEY = "messaging.message.id"; private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.envelope.size"; private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; @@ -128,12 +123,12 @@ public void testPublishSpansSuccess() { AttributesAssert publishRpcSpanAttributesAssert = OpenTelemetryAssertions.assertThat(publishRpcSpanData.getAttributes()); publishRpcSpanAttributesAssert - .containsEntry(SYSTEM_ATTR_KEY, SYSTEM_ATTR_VALUE) - .containsEntry(DESTINATION_ATTR_KEY, FULL_TOPIC_NAME.getTopic()) + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry(CODE_FUNCTION_ATTR_KEY, "Publisher.publishCall") - .containsEntry(OPERATION_ATTR_KEY, "publish") - .containsEntry(MESSAGE_BATCH_SIZE_ATTR_KEY, messageWrappers.size()); + .containsEntry(SemanticAttributes.CODE_FUNCTION, "Publisher.publishCall") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "create") + .containsEntry(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messageWrappers.size()); // Check span data, events, links, and attributes for the publisher create span SpanDataAssert publishSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); @@ -159,14 +154,14 @@ public void testPublishSpansSuccess() { AttributesAssert publisherSpanAttributesAssert = OpenTelemetryAssertions.assertThat(publisherSpanData.getAttributes()); publisherSpanAttributesAssert - .containsEntry(SYSTEM_ATTR_KEY, SYSTEM_ATTR_VALUE) - .containsEntry(DESTINATION_ATTR_KEY, FULL_TOPIC_NAME.getTopic()) + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) - .containsEntry(CODE_FUNCTION_ATTR_KEY, "Publisher.publish") - .containsEntry(OPERATION_ATTR_KEY, "create") + .containsEntry(SemanticAttributes.CODE_FUNCTION, "Publisher.publish") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "create") .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) - .containsEntry(MESSAGE_ID_ATTR_KEY, MESSAGE_ID); + .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); // Check that the message has the attribute containing the trace context. PubsubMessage message = messageWrapper.getPubsubMessage(); diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index a64e2d5c9..bde2a8430 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -101,27 +101,6 @@ 2.39.0 tests - - io.opentelemetry - opentelemetry-sdk - 1.39.0 - - - io.opentelemetry - opentelemetry-exporter-logging - 1.39.0 - - - - io.opentelemetry.semconv - opentelemetry-semconv - 1.25.0-alpha - - - com.google.cloud.opentelemetry - exporter-trace - 0.15.0 - From bc7530abf6874ecc65c1bd807b2286030e7091d9 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 24 Jun 2024 23:41:09 +0000 Subject: [PATCH 11/46] test: Fix some dependency issues --- google-cloud-pubsub/pom.xml | 4 ++-- .../java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/google-cloud-pubsub/pom.xml b/google-cloud-pubsub/pom.xml index b80b5256c..1115d8bfa 100644 --- a/google-cloud-pubsub/pom.xml +++ b/google-cloud-pubsub/pom.xml @@ -113,7 +113,7 @@ io.opentelemetry opentelemetry-semconv - 1.25.0-alpha + 1.26.0-alpha @@ -159,7 +159,7 @@ io.opentelemetry - opentelemetry-sdk + opentelemetry-sdk-trace test diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java index ce9da8878..a0e06303c 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -127,7 +127,7 @@ public void testPublishSpansSuccess() { .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) .containsEntry(SemanticAttributes.CODE_FUNCTION, "Publisher.publishCall") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "create") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "publish") .containsEntry(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messageWrappers.size()); // Check span data, events, links, and attributes for the publisher create span From 77d56dfbe872d1a678055635eac7ca45d3b1938f Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 24 Jun 2024 23:53:20 +0000 Subject: [PATCH 12/46] feat: Test fix --- .../test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java index e062f8cd9..fedd17436 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java @@ -1339,6 +1339,7 @@ public void testPublishOpenTelemetryTracing() throws Exception { testPublisherServiceImpl.addPublishResponse(PublishResponse.newBuilder().addMessageIds("1")); ApiFuture publishFuture = sendTestMessage(publisher, "A"); + fakeExecutor.advanceTime(Duration.ofSeconds(5)); assertEquals("1", publishFuture.get()); fakeExecutor.advanceTime(Duration.ofSeconds(5)); From e275efad7d8afba2b32320e683a0aa5811b80734 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Tue, 25 Jun 2024 18:07:51 +0000 Subject: [PATCH 13/46] feat: Add comment for setter in builder --- .../src/main/java/com/google/cloud/pubsub/v1/Publisher.java | 1 + 1 file changed, 1 insertion(+) diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java index 3790806a4..1701d53f2 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java @@ -927,6 +927,7 @@ public Builder setCompressionBytesThreshold(long compressionBytesThreshold) { return this; } + /** Gives the ability to enable Open Telemetry Tracing */ public Builder setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing) { this.enableOpenTelemetryTracing = enableOpenTelemetryTracing; return this; From 456ac83c7626a7b1b3f5ca52dfaabf10423d6b3d Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Tue, 2 Jul 2024 02:53:33 -0400 Subject: [PATCH 14/46] Opentelemetry subscribe (#2100) * feat: Add OpenTelemetry tracing to the SubscriberClient * feat: Add link to publisher create span in the subscribe process span * feat: Add Ack/Nack/ModAck RPC spans to the subscribe --- google-cloud-pubsub/pom.xml | 4 +- .../cloud/pubsub/v1/AckRequestData.java | 12 + .../cloud/pubsub/v1/MessageDispatcher.java | 92 ++++- .../cloud/pubsub/v1/ModackRequestData.java | 10 + .../cloud/pubsub/v1/OpenTelemetryUtil.java | 108 +++++- .../com/google/cloud/pubsub/v1/Publisher.java | 6 +- .../cloud/pubsub/v1/PubsubMessageWrapper.java | 341 ++++++++++++++++-- .../v1/StreamingSubscriberConnection.java | 103 +++++- .../google/cloud/pubsub/v1/Subscriber.java | 32 ++ .../cloud/pubsub/v1/OpenTelemetryTest.java | 14 +- 10 files changed, 646 insertions(+), 76 deletions(-) diff --git a/google-cloud-pubsub/pom.xml b/google-cloud-pubsub/pom.xml index 1115d8bfa..3604f86b4 100644 --- a/google-cloud-pubsub/pom.xml +++ b/google-cloud-pubsub/pom.xml @@ -103,12 +103,12 @@ io.opentelemetry opentelemetry-api - 1.38.0 + 1.39.0 io.opentelemetry opentelemetry-context - 1.38.0 + 1.39.0 io.opentelemetry diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/AckRequestData.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/AckRequestData.java index 3b67ce219..2bbdb1d75 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/AckRequestData.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/AckRequestData.java @@ -22,10 +22,12 @@ public class AckRequestData { private final String ackId; private final Optional> messageFuture; + private PubsubMessageWrapper messageWrapper; protected AckRequestData(Builder builder) { this.ackId = builder.ackId; this.messageFuture = builder.messageFuture; + this.messageWrapper = builder.messageWrapper; } public String getAckId() { @@ -36,6 +38,10 @@ public SettableApiFuture getMessageFutureIfExists() { return this.messageFuture.orElse(null); } + public PubsubMessageWrapper getMessageWrapper() { + return messageWrapper; + } + public AckRequestData setResponse(AckResponse ackResponse, boolean setResponseOnSuccess) { if (this.messageFuture.isPresent() && !this.messageFuture.get().isDone()) { switch (ackResponse) { @@ -68,6 +74,7 @@ public static Builder newBuilder(String ackId) { protected static final class Builder { private final String ackId; private Optional> messageFuture = Optional.empty(); + private PubsubMessageWrapper messageWrapper; protected Builder(String ackId) { this.ackId = ackId; @@ -78,6 +85,11 @@ public Builder setMessageFuture(SettableApiFuture messageFuture) { return this; } + public Builder setMessageWrapper(PubsubMessageWrapper messageWrapper) { + this.messageWrapper = messageWrapper; + return this; + } + public AckRequestData build() { return new AckRequestData(this); } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java index 1810badd2..a766efb18 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java @@ -28,6 +28,8 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.pubsub.v1.PubsubMessage; import com.google.pubsub.v1.ReceivedMessage; +import com.google.pubsub.v1.SubscriptionName; +import io.opentelemetry.api.trace.Tracer; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -104,6 +106,10 @@ class MessageDispatcher { // To keep track of number of seconds the receiver takes to process messages. private final Distribution ackLatencyDistribution; + private final String subscriptionName; + private final boolean enableOpenTelemetryTracing; + private final Tracer tracer; + /** Internal representation of a reply to a Pubsub message, to be sent back to the service. */ public enum AckReply { ACK, @@ -157,6 +163,7 @@ public void onFailure(Throwable t) { t); this.ackRequestData.setResponse(AckResponse.OTHER, false); pendingNacks.add(this.ackRequestData); + this.ackRequestData.getMessageWrapper().endSubscribeProcessSpan("nack"); forget(); } @@ -169,9 +176,11 @@ public void onSuccess(AckReply reply) { ackLatencyDistribution.record( Ints.saturatedCast( (long) Math.ceil((clock.millisTime() - receivedTimeMillis) / 1000D))); + this.ackRequestData.getMessageWrapper().endSubscribeProcessSpan("ack"); break; case NACK: pendingNacks.add(this.ackRequestData); + this.ackRequestData.getMessageWrapper().endSubscribeProcessSpan("nack"); break; default: throw new IllegalArgumentException(String.format("AckReply: %s not supported", reply)); @@ -217,6 +226,10 @@ private MessageDispatcher(Builder builder) { jobLock = new ReentrantLock(); messagesWaiter = new Waiter(); sequentialExecutor = new SequentialExecutorService.AutoExecutor(builder.executor); + + subscriptionName = builder.subscriptionName; + enableOpenTelemetryTracing = builder.enableOpenTelemetryTracing; + tracer = builder.tracer; } private boolean shouldSetMessageFuture() { @@ -351,13 +364,15 @@ void setMessageOrderingEnabled(boolean messageOrderingEnabled) { } private static class OutstandingMessage { - private final ReceivedMessage receivedMessage; private final AckHandler ackHandler; - private OutstandingMessage(ReceivedMessage receivedMessage, AckHandler ackHandler) { - this.receivedMessage = receivedMessage; + private OutstandingMessage(AckHandler ackHandler) { this.ackHandler = ackHandler; } + + public PubsubMessageWrapper messageWrapper() { + return this.ackHandler.ackRequestData.getMessageWrapper(); + } } private static class ReceiptCompleteData { @@ -390,10 +405,21 @@ void processReceivedMessages(List messages) { if (shouldSetMessageFuture()) { builder.setMessageFuture(SettableApiFuture.create()); } + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder( + message.getMessage(), + SubscriptionName.parse(subscriptionName), + message.getAckId(), + message.getDeliveryAttempt(), + enableOpenTelemetryTracing) + .build(); + builder.setMessageWrapper(messageWrapper); + messageWrapper.startSubscriberSpan(tracer, this.exactlyOnceDeliveryEnabled.get()); + AckRequestData ackRequestData = builder.build(); AckHandler ackHandler = new AckHandler(ackRequestData, message.getMessage().getSerializedSize(), totalExpiration); - OutstandingMessage outstandingMessage = new OutstandingMessage(message, ackHandler); + OutstandingMessage outstandingMessage = new OutstandingMessage(ackHandler); if (this.exactlyOnceDeliveryEnabled.get()) { // For exactly once deliveries we don't add to outstanding batch because we first @@ -457,30 +483,39 @@ private void processBatch(List batch) { for (OutstandingMessage message : batch) { // This is a blocking flow controller. We have already incremented messagesWaiter, so // shutdown will block on processing of all these messages anyway. + message.messageWrapper().startSubscribeConcurrencyControlSpan(tracer); try { - flowController.reserve(1, message.receivedMessage.getMessage().getSerializedSize()); + flowController.reserve(1, message.messageWrapper().getPubsubMessage().getSerializedSize()); + message.messageWrapper().endSubscribeConcurrencyControlSpan(); } catch (FlowControlException unexpectedException) { // This should be a blocking flow controller and never throw an exception. + message.messageWrapper().setSubscribeConcurrencyControlSpanException(unexpectedException); throw new IllegalStateException("Flow control unexpected exception", unexpectedException); } - processOutstandingMessage(addDeliveryInfoCount(message.receivedMessage), message.ackHandler); + addDeliveryInfoCount(message.messageWrapper()); + processOutstandingMessage(message.ackHandler); } } - private PubsubMessage addDeliveryInfoCount(ReceivedMessage receivedMessage) { - PubsubMessage originalMessage = receivedMessage.getMessage(); - int deliveryAttempt = receivedMessage.getDeliveryAttempt(); + private void addDeliveryInfoCount(PubsubMessageWrapper messageWrapper) { + PubsubMessage originalMessage = messageWrapper.getPubsubMessage(); + int deliveryAttempt = messageWrapper.getDeliveryAttempt(); // Delivery Attempt will be set to 0 if DeadLetterPolicy is not set on the subscription. In // this case, do not populate the PubsubMessage with the delivery attempt attribute. if (deliveryAttempt > 0) { - return PubsubMessage.newBuilder(originalMessage) - .putAttributes("googclient_deliveryattempt", Integer.toString(deliveryAttempt)) - .build(); + messageWrapper.setPubsubMessage( + PubsubMessage.newBuilder(originalMessage) + .putAttributes("googclient_deliveryattempt", Integer.toString(deliveryAttempt)) + .build()); } - return originalMessage; } - private void processOutstandingMessage(final PubsubMessage message, final AckHandler ackHandler) { + private void processOutstandingMessage(final AckHandler ackHandler) { + // Get the PubsubMessageWrapper and the PubsubMessage it wraps that are stored withing the + // AckHandler object. + PubsubMessageWrapper messageWrapper = ackHandler.ackRequestData.getMessageWrapper(); + PubsubMessage message = messageWrapper.getPubsubMessage(); + // This future is for internal bookkeeping to be sent to the StreamingSubscriberConnection // use below in the consumers SettableApiFuture ackReplySettableApiFuture = SettableApiFuture.create(); @@ -499,8 +534,10 @@ public void run() { // so it was probably sent to someone else. Don't work on it. // Don't nack it either, because we'd be nacking someone else's message. ackHandler.forget(); + messageWrapper.setSubscriberSpanExpirationResult(); return; } + messageWrapper.startSubscribeProcessSpan(tracer); if (shouldSetMessageFuture()) { // This is the message future that is propagated to the user SettableApiFuture messageFuture = @@ -521,7 +558,9 @@ public void run() { if (!messageOrderingEnabled.get() || message.getOrderingKey().isEmpty()) { executor.execute(deliverMessageTask); } else { + messageWrapper.startSubscribeSchedulerSpan(tracer); sequentialExecutor.submit(message.getOrderingKey(), deliverMessageTask); + messageWrapper.endSubscribeSchedulerSpan(); } } @@ -607,8 +646,10 @@ void processOutstandingOperations() { List ackRequestDataReceipts = new ArrayList(); pendingReceipts.drainTo(ackRequestDataReceipts); if (!ackRequestDataReceipts.isEmpty()) { - modackRequestData.add( - new ModackRequestData(this.getMessageDeadlineSeconds(), ackRequestDataReceipts)); + ModackRequestData receiptModack = + new ModackRequestData(this.getMessageDeadlineSeconds(), ackRequestDataReceipts); + receiptModack.setIsReceiptModack(true); + modackRequestData.add(receiptModack); } logger.log(Level.FINER, "Sending {0} receipts", ackRequestDataReceipts.size()); @@ -645,6 +686,10 @@ public static final class Builder { private ScheduledExecutorService systemExecutor; private ApiClock clock; + private String subscriptionName; + private boolean enableOpenTelemetryTracing; + private Tracer tracer; + protected Builder(MessageReceiver receiver) { this.receiver = receiver; } @@ -715,6 +760,21 @@ public Builder setApiClock(ApiClock clock) { return this; } + public Builder setSubscriptionName(String subscriptionName) { + this.subscriptionName = subscriptionName; + return this; + } + + public Builder setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing) { + this.enableOpenTelemetryTracing = enableOpenTelemetryTracing; + return this; + } + + public Builder setTracer(Tracer tracer) { + this.tracer = tracer; + return this; + } + public MessageDispatcher build() { return new MessageDispatcher(this); } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/ModackRequestData.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/ModackRequestData.java index b4d2dae0f..54c7436af 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/ModackRequestData.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/ModackRequestData.java @@ -21,6 +21,7 @@ class ModackRequestData { private final int deadlineExtensionSeconds; private List ackRequestData; + private boolean isReceiptModack; ModackRequestData(int deadlineExtensionSeconds) { this.deadlineExtensionSeconds = deadlineExtensionSeconds; @@ -45,8 +46,17 @@ public List getAckRequestData() { return ackRequestData; } + public boolean getIsReceiptModack() { + return isReceiptModack; + } + public ModackRequestData addAckRequestData(AckRequestData ackRequestData) { this.ackRequestData.add(ackRequestData); return this; } + + public ModackRequestData setIsReceiptModack(boolean isReceiptModack) { + this.isReceiptModack = isReceiptModack; + return this; + } } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java index 37cc05f7e..6ee6bbccb 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java @@ -16,6 +16,7 @@ package com.google.cloud.pubsub.v1; +import com.google.pubsub.v1.SubscriptionName; import com.google.pubsub.v1.TopicName; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; @@ -28,27 +29,31 @@ public class OpenTelemetryUtil { private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; - private static final String PROJECT_ATTR_KEY = "gcp_pubsub.project_id"; + private static final String PROJECT_ATTR_KEY = "gcp.project_id"; + private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; + private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; /** Populates attributes that are common the publisher parent span and publish RPC span. */ - public static final AttributesBuilder createPublishSpanAttributesBuilder( - TopicName topicName, String codeFunction, String operation) { + public static final AttributesBuilder createCommonSpanAttributesBuilder( + String destinationName, String projectName, String codeFunction, String operation) { AttributesBuilder attributesBuilder = Attributes.builder() .put(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .put(SemanticAttributes.MESSAGING_DESTINATION_NAME, topicName.getTopic()) - .put(PROJECT_ATTR_KEY, topicName.getProject()) - .put(SemanticAttributes.CODE_FUNCTION, codeFunction) - .put(SemanticAttributes.MESSAGING_OPERATION, operation); + .put(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName) + .put(PROJECT_ATTR_KEY, projectName) + .put(SemanticAttributes.CODE_FUNCTION, codeFunction); + if (operation != null) { + attributesBuilder.put(SemanticAttributes.MESSAGING_OPERATION, operation); + } return attributesBuilder; } /** * Creates, starts, and returns a publish RPC span for the given message batch. Bi-directional - * links with the publisher parent span are created for each message in the batch. + * links with the publisher parent span are created for sampled messages in the batch. */ public static final Span startPublishRpcSpan( Tracer tracer, @@ -57,7 +62,8 @@ public static final Span startPublishRpcSpan( boolean enableOpenTelemetryTracing) { if (enableOpenTelemetryTracing && tracer != null) { Attributes attributes = - createPublishSpanAttributesBuilder(topicName, "Publisher.publishCall", "publish") + createCommonSpanAttributesBuilder( + topicName.getTopic(), topicName.getProject(), "Publisher.publishCall", "publish") .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()) .build(); Span publishRpcSpan = @@ -68,11 +74,13 @@ public static final Span startPublishRpcSpan( .startSpan(); for (PubsubMessageWrapper message : messages) { - Attributes linkAttributes = - Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, "publish").build(); - publishRpcSpan.addLink(message.getPublisherSpan().getSpanContext(), linkAttributes); - message.getPublisherSpan().addLink(publishRpcSpan.getSpanContext(), linkAttributes); - message.addPublishStartEvent(); + if (message.getPublisherSpan().getSpanContext().isSampled()) { + Attributes linkAttributes = + Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, "publish").build(); + publishRpcSpan.addLink(message.getPublisherSpan().getSpanContext(), linkAttributes); + message.getPublisherSpan().addLink(publishRpcSpan.getSpanContext(), linkAttributes); + message.addPublishStartEvent(); + } } return publishRpcSpan; } @@ -99,4 +107,76 @@ public static final void setPublishRpcSpanException( endPublishRpcSpan(publishRpcSpan, enableOpenTelemetryTracing); } } + + /** + * Creates, starts, and returns spans for ModAck, Nack, and Ack RPC requests. Bi-directional links + * to parent subscribe span for sampled messages are added. + */ + public static final Span startSubscribeRpcSpan( + Tracer tracer, + SubscriptionName subscriptionName, + String rpcOperation, + List messages, + int ackDeadline, + boolean isReceiptModack, + boolean enableOpenTelemetryTracing) { + if (enableOpenTelemetryTracing && tracer != null) { + String codeFunction = + rpcOperation == "ack" + ? "StreamingSubscriberConnection.sendAckOperations" + : "StreamingSubscriberConnection.sendModAckOperations"; + AttributesBuilder attributesBuilder = + createCommonSpanAttributesBuilder( + subscriptionName.getSubscription(), + subscriptionName.getProject(), + codeFunction, + rpcOperation) + .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()); + + // Ack deadline and receipt modack are specific to the modack operation + if (rpcOperation == "modack") { + attributesBuilder + .put(ACK_DEADLINE_ATTR_KEY, ackDeadline) + .put(RECEIPT_MODACK_ATTR_KEY, isReceiptModack); + } + + Span rpcSpan = + tracer + .spanBuilder(subscriptionName.getSubscription() + " " + rpcOperation) + .setSpanKind(SpanKind.CLIENT) + .setAllAttributes(attributesBuilder.build()) + .startSpan(); + + for (PubsubMessageWrapper message : messages) { + if (message.getSubscriberSpan().getSpanContext().isSampled()) { + Attributes linkAttributes = + Attributes.builder() + .put(SemanticAttributes.MESSAGING_OPERATION, rpcOperation) + .build(); + rpcSpan.addLink(message.getSubscriberSpan().getSpanContext(), linkAttributes); + message.getSubscriberSpan().addLink(rpcSpan.getSpanContext(), linkAttributes); + switch (rpcOperation) { + case "ack": + message.addAckStartEvent(); + break; + case "modack": + message.addModAckStartEvent(); + break; + case "nack": + message.addNackStartEvent(); + break; + } + } + } + return rpcSpan; + } + return null; + } + + /** Ends the given subscribe RPC span if it exists. */ + public static final void endSubscribeRpcSpan(Span rpcSpan, boolean enableOpenTelemetryTracing) { + if (enableOpenTelemetryTracing && rpcSpan != null) { + rpcSpan.end(); + } + } } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java index 1701d53f2..d5547062e 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java @@ -275,7 +275,9 @@ public ApiFuture publish(PubsubMessage message) { PubsubMessageWrapper messageWrapper = PubsubMessageWrapper.newBuilder( - messageTransform.apply(message), topicName, enableOpenTelemetryTracing) + messageTransform.apply(message), + TopicName.parse(topicName), + enableOpenTelemetryTracing) .build(); messageWrapper.startPublisherSpan(tracer); @@ -622,7 +624,7 @@ private void onSuccess(Iterable results) { flowController.release(nextPublish.messageSize); } nextPublish.publishResult.set(messageId); - nextPublish.messageWrapper.setMessageIdSpanAttribute(messageId); + nextPublish.messageWrapper.setPublisherMessageIdSpanAttribute(messageId); nextPublish.messageWrapper.endPublisherSpan(); } } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java index cc00248df..78e6625fe 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java @@ -18,7 +18,9 @@ import com.google.common.base.Preconditions; import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.SubscriptionName; import com.google.pubsub.v1.TopicName; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; @@ -26,6 +28,7 @@ import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; import io.opentelemetry.context.propagation.TextMapSetter; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; @@ -35,36 +38,83 @@ */ public class PubsubMessageWrapper { private PubsubMessage message; + private final boolean enableOpenTelemetryTracing; private final TopicName topicName; + private final SubscriptionName subscriptionName; - private final boolean enableOpenTelemetryTracing; + // Attributes set only for messages received from a streaming pull response. + private final String ackId; + private final int deliveryAttempt; - private final String PUBLISHER_SPAN_NAME; + private String PUBLISHER_SPAN_NAME; private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; private static final String PUBLISH_START_EVENT = "publish start"; private static final String PUBLISH_END_EVENT = "publish end"; + private String SUBSCRIBER_SPAN_NAME; + private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = + "subscriber concurrency control"; + private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; + private String SUBSCRIBE_PROCESS_SPAN_NAME; + private static final String MODACK_START_EVENT = "modack start"; + private static final String MODACK_END_EVENT = "modack end"; + private static final String NACK_START_EVENT = "nack start"; + private static final String NACK_END_EVENT = "nack end"; + private static final String ACK_START_EVENT = "ack start"; + private static final String ACK_END_EVENT = "ack start"; + private static final String GOOGCLIENT_PREFIX = "googclient_"; - private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.envelope.size"; + private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; + private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; + private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; + private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = + "messaging.gcp_pubsub.message.exactly_once_delivery"; + private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; + private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = + "messaging.gcp_pubsub.message.delivery_attempt"; private Span publisherSpan; private Span publishFlowControlSpan; private Span publishBatchingSpan; + private Span subscriberSpan; + private Span subscribeConcurrencyControlSpan; + private Span subscribeSchedulerSpan; + private Span subscribeProcessSpan; + public PubsubMessageWrapper(Builder builder) { this.message = builder.message; this.topicName = builder.topicName; + this.subscriptionName = builder.subscriptionName; + this.ackId = builder.ackId; + this.deliveryAttempt = builder.deliveryAttempt; this.enableOpenTelemetryTracing = builder.enableOpenTelemetryTracing; - this.PUBLISHER_SPAN_NAME = builder.topicName.getTopic() + " create"; + if (this.topicName != null) { + this.PUBLISHER_SPAN_NAME = builder.topicName.getTopic() + " create"; + } + if (this.subscriptionName != null) { + this.SUBSCRIBER_SPAN_NAME = builder.subscriptionName.getSubscription() + " subscribe"; + this.SUBSCRIBE_PROCESS_SPAN_NAME = builder.subscriptionName.getSubscription() + " process"; + } } public static Builder newBuilder( - PubsubMessage message, String fullTopicName, boolean enableOpenTelemetryTracing) { - return new Builder(message, TopicName.parse(fullTopicName), enableOpenTelemetryTracing); + PubsubMessage message, TopicName topicName, boolean enableOpenTelemetryTracing) { + return new Builder(message, topicName, enableOpenTelemetryTracing); + } + + public static Builder newBuilder( + PubsubMessage message, + SubscriptionName subscriptionName, + String ackId, + int deliveryAttempt, + boolean enableOpenTelemetryTracing) { + return new Builder( + message, subscriptionName, ackId, deliveryAttempt, enableOpenTelemetryTracing); } /** Returns the PubsubMessage associated with this wrapper. */ @@ -72,11 +122,26 @@ public PubsubMessage getPubsubMessage() { return message; } - /** Returns the parent span for this message wrapper. */ + /** Returns the parent publisher span for this message wrapper. */ public Span getPublisherSpan() { return publisherSpan; } + /** Returns the parent subscriber span for this message wrapper. */ + public Span getSubscriberSpan() { + return subscriberSpan; + } + + /** Returns the delivery attempt for the received PubsubMessage. */ + public int getDeliveryAttempt() { + return deliveryAttempt; + } + + /** Sets the PubsubMessage for this wrapper. */ + public void setPubsubMessage(PubsubMessage message) { + this.message = message; + } + /** * Creates and starts the parent span with the appropriate span attributes and injects the span * context into the {@link PubsubMessage} attributes. @@ -84,9 +149,10 @@ public Span getPublisherSpan() { public void startPublisherSpan(Tracer tracer) { if (enableOpenTelemetryTracing && tracer != null) { AttributesBuilder attributesBuilder = - OpenTelemetryUtil.createPublishSpanAttributesBuilder( - topicName, "Publisher.publish", "create"); - attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getSerializedSize()); + OpenTelemetryUtil.createCommonSpanAttributesBuilder( + topicName.getTopic(), topicName.getProject(), "Publisher.publish", "create"); + + attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getData().size()); if (!message.getOrderingKey().isEmpty()) { attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); } @@ -138,7 +204,7 @@ public void addPublishEndEvent() { * Sets the message ID attribute in the publisher parent span. This is called after the publish * RPC returns with a message ID. */ - public void setMessageIdSpanAttribute(String messageId) { + public void setPublisherMessageIdSpanAttribute(String messageId) { if (enableOpenTelemetryTracing && publisherSpan != null) { publisherSpan.setAttribute(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId); } @@ -190,44 +256,250 @@ public void setPublishBatchingSpanException(Throwable t) { } } + /** + * Creates the subscriber parent span using span context propagated in the message attributes and + * sets the appropriate span attributes. + */ + public void startSubscriberSpan(Tracer tracer, boolean exactlyOnceDeliveryEnabled) { + if (enableOpenTelemetryTracing && tracer != null) { + AttributesBuilder attributesBuilder = + OpenTelemetryUtil.createCommonSpanAttributesBuilder( + subscriptionName.getSubscription(), + subscriptionName.getProject(), + "StreamingSubscriberConnection.onResponse", + null); + + attributesBuilder + .put(MESSAGE_SIZE_ATTR_KEY, message.getData().size()) + .put(MESSAGE_ACK_ID_ATTR_KEY, ackId) + .put(MESSAGE_EXACTLY_ONCE_ATTR_KEY, exactlyOnceDeliveryEnabled); + if (!message.getOrderingKey().isEmpty()) { + attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); + } + if (deliveryAttempt > 0) { + attributesBuilder.put(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, deliveryAttempt); + } + subscriberSpan = extractSpanContext(tracer, attributesBuilder.build()); + } + } + + /** Creates a span for subscribe concurrency control as a child of the parent subscriber span. */ + public void startSubscribeConcurrencyControlSpan(Tracer tracer) { + if (enableOpenTelemetryTracing && tracer != null) { + subscribeConcurrencyControlSpan = + startChildSpan(tracer, SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME, subscriberSpan); + } + } + + /** + * Creates a span for subscribe ordering key scheduling as a child of the parent subscriber span. + */ + public void startSubscribeSchedulerSpan(Tracer tracer) { + if (enableOpenTelemetryTracing && tracer != null) { + subscribeSchedulerSpan = + startChildSpan(tracer, SUBSCRIBE_SCHEDULER_SPAN_NAME, subscriberSpan); + } + } + + /** Creates a span for subscribe message processing as a child of the parent subscriber span. */ + public void startSubscribeProcessSpan(Tracer tracer) { + if (enableOpenTelemetryTracing && tracer != null) { + subscribeProcessSpan = startChildSpan(tracer, SUBSCRIBE_PROCESS_SPAN_NAME, subscriberSpan); + subscribeProcessSpan.setAttribute( + SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE); + if (publisherSpan != null) { + subscribeProcessSpan.addLink(publisherSpan.getSpanContext()); + } + } + } + + /** + * Creates start and end events for ModAcks, Nacks, and Acks that are tied to the corresponding + * RPC span start and end times. + */ + public void addModAckStartEvent() { + if (enableOpenTelemetryTracing && subscriberSpan != null) { + subscriberSpan.addEvent(MODACK_START_EVENT); + } + } + + public void addModAckEndEvent() { + if (enableOpenTelemetryTracing && subscriberSpan != null) { + subscriberSpan.addEvent(MODACK_END_EVENT); + } + } + + public void addNackStartEvent() { + if (enableOpenTelemetryTracing && subscriberSpan != null) { + subscriberSpan.addEvent(NACK_START_EVENT); + } + } + + public void addNackEndEvent() { + if (enableOpenTelemetryTracing && subscriberSpan != null) { + subscriberSpan.addEvent(NACK_END_EVENT); + } + } + + public void addAckStartEvent() { + if (enableOpenTelemetryTracing && subscriberSpan != null) { + subscriberSpan.addEvent(ACK_START_EVENT); + } + } + + public void addAckEndEvent() { + if (enableOpenTelemetryTracing && subscriberSpan != null) { + subscriberSpan.addEvent(ACK_END_EVENT); + } + } + + public void addEndRpcEvent(boolean isModack, int ackDeadline) { + if (!isModack) { + addAckEndEvent(); + } else if (ackDeadline == 0) { + addNackEndEvent(); + } else { + addModAckEndEvent(); + } + } + + /** Ends the subscriber parent span if exists. */ + public void endSubscriberSpan() { + if (enableOpenTelemetryTracing && subscriberSpan != null) { + subscriberSpan.end(); + } + } + + /** Ends the subscribe concurreny control span if exists. */ + public void endSubscribeConcurrencyControlSpan() { + if (enableOpenTelemetryTracing && subscribeConcurrencyControlSpan != null) { + subscribeConcurrencyControlSpan.end(); + } + } + + /** Ends the subscribe scheduler span if exists. */ + public void endSubscribeSchedulerSpan() { + if (enableOpenTelemetryTracing && subscribeSchedulerSpan != null) { + subscribeSchedulerSpan.end(); + } + } + + /** + * Ends the subscribe process span if it exists, creates an event with the appropriate result, and + * sets the result on the parent subscriber span. + */ + public void endSubscribeProcessSpan(String action) { + if (enableOpenTelemetryTracing && subscribeProcessSpan != null) { + subscribeProcessSpan.addEvent(action + " called"); + subscribeProcessSpan.end(); + subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, action); + } + } + + /** Sets an exception on the subscriber span during Ack/ModAck/Nack failures */ + public void setSubscriberSpanException(Throwable t, String exception) { + if (enableOpenTelemetryTracing && subscriberSpan != null) { + subscriberSpan.setStatus(StatusCode.ERROR, exception); + subscriberSpan.recordException(t); + endAllSubscribeSpans(); + } + } + + /** Sets result of the parent subscriber span to expired and ends its. */ + public void setSubscriberSpanExpirationResult() { + if (enableOpenTelemetryTracing && subscriberSpan != null) { + subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, "expired"); + endSubscriberSpan(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown subscriber + * concurrency control. + */ + public void setSubscribeConcurrencyControlSpanException(Throwable t) { + if (enableOpenTelemetryTracing && subscribeConcurrencyControlSpan != null) { + subscribeConcurrencyControlSpan.setStatus( + StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); + subscribeConcurrencyControlSpan.recordException(t); + endAllSubscribeSpans(); + } + } + /** Creates a child span of the given parent span. */ private Span startChildSpan(Tracer tracer, String name, Span parent) { return tracer.spanBuilder(name).setParent(Context.current().with(parent)).startSpan(); } - /** Ends all spans associated with this message wrapper. */ + /** Ends all publisher-side spans associated with this message wrapper. */ private void endAllPublishSpans() { endPublishFlowControlSpan(); endPublishBatchingSpan(); endPublisherSpan(); } + /** Ends all subscriber-side spans associated with this message wrapper. */ + private void endAllSubscribeSpans() { + endSubscribeConcurrencyControlSpan(); + endSubscribeSchedulerSpan(); + endSubscriberSpan(); + } + /** * Injects the span context into the attributes of a Pub/Sub message for propagation to the * subscriber client. */ private void injectSpanContext() { - if (enableOpenTelemetryTracing && publisherSpan != null) { - TextMapSetter injectMessageAttributes = - new TextMapSetter() { - @Override - public void set(PubsubMessageWrapper carrier, String key, String value) { - PubsubMessage newMessage = - PubsubMessage.newBuilder(carrier.message) - .putAttributes(GOOGCLIENT_PREFIX + key, value) - .build(); - carrier.message = newMessage; - } - }; - W3CTraceContextPropagator.getInstance() - .inject(Context.current().with(publisherSpan), this, injectMessageAttributes); - } + TextMapSetter injectMessageAttributes = + new TextMapSetter() { + @Override + public void set(PubsubMessageWrapper carrier, String key, String value) { + PubsubMessage newMessage = + PubsubMessage.newBuilder(carrier.message) + .putAttributes(GOOGCLIENT_PREFIX + key, value) + .build(); + carrier.message = newMessage; + } + }; + W3CTraceContextPropagator.getInstance() + .inject(Context.current().with(publisherSpan), this, injectMessageAttributes); + } + + /** + * Extracts the span context from the attributes of a Pub/Sub message and creates the parent + * subscriber span using that context. + */ + private Span extractSpanContext(Tracer tracer, Attributes attributes) { + TextMapGetter extractMessageAttributes = + new TextMapGetter() { + @Override + public String get(PubsubMessageWrapper carrier, String key) { + return carrier.message.getAttributesOrDefault(GOOGCLIENT_PREFIX + key, ""); + } + + public Iterable keys(PubsubMessageWrapper carrier) { + return carrier.message.getAttributesMap().keySet(); + } + }; + Context context = + W3CTraceContextPropagator.getInstance() + .extract(Context.current(), this, extractMessageAttributes); + publisherSpan = Span.fromContextOrNull(context); + return tracer + .spanBuilder(SUBSCRIBER_SPAN_NAME) + .setSpanKind(SpanKind.CONSUMER) + .setParent(context) + .setAllAttributes(attributes) + .startSpan(); } /** Builder of {@link PubsubMessageWrapper PubsubMessageWrapper}. */ protected static final class Builder { private PubsubMessage message = null; private TopicName topicName = null; + private SubscriptionName subscriptionName = null; + private String ackId = null; + private int deliveryAttempt = 0; private boolean enableOpenTelemetryTracing = false; public Builder(PubsubMessage message, TopicName topicName, boolean enableOpenTelemetryTracing) { @@ -236,8 +508,21 @@ public Builder(PubsubMessage message, TopicName topicName, boolean enableOpenTel this.enableOpenTelemetryTracing = enableOpenTelemetryTracing; } + public Builder( + PubsubMessage message, + SubscriptionName subscriptionName, + String ackId, + int deliveryAttempt, + boolean enableOpenTelemetryTracing) { + this.message = message; + this.subscriptionName = subscriptionName; + this.ackId = ackId; + this.deliveryAttempt = deliveryAttempt; + this.enableOpenTelemetryTracing = enableOpenTelemetryTracing; + } + public PubsubMessageWrapper build() { - Preconditions.checkArgument(this.topicName != null); + Preconditions.checkArgument(this.topicName != null || this.subscriptionName != null); return new PubsubMessageWrapper(this); } } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java index 7849bdb74..5824ba41b 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java @@ -47,9 +47,12 @@ import com.google.pubsub.v1.ModifyAckDeadlineRequest; import com.google.pubsub.v1.StreamingPullRequest; import com.google.pubsub.v1.StreamingPullResponse; +import com.google.pubsub.v1.SubscriptionName; import com.google.rpc.ErrorInfo; import io.grpc.Status; import io.grpc.protobuf.StatusProto; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -118,6 +121,10 @@ final class StreamingSubscriberConnection extends AbstractApiService implements */ private final String clientId = UUID.randomUUID().toString(); + private final String subscriptionName; + private final boolean enableOpenTelemetryTracing; + private final Tracer tracer; + private StreamingSubscriberConnection(Builder builder) { subscription = builder.subscription; systemExecutor = builder.systemExecutor; @@ -151,6 +158,10 @@ private StreamingSubscriberConnection(Builder builder) { messageDispatcherBuilder = MessageDispatcher.newBuilder(builder.receiverWithAckResponse); } + subscriptionName = builder.subscriptionName; + enableOpenTelemetryTracing = builder.enableOpenTelemetryTracing; + tracer = builder.tracer; + messageDispatcher = messageDispatcherBuilder .setAckProcessor(this) @@ -165,6 +176,9 @@ private StreamingSubscriberConnection(Builder builder) { .setExecutor(builder.executor) .setSystemExecutor(builder.systemExecutor) .setApiClock(builder.clock) + .setSubscriptionName(subscriptionName) + .setEnableOpenTelemetryTracing(enableOpenTelemetryTracing) + .setTracer(tracer) .build(); flowControlSettings = builder.flowControlSettings; @@ -432,15 +446,27 @@ private void sendAckOperations( for (List ackRequestDataInRequestList : Lists.partition(ackRequestDataList, MAX_PER_REQUEST_CHANGES)) { List ackIdsInRequest = new ArrayList<>(); + List messagesInRequest = new ArrayList<>(); for (AckRequestData ackRequestData : ackRequestDataInRequestList) { ackIdsInRequest.add(ackRequestData.getAckId()); + messagesInRequest.add(ackRequestData.getMessageWrapper()); if (ackRequestData.hasMessageFuture()) { // Add to our pending requests if we care about the response pendingRequests.add(ackRequestData); } } + // Creates an Ack span to be passed to the callback + Span rpcSpan = + OpenTelemetryUtil.startSubscribeRpcSpan( + tracer, + SubscriptionName.parse(subscriptionName), + "ack", + messagesInRequest, + 0, + false, + enableOpenTelemetryTracing); ApiFutureCallback callback = - getCallback(ackRequestDataInRequestList, 0, false, currentBackoffMillis); + getCallback(ackRequestDataInRequestList, 0, false, currentBackoffMillis, rpcSpan); ApiFuture ackFuture = subscriberStub .acknowledgeCallable() @@ -463,19 +489,34 @@ private void sendModackOperations( for (List ackRequestDataInRequestList : Lists.partition(modackRequestData.getAckRequestData(), MAX_PER_REQUEST_CHANGES)) { List ackIdsInRequest = new ArrayList<>(); + List messagesInRequest = new ArrayList<>(); for (AckRequestData ackRequestData : ackRequestDataInRequestList) { ackIdsInRequest.add(ackRequestData.getAckId()); + messagesInRequest.add(ackRequestData.getMessageWrapper()); if (ackRequestData.hasMessageFuture()) { // Add to our pending requests if we care about the response pendingRequests.add(ackRequestData); } } + int deadlineExtensionSeconds = modackRequestData.getDeadlineExtensionSeconds(); + String rpcOperation = deadlineExtensionSeconds == 0 ? "nack" : "modack"; + // Creates either a ModAck span or a Nack span depending on the given ack deadline + Span rpcSpan = + OpenTelemetryUtil.startSubscribeRpcSpan( + tracer, + SubscriptionName.parse(subscriptionName), + rpcOperation, + messagesInRequest, + deadlineExtensionSeconds, + modackRequestData.getIsReceiptModack(), + enableOpenTelemetryTracing); ApiFutureCallback callback = getCallback( modackRequestData.getAckRequestData(), - modackRequestData.getDeadlineExtensionSeconds(), + deadlineExtensionSeconds, true, - currentBackoffMillis); + currentBackoffMillis, + rpcSpan); ApiFuture modackFuture = subscriberStub .modifyAckDeadlineCallable() @@ -517,7 +558,8 @@ private ApiFutureCallback getCallback( List ackRequestDataList, int deadlineExtensionSeconds, boolean isModack, - long currentBackoffMillis) { + long currentBackoffMillis, + Span rpcSpan) { // This callback handles retries, and sets message futures // Check if ack or nack @@ -533,7 +575,16 @@ public void onSuccess(Empty empty) { messageDispatcher.notifyAckSuccess(ackRequestData); // Remove from our pending operations pendingRequests.remove(ackRequestData); + if (!isModack) { + ackRequestData.getMessageWrapper().endSubscriberSpan(); + ackRequestData.getMessageWrapper().addAckEndEvent(); + } else if (deadlineExtensionSeconds == 0) { + ackRequestData.getMessageWrapper().addNackEndEvent(); + } else { + ackRequestData.getMessageWrapper().addModAckEndEvent(); + } } + OpenTelemetryUtil.endSubscribeRpcSpan(rpcSpan, enableOpenTelemetryTracing); } @Override @@ -544,10 +595,17 @@ public void onFailure(Throwable t) { Level level = isAlive() ? Level.WARNING : Level.FINER; logger.log(level, "failed to send operations", t); + OpenTelemetryUtil.endSubscribeRpcSpan(rpcSpan, enableOpenTelemetryTracing); + if (!getExactlyOnceDeliveryEnabled()) { + if (enableOpenTelemetryTracing) { + for (AckRequestData ackRequestData : ackRequestDataList) { + ackRequestData.getMessageWrapper().endSubscriberSpan(); + ackRequestData.getMessageWrapper().addEndRpcEvent(isModack, deadlineExtensionSeconds); + } + } return; } - List ackRequestDataArrayRetryList = new ArrayList<>(); try { Map metadataMap = getMetadataMapFromThrowable(t); @@ -569,14 +627,30 @@ public void onFailure(Throwable t) { errorMessage); ackRequestData.setResponse(AckResponse.INVALID, setResponseOnSuccess); messageDispatcher.notifyAckFailed(ackRequestData); + ackRequestData + .getMessageWrapper() + .setSubscriberSpanException(t, "Invalid ack ID"); + ackRequestData + .getMessageWrapper() + .addEndRpcEvent(isModack, deadlineExtensionSeconds); } else { logger.log(Level.INFO, "Unknown error message, will not resend", errorMessage); ackRequestData.setResponse(AckResponse.OTHER, setResponseOnSuccess); messageDispatcher.notifyAckFailed(ackRequestData); + ackRequestData + .getMessageWrapper() + .setSubscriberSpanException(t, "Unknown error message"); + ackRequestData + .getMessageWrapper() + .addEndRpcEvent(isModack, deadlineExtensionSeconds); } } else { ackRequestData.setResponse(AckResponse.SUCCESSFUL, setResponseOnSuccess); messageDispatcher.notifyAckSuccess(ackRequestData); + ackRequestData.getMessageWrapper().endSubscriberSpan(); + ackRequestData + .getMessageWrapper() + .addEndRpcEvent(isModack, deadlineExtensionSeconds); } // Remove from our pending pendingRequests.remove(ackRequestData); @@ -637,6 +711,10 @@ public static final class Builder { private ScheduledExecutorService systemExecutor; private ApiClock clock; + private String subscriptionName; + private boolean enableOpenTelemetryTracing; + private Tracer tracer; + protected Builder(MessageReceiver receiver) { this.receiver = receiver; } @@ -727,6 +805,21 @@ public Builder setClock(ApiClock clock) { return this; } + public Builder setSubscriptionName(String subscriptionName) { + this.subscriptionName = subscriptionName; + return this; + } + + public Builder setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing) { + this.enableOpenTelemetryTracing = enableOpenTelemetryTracing; + return this; + } + + public Builder setTracer(Tracer tracer) { + this.tracer = tracer; + return this; + } + public StreamingSubscriberConnection build() { return new StreamingSubscriberConnection(this); } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java index 1723c72b1..14b6731f1 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java @@ -43,6 +43,8 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.pubsub.v1.ProjectSubscriptionName; import com.google.pubsub.v1.PubsubMessage; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Tracer; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -117,6 +119,8 @@ public class Subscriber extends AbstractApiService implements SubscriberInterfac private static final Logger logger = Logger.getLogger(Subscriber.class.getName()); + private static final String OPEN_TELEMETRY_TRACER_NAME = "com.google.cloud.pubsub.v1"; + private final String subscriptionName; private final FlowControlSettings flowControlSettings; private final boolean useLegacyFlowControl; @@ -145,6 +149,10 @@ public class Subscriber extends AbstractApiService implements SubscriberInterfac private final ApiClock clock; private final List backgroundResources = new ArrayList<>(); + private final boolean enableOpenTelemetryTracing; + private final OpenTelemetry openTelemetry; + private Tracer tracer = null; + private Subscriber(Builder builder) { receiver = builder.receiver; receiverWithAckResponse = builder.receiverWithAckResponse; @@ -199,6 +207,12 @@ private Subscriber(Builder builder) { throw new IllegalStateException(e); } + this.enableOpenTelemetryTracing = builder.enableOpenTelemetryTracing; + this.openTelemetry = builder.openTelemetry; + if (this.openTelemetry != null) { + this.tracer = builder.openTelemetry.getTracer(OPEN_TELEMETRY_TRACER_NAME); + } + streamingSubscriberConnections = new ArrayList(numPullers); // We regularly look up the distribution for a good subscription deadline. @@ -386,6 +400,9 @@ private void startStreamingConnections() { .setExecutor(executor) .setSystemExecutor(alarmsExecutor) .setClock(clock) + .setSubscriptionName(subscriptionName) + .setEnableOpenTelemetryTracing(enableOpenTelemetryTracing) + .setTracer(tracer) .build(); streamingSubscriberConnections.add(streamingSubscriberConnection); @@ -495,6 +512,9 @@ public static final class Builder { private String endpoint = null; private String universeDomain = null; + private boolean enableOpenTelemetryTracing = false; + private OpenTelemetry openTelemetry = null; + Builder(String subscription, MessageReceiver receiver) { this.subscription = subscription; this.receiver = receiver; @@ -684,6 +704,18 @@ Builder setClock(ApiClock clock) { return this; } + /** Gives the ability to enable Open Telemetry Tracing */ + public Builder setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing) { + this.enableOpenTelemetryTracing = enableOpenTelemetryTracing; + return this; + } + + /** Sets the instance of OpenTelemetry for the Publisher class. */ + public Builder setOpenTelemetry(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + return this; + } + /** Returns the default FlowControlSettings used by the client if settings are not provided. */ public static FlowControlSettings getDefaultFlowControlSettings() { return DEFAULT_FLOW_CONTROL_SETTINGS; diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java index a0e06303c..9c414d6a9 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -67,8 +67,7 @@ public void testPublishSpansSuccess() { openTelemetryTesting.clearSpans(); PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) - .build(); + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME, true).build(); List messageWrappers = Arrays.asList(messageWrapper); long messageSize = messageWrapper.getPubsubMessage().getSerializedSize(); @@ -83,7 +82,7 @@ public void testPublishSpansSuccess() { Span publishRpcSpan = OpenTelemetryUtil.startPublishRpcSpan(tracer, FULL_TOPIC_NAME, messageWrappers, true); OpenTelemetryUtil.endPublishRpcSpan(publishRpcSpan, true); - messageWrapper.setMessageIdSpanAttribute(MESSAGE_ID); + messageWrapper.setPublisherMessageIdSpanAttribute(MESSAGE_ID); messageWrapper.endPublisherSpan(); List allSpans = openTelemetryTesting.getSpans(); @@ -182,8 +181,7 @@ public void testPublishFlowControlSpanFailure() { openTelemetryTesting.clearSpans(); PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) - .build(); + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME, true).build(); Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); @@ -222,8 +220,7 @@ public void testPublishBatchingSpanFailure() { openTelemetryTesting.clearSpans(); PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) - .build(); + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME, true).build(); Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); @@ -261,8 +258,7 @@ public void testPublishRpcSpanFailure() { openTelemetryTesting.clearSpans(); PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) - .build(); + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME, true).build(); List messageWrappers = Arrays.asList(messageWrapper); Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); From 620e6b52da74ed019add1815f9418b58fdf92364 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Tue, 2 Jul 2024 06:55:57 +0000 Subject: [PATCH 15/46] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a1adf4407..bae5422f4 100644 --- a/README.md +++ b/README.md @@ -59,13 +59,13 @@ implementation 'com.google.cloud:google-cloud-pubsub' If you are using Gradle without BOM, add this to your dependencies: ```Groovy -implementation 'com.google.cloud:google-cloud-pubsub:1.130.1' +implementation 'com.google.cloud:google-cloud-pubsub:1.131.0' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-pubsub" % "1.130.1" +libraryDependencies += "com.google.cloud" % "google-cloud-pubsub" % "1.131.0" ``` @@ -411,7 +411,7 @@ Java is a registered trademark of Oracle and/or its affiliates. [kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-pubsub/java11.html [stability-image]: https://img.shields.io/badge/stability-stable-green [maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-pubsub.svg -[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-pubsub/1.130.1 +[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-pubsub/1.131.0 [authentication]: https://github.com/googleapis/google-cloud-java#authentication [auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes [predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles From 61257b87e8ff1e0ea0bda0acae836fab4f7c26d8 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Tue, 2 Jul 2024 03:32:26 -0400 Subject: [PATCH 16/46] Opentelemetry subscribe (#2101) * feat: Add OpenTelemetry tracing to the SubscriberClient * feat: Add link to publisher create span in the subscribe process span * feat: Add Ack/Nack/ModAck RPC spans to the subscribe * fix: Fix test errors caused by otel changes --- .../cloud/pubsub/v1/AckRequestData.java | 7 +++++ .../cloud/pubsub/v1/MessageDispatcher.java | 3 +- .../cloud/pubsub/v1/OpenTelemetryUtil.java | 6 ++-- .../com/google/cloud/pubsub/v1/Publisher.java | 2 +- .../cloud/pubsub/v1/PubsubMessageWrapper.java | 30 +++++++++++++++---- .../v1/StreamingSubscriberConnection.java | 20 ++----------- .../google/cloud/pubsub/v1/Subscriber.java | 1 - .../cloud/pubsub/v1/OpenTelemetryTest.java | 12 ++++---- 8 files changed, 48 insertions(+), 33 deletions(-) diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/AckRequestData.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/AckRequestData.java index 2bbdb1d75..64bce6cc2 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/AckRequestData.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/AckRequestData.java @@ -38,7 +38,14 @@ public SettableApiFuture getMessageFutureIfExists() { return this.messageFuture.orElse(null); } + /** + * Returns an empty PubsubMessageWrapper with OpenTelemetry tracing disabled. This allows methods + * that use this method to be unit tested. + */ public PubsubMessageWrapper getMessageWrapper() { + if (this.messageWrapper == null) { + return PubsubMessageWrapper.newBuilder(null, null, false).build(); + } return messageWrapper; } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java index a766efb18..26d1f253d 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java @@ -28,7 +28,6 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.pubsub.v1.PubsubMessage; import com.google.pubsub.v1.ReceivedMessage; -import com.google.pubsub.v1.SubscriptionName; import io.opentelemetry.api.trace.Tracer; import java.util.ArrayList; import java.util.HashMap; @@ -408,7 +407,7 @@ void processReceivedMessages(List messages) { PubsubMessageWrapper messageWrapper = PubsubMessageWrapper.newBuilder( message.getMessage(), - SubscriptionName.parse(subscriptionName), + subscriptionName, message.getAckId(), message.getDeliveryAttempt(), enableOpenTelemetryTracing) diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java index 6ee6bbccb..b99db9c2a 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java @@ -57,10 +57,11 @@ public static final AttributesBuilder createCommonSpanAttributesBuilder( */ public static final Span startPublishRpcSpan( Tracer tracer, - TopicName topicName, + String topic, List messages, boolean enableOpenTelemetryTracing) { if (enableOpenTelemetryTracing && tracer != null) { + TopicName topicName = TopicName.parse(topic); Attributes attributes = createCommonSpanAttributesBuilder( topicName.getTopic(), topicName.getProject(), "Publisher.publishCall", "publish") @@ -114,7 +115,7 @@ public static final void setPublishRpcSpanException( */ public static final Span startSubscribeRpcSpan( Tracer tracer, - SubscriptionName subscriptionName, + String subscription, String rpcOperation, List messages, int ackDeadline, @@ -125,6 +126,7 @@ public static final Span startSubscribeRpcSpan( rpcOperation == "ack" ? "StreamingSubscriberConnection.sendAckOperations" : "StreamingSubscriberConnection.sendModAckOperations"; + SubscriptionName subscriptionName = SubscriptionName.parse(subscription); AttributesBuilder attributesBuilder = createCommonSpanAttributesBuilder( subscriptionName.getSubscription(), diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java index d5547062e..fab0c4e5c 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java @@ -488,7 +488,7 @@ private ApiFuture publishCall(OutstandingBatch outstandingBatch outstandingBatch.publishRpcSpan = OpenTelemetryUtil.startPublishRpcSpan( - tracer, TopicName.parse(topicName), messageWrappers, enableOpenTelemetryTracing); + tracer, topicName, messageWrappers, enableOpenTelemetryTracing); return publisherStub .publishCallable() diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java index 78e6625fe..cf270351c 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java @@ -103,13 +103,13 @@ public PubsubMessageWrapper(Builder builder) { } public static Builder newBuilder( - PubsubMessage message, TopicName topicName, boolean enableOpenTelemetryTracing) { + PubsubMessage message, String topicName, boolean enableOpenTelemetryTracing) { return new Builder(message, topicName, enableOpenTelemetryTracing); } public static Builder newBuilder( PubsubMessage message, - SubscriptionName subscriptionName, + String subscriptionName, String ackId, int deliveryAttempt, boolean enableOpenTelemetryTracing) { @@ -502,9 +502,26 @@ protected static final class Builder { private int deliveryAttempt = 0; private boolean enableOpenTelemetryTracing = false; - public Builder(PubsubMessage message, TopicName topicName, boolean enableOpenTelemetryTracing) { + public Builder(PubsubMessage message, String topicName, boolean enableOpenTelemetryTracing) { this.message = message; - this.topicName = topicName; + if (topicName != null) { + this.topicName = TopicName.parse(topicName); + } + this.enableOpenTelemetryTracing = enableOpenTelemetryTracing; + } + + public Builder( + PubsubMessage message, + String subscriptionName, + String ackId, + int deliveryAttempt, + boolean enableOpenTelemetryTracing) { + this.message = message; + if (subscriptionName != null) { + this.subscriptionName = SubscriptionName.parse(subscriptionName); + } + this.ackId = ackId; + this.deliveryAttempt = deliveryAttempt; this.enableOpenTelemetryTracing = enableOpenTelemetryTracing; } @@ -522,7 +539,10 @@ public Builder( } public PubsubMessageWrapper build() { - Preconditions.checkArgument(this.topicName != null || this.subscriptionName != null); + Preconditions.checkArgument( + this.enableOpenTelemetryTracing == false + || this.topicName != null + || this.subscriptionName != null); return new PubsubMessageWrapper(this); } } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java index 5824ba41b..17b1e7939 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java @@ -121,7 +121,6 @@ final class StreamingSubscriberConnection extends AbstractApiService implements */ private final String clientId = UUID.randomUUID().toString(); - private final String subscriptionName; private final boolean enableOpenTelemetryTracing; private final Tracer tracer; @@ -158,7 +157,6 @@ private StreamingSubscriberConnection(Builder builder) { messageDispatcherBuilder = MessageDispatcher.newBuilder(builder.receiverWithAckResponse); } - subscriptionName = builder.subscriptionName; enableOpenTelemetryTracing = builder.enableOpenTelemetryTracing; tracer = builder.tracer; @@ -176,7 +174,7 @@ private StreamingSubscriberConnection(Builder builder) { .setExecutor(builder.executor) .setSystemExecutor(builder.systemExecutor) .setApiClock(builder.clock) - .setSubscriptionName(subscriptionName) + .setSubscriptionName(subscription) .setEnableOpenTelemetryTracing(enableOpenTelemetryTracing) .setTracer(tracer) .build(); @@ -458,13 +456,7 @@ private void sendAckOperations( // Creates an Ack span to be passed to the callback Span rpcSpan = OpenTelemetryUtil.startSubscribeRpcSpan( - tracer, - SubscriptionName.parse(subscriptionName), - "ack", - messagesInRequest, - 0, - false, - enableOpenTelemetryTracing); + tracer, subscription, "ack", messagesInRequest, 0, false, enableOpenTelemetryTracing); ApiFutureCallback callback = getCallback(ackRequestDataInRequestList, 0, false, currentBackoffMillis, rpcSpan); ApiFuture ackFuture = @@ -504,7 +496,7 @@ private void sendModackOperations( Span rpcSpan = OpenTelemetryUtil.startSubscribeRpcSpan( tracer, - SubscriptionName.parse(subscriptionName), + subscription, rpcOperation, messagesInRequest, deadlineExtensionSeconds, @@ -711,7 +703,6 @@ public static final class Builder { private ScheduledExecutorService systemExecutor; private ApiClock clock; - private String subscriptionName; private boolean enableOpenTelemetryTracing; private Tracer tracer; @@ -805,11 +796,6 @@ public Builder setClock(ApiClock clock) { return this; } - public Builder setSubscriptionName(String subscriptionName) { - this.subscriptionName = subscriptionName; - return this; - } - public Builder setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing) { this.enableOpenTelemetryTracing = enableOpenTelemetryTracing; return this; diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java index 14b6731f1..e581875a7 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java @@ -400,7 +400,6 @@ private void startStreamingConnections() { .setExecutor(executor) .setSystemExecutor(alarmsExecutor) .setClock(clock) - .setSubscriptionName(subscriptionName) .setEnableOpenTelemetryTracing(enableOpenTelemetryTracing) .setTracer(tracer) .build(); diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java index 9c414d6a9..8690d0f04 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -54,8 +54,8 @@ public class OpenTelemetryTest { private static final String PUBLISH_END_EVENT = "publish end"; private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; - private static final String PROJECT_ATTR_KEY = "gcp_pubsub.project_id"; - private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.envelope.size"; + private static final String PROJECT_ATTR_KEY = "gcp.project_id"; + private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; private static final String TRACEPARENT_ATTRIBUTE = "googclient_traceparent"; @@ -70,7 +70,7 @@ public void testPublishSpansSuccess() { PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME, true).build(); List messageWrappers = Arrays.asList(messageWrapper); - long messageSize = messageWrapper.getPubsubMessage().getSerializedSize(); + long messageSize = messageWrapper.getPubsubMessage().getData().size(); Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); // Call all span start/end methods in the expected order @@ -80,7 +80,8 @@ public void testPublishSpansSuccess() { messageWrapper.startPublishBatchingSpan(tracer); messageWrapper.endPublishBatchingSpan(); Span publishRpcSpan = - OpenTelemetryUtil.startPublishRpcSpan(tracer, FULL_TOPIC_NAME, messageWrappers, true); + OpenTelemetryUtil.startPublishRpcSpan( + tracer, FULL_TOPIC_NAME.toString(), messageWrappers, true); OpenTelemetryUtil.endPublishRpcSpan(publishRpcSpan, true); messageWrapper.setPublisherMessageIdSpanAttribute(MESSAGE_ID); messageWrapper.endPublisherSpan(); @@ -265,7 +266,8 @@ public void testPublishRpcSpanFailure() { messageWrapper.startPublisherSpan(tracer); Span publishRpcSpan = - OpenTelemetryUtil.startPublishRpcSpan(tracer, FULL_TOPIC_NAME, messageWrappers, true); + OpenTelemetryUtil.startPublishRpcSpan( + tracer, FULL_TOPIC_NAME.toString(), messageWrappers, true); Exception e = new Exception("test-exception"); OpenTelemetryUtil.setPublishRpcSpanException(publishRpcSpan, e, true); From b0e0424ab4d4a82c5bfc443a9d188b2e0ca1496c Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Tue, 2 Jul 2024 07:35:31 +0000 Subject: [PATCH 17/46] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- .../google/cloud/pubsub/v1/StreamingSubscriberConnection.java | 1 - 1 file changed, 1 deletion(-) diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java index 17b1e7939..ff8c78cff 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java @@ -47,7 +47,6 @@ import com.google.pubsub.v1.ModifyAckDeadlineRequest; import com.google.pubsub.v1.StreamingPullRequest; import com.google.pubsub.v1.StreamingPullResponse; -import com.google.pubsub.v1.SubscriptionName; import com.google.rpc.ErrorInfo; import io.grpc.Status; import io.grpc.protobuf.StatusProto; From e21a9f018622e26bee4aa66ade5456d6487dfad2 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Tue, 2 Jul 2024 07:38:55 +0000 Subject: [PATCH 18/46] fix: Fix build errors in Publisher --- .../java/com/google/cloud/pubsub/v1/Publisher.java | 4 +--- .../google/cloud/pubsub/v1/OpenTelemetryTest.java | 12 ++++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java index fab0c4e5c..451bf6e89 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java @@ -275,9 +275,7 @@ public ApiFuture publish(PubsubMessage message) { PubsubMessageWrapper messageWrapper = PubsubMessageWrapper.newBuilder( - messageTransform.apply(message), - TopicName.parse(topicName), - enableOpenTelemetryTracing) + messageTransform.apply(message), topicName, enableOpenTelemetryTracing) .build(); messageWrapper.startPublisherSpan(tracer); diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java index 8690d0f04..ac9ab6c80 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -67,7 +67,8 @@ public void testPublishSpansSuccess() { openTelemetryTesting.clearSpans(); PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME, true).build(); + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) + .build(); List messageWrappers = Arrays.asList(messageWrapper); long messageSize = messageWrapper.getPubsubMessage().getData().size(); @@ -182,7 +183,8 @@ public void testPublishFlowControlSpanFailure() { openTelemetryTesting.clearSpans(); PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME, true).build(); + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) + .build(); Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); @@ -221,7 +223,8 @@ public void testPublishBatchingSpanFailure() { openTelemetryTesting.clearSpans(); PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME, true).build(); + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) + .build(); Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); @@ -259,7 +262,8 @@ public void testPublishRpcSpanFailure() { openTelemetryTesting.clearSpans(); PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME, true).build(); + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) + .build(); List messageWrappers = Arrays.asList(messageWrapper); Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); From 150ab74c4a141361145764483c17d80b4ef23b41 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 8 Jul 2024 20:16:34 +0000 Subject: [PATCH 19/46] test: Ignore org.assertj:assertj-core which is required for OTel testing assertions --- google-cloud-pubsub/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/google-cloud-pubsub/pom.xml b/google-cloud-pubsub/pom.xml index 05b18e0c6..6fe0a37d3 100644 --- a/google-cloud-pubsub/pom.xml +++ b/google-cloud-pubsub/pom.xml @@ -204,6 +204,7 @@ com.google.auth:google-auth-library-oauth2-http:jar io.opencensus:opencensus-impl javax.annotation:javax.annotation-api + org.assertj:assertj-core From f558305667eb0119546b6a7f1315616a69a65082 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Mon, 8 Jul 2024 20:18:53 +0000 Subject: [PATCH 20/46] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eb350d3d0..cd5149234 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ If you are using Maven without the BOM, add this to your dependencies: If you are using Gradle 5.x or later, add this to your dependencies: ```Groovy -implementation platform('com.google.cloud:libraries-bom:26.42.0') +implementation platform('com.google.cloud:libraries-bom:26.43.0') implementation 'com.google.cloud:google-cloud-pubsub' ``` From e7b05de7696851ae782bce55f54238fd37a24cb5 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Tue, 9 Jul 2024 07:15:19 +0000 Subject: [PATCH 21/46] test: Add tests for subscriber OTel functions --- google-cloud-pubsub/pom.xml | 2 - .../cloud/pubsub/v1/OpenTelemetryUtil.java | 16 +- .../cloud/pubsub/v1/PubsubMessageWrapper.java | 3 +- .../v1/StreamingSubscriberConnection.java | 27 +- .../cloud/pubsub/v1/OpenTelemetryTest.java | 452 +++++++++++++++++- 5 files changed, 475 insertions(+), 25 deletions(-) diff --git a/google-cloud-pubsub/pom.xml b/google-cloud-pubsub/pom.xml index 6fe0a37d3..4679aebc9 100644 --- a/google-cloud-pubsub/pom.xml +++ b/google-cloud-pubsub/pom.xml @@ -103,12 +103,10 @@ io.opentelemetry opentelemetry-api - 1.39.0 io.opentelemetry opentelemetry-context - 1.39.0 io.opentelemetry diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java index b99db9c2a..e6d275935 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java @@ -105,7 +105,7 @@ public static final void setPublishRpcSpanException( if (enableOpenTelemetryTracing && publishRpcSpan != null) { publishRpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on publish RPC."); publishRpcSpan.recordException(t); - endPublishRpcSpan(publishRpcSpan, enableOpenTelemetryTracing); + publishRpcSpan.end(); } } @@ -181,4 +181,18 @@ public static final void endSubscribeRpcSpan(Span rpcSpan, boolean enableOpenTel rpcSpan.end(); } } + + public static final void setSubscribeRpcSpanException( + Span rpcSpan, + boolean isModack, + int ackDeadline, + Throwable t, + boolean enableOpenTelemetryTracing) { + if (enableOpenTelemetryTracing && rpcSpan != null) { + String operation = !isModack ? "ack" : (ackDeadline == 0 ? "nack" : "modack"); + rpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on " + operation + " RPC."); + rpcSpan.recordException(t); + rpcSpan.end(); + } + } } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java index cf270351c..ed35ebb6d 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java @@ -63,7 +63,7 @@ public class PubsubMessageWrapper { private static final String NACK_START_EVENT = "nack start"; private static final String NACK_END_EVENT = "nack end"; private static final String ACK_START_EVENT = "ack start"; - private static final String ACK_END_EVENT = "ack start"; + private static final String ACK_END_EVENT = "ack end"; private static final String GOOGCLIENT_PREFIX = "googclient_"; @@ -270,6 +270,7 @@ public void startSubscriberSpan(Tracer tracer, boolean exactlyOnceDeliveryEnable null); attributesBuilder + .put(SemanticAttributes.MESSAGING_MESSAGE_ID, message.getMessageId()) .put(MESSAGE_SIZE_ATTR_KEY, message.getData().size()) .put(MESSAGE_ACK_ID_ATTR_KEY, ackId) .put(MESSAGE_EXACTLY_ONCE_ATTR_KEY, exactlyOnceDeliveryEnabled); diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java index ff8c78cff..26f2e0eec 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java @@ -560,22 +560,20 @@ private ApiFutureCallback getCallback( @Override public void onSuccess(Empty empty) { ackOperationsWaiter.incrementPendingCount(-1); + + OpenTelemetryUtil.endSubscribeRpcSpan(rpcSpan, enableOpenTelemetryTracing); + for (AckRequestData ackRequestData : ackRequestDataList) { // This will check if a response is needed, and if it has already been set ackRequestData.setResponse(AckResponse.SUCCESSFUL, setResponseOnSuccess); messageDispatcher.notifyAckSuccess(ackRequestData); // Remove from our pending operations pendingRequests.remove(ackRequestData); - if (!isModack) { + ackRequestData.getMessageWrapper().addEndRpcEvent(isModack, deadlineExtensionSeconds); + if (!isModack || deadlineExtensionSeconds == 0) { ackRequestData.getMessageWrapper().endSubscriberSpan(); - ackRequestData.getMessageWrapper().addAckEndEvent(); - } else if (deadlineExtensionSeconds == 0) { - ackRequestData.getMessageWrapper().addNackEndEvent(); - } else { - ackRequestData.getMessageWrapper().addModAckEndEvent(); } } - OpenTelemetryUtil.endSubscribeRpcSpan(rpcSpan, enableOpenTelemetryTracing); } @Override @@ -586,13 +584,16 @@ public void onFailure(Throwable t) { Level level = isAlive() ? Level.WARNING : Level.FINER; logger.log(level, "failed to send operations", t); - OpenTelemetryUtil.endSubscribeRpcSpan(rpcSpan, enableOpenTelemetryTracing); + OpenTelemetryUtil.setSubscribeRpcSpanException( + rpcSpan, isModack, deadlineExtensionSeconds, t, enableOpenTelemetryTracing); if (!getExactlyOnceDeliveryEnabled()) { if (enableOpenTelemetryTracing) { for (AckRequestData ackRequestData : ackRequestDataList) { - ackRequestData.getMessageWrapper().endSubscriberSpan(); ackRequestData.getMessageWrapper().addEndRpcEvent(isModack, deadlineExtensionSeconds); + if (!isModack || deadlineExtensionSeconds == 0) { + ackRequestData.getMessageWrapper().endSubscriberSpan(); + } } } return; @@ -620,20 +621,20 @@ public void onFailure(Throwable t) { messageDispatcher.notifyAckFailed(ackRequestData); ackRequestData .getMessageWrapper() - .setSubscriberSpanException(t, "Invalid ack ID"); + .addEndRpcEvent(isModack, deadlineExtensionSeconds); ackRequestData .getMessageWrapper() - .addEndRpcEvent(isModack, deadlineExtensionSeconds); + .setSubscriberSpanException(t, "Invalid ack ID"); } else { logger.log(Level.INFO, "Unknown error message, will not resend", errorMessage); ackRequestData.setResponse(AckResponse.OTHER, setResponseOnSuccess); messageDispatcher.notifyAckFailed(ackRequestData); ackRequestData .getMessageWrapper() - .setSubscriberSpanException(t, "Unknown error message"); + .addEndRpcEvent(isModack, deadlineExtensionSeconds); ackRequestData .getMessageWrapper() - .addEndRpcEvent(isModack, deadlineExtensionSeconds); + .setSubscriberSpanException(t, "Unknown error message"); } } else { ackRequestData.setResponse(AckResponse.SUCCESSFUL, setResponseOnSuccess); diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java index ac9ab6c80..aeb21cce4 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -21,6 +21,7 @@ import com.google.protobuf.ByteString; import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.SubscriptionName; import com.google.pubsub.v1.TopicName; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; @@ -42,9 +43,15 @@ public class OpenTelemetryTest { private static final TopicName FULL_TOPIC_NAME = TopicName.parse("projects/test-project/topics/test-topic"); + private static final SubscriptionName FULL_SUBSCRIPTION_NAME = + SubscriptionName.parse("projects/test-project/subscriptions/test-sub"); private static final String PROJECT_NAME = "test-project"; private static final String ORDERING_KEY = "abc"; private static final String MESSAGE_ID = "m0"; + private static final String ACK_ID = "def"; + private static final int DELIVERY_ATTEMPT = 1; + private static final int ACK_DEADLINE = 10; + private static final boolean EXACTLY_ONCE_ENABLED = true; private static final String PUBLISHER_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " create"; private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; @@ -53,10 +60,40 @@ public class OpenTelemetryTest { private static final String PUBLISH_START_EVENT = "publish start"; private static final String PUBLISH_END_EVENT = "publish end"; + private static final String SUBSCRIBER_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " subscribe"; + private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = + "subscriber concurrency control"; + private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; + private static final String SUBSCRIBE_PROCESS_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " process"; + private static final String SUBSCRIBE_MODACK_RPC_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " modack"; + private static final String SUBSCRIBE_ACK_RPC_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " ack"; + private static final String SUBSCRIBE_NACK_RPC_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " nack"; + + private static final String PROCESS_ACTION = "ack"; + private static final String MODACK_START_EVENT = "modack start"; + private static final String MODACK_END_EVENT = "modack end"; + private static final String NACK_START_EVENT = "nack start"; + private static final String NACK_END_EVENT = "nack end"; + private static final String ACK_START_EVENT = "ack start"; + private static final String ACK_END_EVENT = "ack end"; + private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; private static final String PROJECT_ATTR_KEY = "gcp.project_id"; private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; + private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; + private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; + private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; + private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = + "messaging.gcp_pubsub.message.exactly_once_delivery"; + private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; + private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = + "messaging.gcp_pubsub.message.delivery_attempt"; private static final String TRACEPARENT_ATTRIBUTE = "googclient_traceparent"; @@ -132,8 +169,8 @@ public void testPublishSpansSuccess() { .containsEntry(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messageWrappers.size()); // Check span data, events, links, and attributes for the publisher create span - SpanDataAssert publishSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); - publishSpanDataAssert + SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publisherSpanDataAssert .hasName(PUBLISHER_SPAN_NAME) .hasKind(SpanKind.PRODUCER) .hasNoParent() @@ -210,8 +247,8 @@ public void testPublishFlowControlSpanFailure() { .hasException(e) .hasEnded(); - SpanDataAssert publishSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); - publishSpanDataAssert + SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publisherSpanDataAssert .hasName(PUBLISHER_SPAN_NAME) .hasKind(SpanKind.PRODUCER) .hasNoParent() @@ -249,8 +286,8 @@ public void testPublishBatchingSpanFailure() { .hasException(e) .hasEnded(); - SpanDataAssert publishSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); - publishSpanDataAssert + SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publisherSpanDataAssert .hasName(PUBLISHER_SPAN_NAME) .hasKind(SpanKind.PRODUCER) .hasNoParent() @@ -292,14 +329,413 @@ public void testPublishRpcSpanFailure() { .hasException(e) .hasEnded(); - SpanDataAssert publishSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); - publishSpanDataAssert + SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publisherSpanDataAssert .hasName(PUBLISHER_SPAN_NAME) .hasKind(SpanKind.PRODUCER) .hasNoParent() .hasEnded(); } + @Test + public void testSubscribeSpansSuccess() { + openTelemetryTesting.clearSpans(); + + Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + + PubsubMessageWrapper publishMessageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) + .build(); + // Initialize the Publisher span to inject the context in the message + publishMessageWrapper.startPublisherSpan(tracer); + publishMessageWrapper.endPublisherSpan(); + + PubsubMessage publishedMessage = + publishMessageWrapper.getPubsubMessage().toBuilder().setMessageId(MESSAGE_ID).build(); + PubsubMessageWrapper subscribeMessageWrapper = + PubsubMessageWrapper.newBuilder( + publishedMessage, FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, 1, true) + .build(); + List subscribeMessageWrappers = Arrays.asList(subscribeMessageWrapper); + + long messageSize = subscribeMessageWrapper.getPubsubMessage().getData().size(); + + // Call all span start/end methods in the expected order + subscribeMessageWrapper.startSubscriberSpan(tracer, EXACTLY_ONCE_ENABLED); + subscribeMessageWrapper.startSubscribeConcurrencyControlSpan(tracer); + subscribeMessageWrapper.endSubscribeConcurrencyControlSpan(); + subscribeMessageWrapper.startSubscribeSchedulerSpan(tracer); + subscribeMessageWrapper.endSubscribeSchedulerSpan(); + subscribeMessageWrapper.startSubscribeProcessSpan(tracer); + subscribeMessageWrapper.endSubscribeProcessSpan(PROCESS_ACTION); + Span subscribeModackRpcSpan = + OpenTelemetryUtil.startSubscribeRpcSpan( + tracer, + FULL_SUBSCRIPTION_NAME.toString(), + "modack", + subscribeMessageWrappers, + ACK_DEADLINE, + true, + true); + OpenTelemetryUtil.endSubscribeRpcSpan(subscribeModackRpcSpan, true); + subscribeMessageWrapper.addEndRpcEvent(true, ACK_DEADLINE); + Span subscribeAckRpcSpan = + OpenTelemetryUtil.startSubscribeRpcSpan( + tracer, + FULL_SUBSCRIPTION_NAME.toString(), + "ack", + subscribeMessageWrappers, + 0, + false, + true); + OpenTelemetryUtil.endSubscribeRpcSpan(subscribeAckRpcSpan, true); + subscribeMessageWrapper.addEndRpcEvent(false, 0); + Span subscribeNackRpcSpan = + OpenTelemetryUtil.startSubscribeRpcSpan( + tracer, + FULL_SUBSCRIPTION_NAME.toString(), + "nack", + subscribeMessageWrappers, + 0, + false, + true); + OpenTelemetryUtil.endSubscribeRpcSpan(subscribeNackRpcSpan, true); + subscribeMessageWrapper.addEndRpcEvent(true, 0); + subscribeMessageWrapper.endSubscriberSpan(); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(8, allSpans.size()); + + SpanData publisherSpanData = allSpans.get(0); + SpanData concurrencyControlSpanData = allSpans.get(1); + SpanData schedulerSpanData = allSpans.get(2); + SpanData processSpanData = allSpans.get(3); + SpanData modackRpcSpanData = allSpans.get(4); + SpanData ackRpcSpanData = allSpans.get(5); + SpanData nackRpcSpanData = allSpans.get(6); + SpanData subscriberSpanData = allSpans.get(7); + + SpanDataAssert concurrencyControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(concurrencyControlSpanData); + concurrencyControlSpanDataAssert + .hasName(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasEnded(); + + SpanDataAssert schedulerSpanDataAssert = OpenTelemetryAssertions.assertThat(schedulerSpanData); + schedulerSpanDataAssert + .hasName(SUBSCRIBE_SCHEDULER_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasEnded(); + + SpanDataAssert processSpanDataAssert = OpenTelemetryAssertions.assertThat(processSpanData); + processSpanDataAssert + .hasName(SUBSCRIBE_PROCESS_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasEnded(); + + assertEquals(1, processSpanData.getEvents().size()); + EventDataAssert actionCalledEventAssert = + OpenTelemetryAssertions.assertThat(processSpanData.getEvents().get(0)); + actionCalledEventAssert.hasName(PROCESS_ACTION + " called"); + + // Check span data, links, and attributes for the modack RPC span + SpanDataAssert modackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(modackRpcSpanData); + modackRpcSpanDataAssert + .hasName(SUBSCRIBE_MODACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List modackRpcLinks = modackRpcSpanData.getLinks(); + assertEquals(subscribeMessageWrappers.size(), modackRpcLinks.size()); + assertEquals(subscriberSpanData.getSpanContext(), modackRpcLinks.get(0).getSpanContext()); + + assertEquals(8, modackRpcSpanData.getAttributes().size()); + AttributesAssert modackRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(modackRpcSpanData.getAttributes()); + modackRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry( + SemanticAttributes.CODE_FUNCTION, "StreamingSubscriberConnection.sendModAckOperations") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "modack") + .containsEntry( + SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()) + .containsEntry(ACK_DEADLINE_ATTR_KEY, 10) + .containsEntry(RECEIPT_MODACK_ATTR_KEY, true); + + // Check span data, links, and attributes for the ack RPC span + SpanDataAssert ackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(ackRpcSpanData); + ackRpcSpanDataAssert + .hasName(SUBSCRIBE_ACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List ackRpcLinks = ackRpcSpanData.getLinks(); + assertEquals(subscribeMessageWrappers.size(), ackRpcLinks.size()); + assertEquals(subscriberSpanData.getSpanContext(), ackRpcLinks.get(0).getSpanContext()); + + assertEquals(6, ackRpcSpanData.getAttributes().size()); + AttributesAssert ackRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(ackRpcSpanData.getAttributes()); + ackRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry( + SemanticAttributes.CODE_FUNCTION, "StreamingSubscriberConnection.sendAckOperations") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "ack") + .containsEntry( + SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); + + // Check span data, links, and attributes for the nack RPC span + SpanDataAssert nackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(nackRpcSpanData); + nackRpcSpanDataAssert + .hasName(SUBSCRIBE_NACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List nackRpcLinks = nackRpcSpanData.getLinks(); + assertEquals(subscribeMessageWrappers.size(), nackRpcLinks.size()); + assertEquals(subscriberSpanData.getSpanContext(), nackRpcLinks.get(0).getSpanContext()); + + assertEquals(6, nackRpcSpanData.getAttributes().size()); + AttributesAssert nackRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(nackRpcSpanData.getAttributes()); + nackRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry( + SemanticAttributes.CODE_FUNCTION, "StreamingSubscriberConnection.sendModAckOperations") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "nack") + .containsEntry( + SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); + + // Check span data, events, links, and attributes for the publisher create span + SpanDataAssert subscriberSpanDataAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData); + subscriberSpanDataAssert + .hasName(SUBSCRIBER_SPAN_NAME) + .hasKind(SpanKind.CONSUMER) + .hasParent(publisherSpanData) + .hasEnded(); + + assertEquals(6, subscriberSpanData.getEvents().size()); + EventDataAssert startModackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(0)); + startModackEventAssert.hasName(MODACK_START_EVENT); + EventDataAssert endModackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(1)); + endModackEventAssert.hasName(MODACK_END_EVENT); + EventDataAssert startAckEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(2)); + startAckEventAssert.hasName(ACK_START_EVENT); + EventDataAssert endAckEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(3)); + endAckEventAssert.hasName(ACK_END_EVENT); + EventDataAssert startNackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(4)); + startNackEventAssert.hasName(NACK_START_EVENT); + EventDataAssert endNackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(5)); + endNackEventAssert.hasName(NACK_END_EVENT); + + List subscriberLinks = subscriberSpanData.getLinks(); + assertEquals(3, subscriberLinks.size()); + assertEquals(modackRpcSpanData.getSpanContext(), subscriberLinks.get(0).getSpanContext()); + assertEquals(ackRpcSpanData.getSpanContext(), subscriberLinks.get(1).getSpanContext()); + assertEquals(nackRpcSpanData.getSpanContext(), subscriberLinks.get(2).getSpanContext()); + + assertEquals(11, subscriberSpanData.getAttributes().size()); + AttributesAssert subscriberSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getAttributes()); + subscriberSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "StreamingSubscriberConnection.onResponse") + .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) + .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) + .containsEntry(MESSAGE_ACK_ID_ATTR_KEY, ACK_ID) + .containsEntry(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, DELIVERY_ATTEMPT) + .containsEntry(MESSAGE_EXACTLY_ONCE_ATTR_KEY, EXACTLY_ONCE_ENABLED) + .containsEntry(MESSAGE_RESULT_ATTR_KEY, PROCESS_ACTION) + .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); + } + + @Test + public void testSubscribeConcurrencyControlSpanFailure() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder( + getPubsubMessage(), + FULL_SUBSCRIPTION_NAME.toString(), + ACK_ID, + DELIVERY_ATTEMPT, + true) + .build(); + + Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + + messageWrapper.startSubscriberSpan(tracer, EXACTLY_ONCE_ENABLED); + messageWrapper.startSubscribeConcurrencyControlSpan(tracer); + + Exception e = new Exception("test-exception"); + messageWrapper.setSubscribeConcurrencyControlSpanException(e); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(2, allSpans.size()); + SpanData concurrencyControlSpanData = allSpans.get(0); + SpanData subscriberSpanData = allSpans.get(1); + + SpanDataAssert concurrencyControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(concurrencyControlSpanData); + StatusData expectedStatus = + StatusData.create( + StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); + concurrencyControlSpanDataAssert + .hasName(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + + SpanDataAssert subscriberSpanDataAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData); + subscriberSpanDataAssert + .hasName(SUBSCRIBER_SPAN_NAME) + .hasKind(SpanKind.CONSUMER) + .hasNoParent() + .hasEnded(); + } + + @Test + public void testSubscriberSpanFailure() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder( + getPubsubMessage(), + FULL_SUBSCRIPTION_NAME.toString(), + ACK_ID, + DELIVERY_ATTEMPT, + true) + .build(); + + Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + + messageWrapper.startSubscriberSpan(tracer, EXACTLY_ONCE_ENABLED); + + Exception e = new Exception("test-exception"); + messageWrapper.setSubscriberSpanException(e, "Test exception"); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(1, allSpans.size()); + SpanData subscriberSpanData = allSpans.get(0); + + StatusData expectedStatus = StatusData.create(StatusCode.ERROR, "Test exception"); + SpanDataAssert subscriberSpanDataAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData); + subscriberSpanDataAssert + .hasName(SUBSCRIBER_SPAN_NAME) + .hasKind(SpanKind.CONSUMER) + .hasNoParent() + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + } + + @Test + public void testSubscribeRpcSpanFailures() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder( + getPubsubMessage(), + FULL_SUBSCRIPTION_NAME.toString(), + ACK_ID, + DELIVERY_ATTEMPT, + true) + .build(); + List messageWrappers = Arrays.asList(messageWrapper); + + Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + + messageWrapper.startSubscriberSpan(tracer, EXACTLY_ONCE_ENABLED); + Span subscribeModackRpcSpan = + OpenTelemetryUtil.startSubscribeRpcSpan( + tracer, + FULL_SUBSCRIPTION_NAME.toString(), + "modack", + messageWrappers, + ACK_DEADLINE, + true, + true); + Span subscribeAckRpcSpan = + OpenTelemetryUtil.startSubscribeRpcSpan( + tracer, FULL_SUBSCRIPTION_NAME.toString(), "ack", messageWrappers, 0, false, true); + Span subscribeNackRpcSpan = + OpenTelemetryUtil.startSubscribeRpcSpan( + tracer, FULL_SUBSCRIPTION_NAME.toString(), "nack", messageWrappers, 0, false, true); + + Exception e = new Exception("test-exception"); + OpenTelemetryUtil.setSubscribeRpcSpanException( + subscribeModackRpcSpan, true, ACK_DEADLINE, e, true); + OpenTelemetryUtil.setSubscribeRpcSpanException(subscribeAckRpcSpan, false, 0, e, true); + OpenTelemetryUtil.setSubscribeRpcSpanException(subscribeNackRpcSpan, true, 0, e, true); + messageWrapper.endSubscriberSpan(); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(4, allSpans.size()); + SpanData modackSpanData = allSpans.get(0); + SpanData ackSpanData = allSpans.get(1); + SpanData nackSpanData = allSpans.get(2); + SpanData subscriberSpanData = allSpans.get(3); + + StatusData expectedModackStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on modack RPC."); + SpanDataAssert modackSpanDataAssert = OpenTelemetryAssertions.assertThat(modackSpanData); + modackSpanDataAssert + .hasName(SUBSCRIBE_MODACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasStatus(expectedModackStatus) + .hasException(e) + .hasEnded(); + + StatusData expectedAckStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on ack RPC."); + SpanDataAssert ackSpanDataAssert = OpenTelemetryAssertions.assertThat(ackSpanData); + ackSpanDataAssert + .hasName(SUBSCRIBE_ACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasStatus(expectedAckStatus) + .hasException(e) + .hasEnded(); + + StatusData expectedNackStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on nack RPC."); + SpanDataAssert nackSpanDataAssert = OpenTelemetryAssertions.assertThat(nackSpanData); + nackSpanDataAssert + .hasName(SUBSCRIBE_NACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasStatus(expectedNackStatus) + .hasException(e) + .hasEnded(); + } + private PubsubMessage getPubsubMessage() { return PubsubMessage.newBuilder() .setData(ByteString.copyFromUtf8("test-data")) From 6c5e03cf1d83fb88184641c80c7d1451d52e57e9 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Thu, 29 Aug 2024 00:03:00 +0000 Subject: [PATCH 22/46] feat: Changes to OpenTelemetry implementation to add links earlier and prevent methods from being exposed to users --- google-cloud-pubsub/pom.xml | 1 - .../cloud/pubsub/v1/OpenTelemetryUtil.java | 56 +++++++++++-------- .../cloud/pubsub/v1/PubsubMessageWrapper.java | 4 +- .../cloud/pubsub/v1/OpenTelemetryTest.java | 12 ++-- 4 files changed, 40 insertions(+), 33 deletions(-) diff --git a/google-cloud-pubsub/pom.xml b/google-cloud-pubsub/pom.xml index 454076012..b40f4ddde 100644 --- a/google-cloud-pubsub/pom.xml +++ b/google-cloud-pubsub/pom.xml @@ -111,7 +111,6 @@ io.opentelemetry opentelemetry-semconv - 1.26.0-alpha diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java index e6d275935..c6760f78a 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java @@ -21,6 +21,7 @@ import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; @@ -36,7 +37,7 @@ public class OpenTelemetryUtil { private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; /** Populates attributes that are common the publisher parent span and publish RPC span. */ - public static final AttributesBuilder createCommonSpanAttributesBuilder( + protected static final AttributesBuilder createCommonSpanAttributesBuilder( String destinationName, String projectName, String codeFunction, String operation) { AttributesBuilder attributesBuilder = Attributes.builder() @@ -55,7 +56,7 @@ public static final AttributesBuilder createCommonSpanAttributesBuilder( * Creates, starts, and returns a publish RPC span for the given message batch. Bi-directional * links with the publisher parent span are created for sampled messages in the batch. */ - public static final Span startPublishRpcSpan( + protected static final Span startPublishRpcSpan( Tracer tracer, String topic, List messages, @@ -64,21 +65,24 @@ public static final Span startPublishRpcSpan( TopicName topicName = TopicName.parse(topic); Attributes attributes = createCommonSpanAttributesBuilder( - topicName.getTopic(), topicName.getProject(), "Publisher.publishCall", "publish") + topicName.getTopic(), topicName.getProject(), "publishCall", "publish") .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()) .build(); - Span publishRpcSpan = + SpanBuilder publishRpcSpanBuilder = tracer .spanBuilder(topicName.getTopic() + PUBLISH_RPC_SPAN_SUFFIX) .setSpanKind(SpanKind.CLIENT) - .setAllAttributes(attributes) - .startSpan(); + .setAllAttributes(attributes); + Attributes linkAttributes = + Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, "publish").build(); + for (PubsubMessageWrapper message : messages) { + if (message.getPublisherSpan().getSpanContext().isSampled()) + publishRpcSpanBuilder.addLink(message.getPublisherSpan().getSpanContext(), linkAttributes); + } + Span publishRpcSpan = publishRpcSpanBuilder.startSpan(); for (PubsubMessageWrapper message : messages) { if (message.getPublisherSpan().getSpanContext().isSampled()) { - Attributes linkAttributes = - Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, "publish").build(); - publishRpcSpan.addLink(message.getPublisherSpan().getSpanContext(), linkAttributes); message.getPublisherSpan().addLink(publishRpcSpan.getSpanContext(), linkAttributes); message.addPublishStartEvent(); } @@ -89,7 +93,7 @@ public static final Span startPublishRpcSpan( } /** Ends the given publish RPC span if it exists. */ - public static final void endPublishRpcSpan( + protected static final void endPublishRpcSpan( Span publishRpcSpan, boolean enableOpenTelemetryTracing) { if (enableOpenTelemetryTracing && publishRpcSpan != null) { publishRpcSpan.end(); @@ -100,7 +104,7 @@ public static final void endPublishRpcSpan( * Sets an error status and records an exception when an exception is thrown when publishing the * message batch. */ - public static final void setPublishRpcSpanException( + protected static final void setPublishRpcSpanException( Span publishRpcSpan, Throwable t, boolean enableOpenTelemetryTracing) { if (enableOpenTelemetryTracing && publishRpcSpan != null) { publishRpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on publish RPC."); @@ -113,7 +117,7 @@ public static final void setPublishRpcSpanException( * Creates, starts, and returns spans for ModAck, Nack, and Ack RPC requests. Bi-directional links * to parent subscribe span for sampled messages are added. */ - public static final Span startSubscribeRpcSpan( + protected static final Span startSubscribeRpcSpan( Tracer tracer, String subscription, String rpcOperation, @@ -124,8 +128,8 @@ public static final Span startSubscribeRpcSpan( if (enableOpenTelemetryTracing && tracer != null) { String codeFunction = rpcOperation == "ack" - ? "StreamingSubscriberConnection.sendAckOperations" - : "StreamingSubscriberConnection.sendModAckOperations"; + ? "sendAckOperations" + : "sendModAckOperations"; SubscriptionName subscriptionName = SubscriptionName.parse(subscription); AttributesBuilder attributesBuilder = createCommonSpanAttributesBuilder( @@ -142,20 +146,24 @@ public static final Span startSubscribeRpcSpan( .put(RECEIPT_MODACK_ATTR_KEY, isReceiptModack); } - Span rpcSpan = + SpanBuilder rpcSpanBuilder = tracer .spanBuilder(subscriptionName.getSubscription() + " " + rpcOperation) .setSpanKind(SpanKind.CLIENT) - .setAllAttributes(attributesBuilder.build()) - .startSpan(); + .setAllAttributes(attributesBuilder.build()); + Attributes linkAttributes = + Attributes.builder() + .put(SemanticAttributes.MESSAGING_OPERATION, rpcOperation) + .build(); + for (PubsubMessageWrapper message : messages) { + if (message.getSubscriberSpan().getSpanContext().isSampled()) { + rpcSpanBuilder.addLink(message.getSubscriberSpan().getSpanContext(), linkAttributes); + } + } + Span rpcSpan = rpcSpanBuilder.startSpan(); for (PubsubMessageWrapper message : messages) { if (message.getSubscriberSpan().getSpanContext().isSampled()) { - Attributes linkAttributes = - Attributes.builder() - .put(SemanticAttributes.MESSAGING_OPERATION, rpcOperation) - .build(); - rpcSpan.addLink(message.getSubscriberSpan().getSpanContext(), linkAttributes); message.getSubscriberSpan().addLink(rpcSpan.getSpanContext(), linkAttributes); switch (rpcOperation) { case "ack": @@ -176,13 +184,13 @@ public static final Span startSubscribeRpcSpan( } /** Ends the given subscribe RPC span if it exists. */ - public static final void endSubscribeRpcSpan(Span rpcSpan, boolean enableOpenTelemetryTracing) { + protected static final void endSubscribeRpcSpan(Span rpcSpan, boolean enableOpenTelemetryTracing) { if (enableOpenTelemetryTracing && rpcSpan != null) { rpcSpan.end(); } } - public static final void setSubscribeRpcSpanException( + protected static final void setSubscribeRpcSpanException( Span rpcSpan, boolean isModack, int ackDeadline, diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java index ed35ebb6d..4fa899ad3 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java @@ -150,7 +150,7 @@ public void startPublisherSpan(Tracer tracer) { if (enableOpenTelemetryTracing && tracer != null) { AttributesBuilder attributesBuilder = OpenTelemetryUtil.createCommonSpanAttributesBuilder( - topicName.getTopic(), topicName.getProject(), "Publisher.publish", "create"); + topicName.getTopic(), topicName.getProject(), "publish", "create"); attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getData().size()); if (!message.getOrderingKey().isEmpty()) { @@ -266,7 +266,7 @@ public void startSubscriberSpan(Tracer tracer, boolean exactlyOnceDeliveryEnable OpenTelemetryUtil.createCommonSpanAttributesBuilder( subscriptionName.getSubscription(), subscriptionName.getProject(), - "StreamingSubscriberConnection.onResponse", + "onResponse", null); attributesBuilder diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java index aeb21cce4..9106f69c4 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -164,7 +164,7 @@ public void testPublishSpansSuccess() { .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "Publisher.publishCall") + .containsEntry(SemanticAttributes.CODE_FUNCTION, "publishCall") .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "publish") .containsEntry(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messageWrappers.size()); @@ -195,7 +195,7 @@ public void testPublishSpansSuccess() { .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "Publisher.publish") + .containsEntry(SemanticAttributes.CODE_FUNCTION, "publish") .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "create") .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) @@ -460,7 +460,7 @@ public void testSubscribeSpansSuccess() { SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) .containsEntry( - SemanticAttributes.CODE_FUNCTION, "StreamingSubscriberConnection.sendModAckOperations") + SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "modack") .containsEntry( SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()) @@ -488,7 +488,7 @@ public void testSubscribeSpansSuccess() { SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) .containsEntry( - SemanticAttributes.CODE_FUNCTION, "StreamingSubscriberConnection.sendAckOperations") + SemanticAttributes.CODE_FUNCTION, "sendAckOperations") .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "ack") .containsEntry( SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); @@ -514,7 +514,7 @@ public void testSubscribeSpansSuccess() { SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) .containsEntry( - SemanticAttributes.CODE_FUNCTION, "StreamingSubscriberConnection.sendModAckOperations") + SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "nack") .containsEntry( SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); @@ -562,7 +562,7 @@ public void testSubscribeSpansSuccess() { .containsEntry( SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "StreamingSubscriberConnection.onResponse") + .containsEntry(SemanticAttributes.CODE_FUNCTION, "onResponse") .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) .containsEntry(MESSAGE_ACK_ID_ATTR_KEY, ACK_ID) From d257b6bb59ecbc607b1333580d615cff79898c85 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Thu, 12 Sep 2024 06:55:50 +0000 Subject: [PATCH 23/46] feat: Refactor OpenTelemetry implementation to use a context aware wrapper for the tracer and a PubsubTracer interface --- .../cloud/pubsub/v1/AckRequestData.java | 2 +- .../cloud/pubsub/v1/MessageDispatcher.java | 33 +- .../pubsub/v1/OpenTelemetryPubsubTracer.java | 391 ++++++++++++++++++ .../cloud/pubsub/v1/OpenTelemetryUtil.java | 164 -------- .../com/google/cloud/pubsub/v1/Publisher.java | 39 +- .../cloud/pubsub/v1/PubsubMessageWrapper.java | 368 ++++++----------- .../google/cloud/pubsub/v1/PubsubTracer.java | 137 ++++++ .../v1/StreamingSubscriberConnection.java | 56 ++- .../google/cloud/pubsub/v1/Subscriber.java | 9 +- .../cloud/pubsub/v1/OpenTelemetryTest.java | 232 ++++------- 10 files changed, 799 insertions(+), 632 deletions(-) create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubTracer.java diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/AckRequestData.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/AckRequestData.java index 64bce6cc2..5cab83f49 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/AckRequestData.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/AckRequestData.java @@ -44,7 +44,7 @@ public SettableApiFuture getMessageFutureIfExists() { */ public PubsubMessageWrapper getMessageWrapper() { if (this.messageWrapper == null) { - return PubsubMessageWrapper.newBuilder(null, null, false).build(); + return PubsubMessageWrapper.newBuilder(null, null).build(); } return messageWrapper; } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java index 26d1f253d..3ebb99733 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java @@ -28,7 +28,6 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.pubsub.v1.PubsubMessage; import com.google.pubsub.v1.ReceivedMessage; -import io.opentelemetry.api.trace.Tracer; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -107,7 +106,7 @@ class MessageDispatcher { private final String subscriptionName; private final boolean enableOpenTelemetryTracing; - private final Tracer tracer; + private final PubsubTracer tracer; /** Internal representation of a reply to a Pubsub message, to be sent back to the service. */ public enum AckReply { @@ -162,7 +161,7 @@ public void onFailure(Throwable t) { t); this.ackRequestData.setResponse(AckResponse.OTHER, false); pendingNacks.add(this.ackRequestData); - this.ackRequestData.getMessageWrapper().endSubscribeProcessSpan("nack"); + tracer.endSubscribeProcessSpan(this.ackRequestData.getMessageWrapper(), "nack"); forget(); } @@ -175,11 +174,11 @@ public void onSuccess(AckReply reply) { ackLatencyDistribution.record( Ints.saturatedCast( (long) Math.ceil((clock.millisTime() - receivedTimeMillis) / 1000D))); - this.ackRequestData.getMessageWrapper().endSubscribeProcessSpan("ack"); + tracer.endSubscribeProcessSpan(this.ackRequestData.getMessageWrapper(), "ack"); break; case NACK: pendingNacks.add(this.ackRequestData); - this.ackRequestData.getMessageWrapper().endSubscribeProcessSpan("nack"); + tracer.endSubscribeProcessSpan(this.ackRequestData.getMessageWrapper(), "nack"); break; default: throw new IllegalArgumentException(String.format("AckReply: %s not supported", reply)); @@ -409,11 +408,10 @@ void processReceivedMessages(List messages) { message.getMessage(), subscriptionName, message.getAckId(), - message.getDeliveryAttempt(), - enableOpenTelemetryTracing) + message.getDeliveryAttempt()) .build(); builder.setMessageWrapper(messageWrapper); - messageWrapper.startSubscriberSpan(tracer, this.exactlyOnceDeliveryEnabled.get()); + tracer.startSubscriberSpan(messageWrapper, this.exactlyOnceDeliveryEnabled.get()); AckRequestData ackRequestData = builder.build(); AckHandler ackHandler = @@ -482,13 +480,14 @@ private void processBatch(List batch) { for (OutstandingMessage message : batch) { // This is a blocking flow controller. We have already incremented messagesWaiter, so // shutdown will block on processing of all these messages anyway. - message.messageWrapper().startSubscribeConcurrencyControlSpan(tracer); + tracer.startSubscribeConcurrencyControlSpan(message.messageWrapper()); try { flowController.reserve(1, message.messageWrapper().getPubsubMessage().getSerializedSize()); - message.messageWrapper().endSubscribeConcurrencyControlSpan(); + tracer.endSubscribeConcurrencyControlSpan(message.messageWrapper()); } catch (FlowControlException unexpectedException) { // This should be a blocking flow controller and never throw an exception. - message.messageWrapper().setSubscribeConcurrencyControlSpanException(unexpectedException); + tracer.setSubscribeConcurrencyControlSpanException( + message.messageWrapper(), unexpectedException); throw new IllegalStateException("Flow control unexpected exception", unexpectedException); } addDeliveryInfoCount(message.messageWrapper()); @@ -533,10 +532,10 @@ public void run() { // so it was probably sent to someone else. Don't work on it. // Don't nack it either, because we'd be nacking someone else's message. ackHandler.forget(); - messageWrapper.setSubscriberSpanExpirationResult(); + tracer.setSubscriberSpanExpirationResult(messageWrapper); return; } - messageWrapper.startSubscribeProcessSpan(tracer); + tracer.startSubscribeProcessSpan(messageWrapper); if (shouldSetMessageFuture()) { // This is the message future that is propagated to the user SettableApiFuture messageFuture = @@ -557,9 +556,9 @@ public void run() { if (!messageOrderingEnabled.get() || message.getOrderingKey().isEmpty()) { executor.execute(deliverMessageTask); } else { - messageWrapper.startSubscribeSchedulerSpan(tracer); + tracer.startSubscribeSchedulerSpan(messageWrapper); sequentialExecutor.submit(message.getOrderingKey(), deliverMessageTask); - messageWrapper.endSubscribeSchedulerSpan(); + tracer.endSubscribeSchedulerSpan(messageWrapper); } } @@ -687,7 +686,7 @@ public static final class Builder { private String subscriptionName; private boolean enableOpenTelemetryTracing; - private Tracer tracer; + private PubsubTracer tracer; protected Builder(MessageReceiver receiver) { this.receiver = receiver; @@ -769,7 +768,7 @@ public Builder setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing) return this; } - public Builder setTracer(Tracer tracer) { + public Builder setTracer(PubsubTracer tracer) { this.tracer = tracer; return this; } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java new file mode 100644 index 000000000..e8ca33a6f --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java @@ -0,0 +1,391 @@ +/* + * 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.pubsub.v1; + +import com.google.api.core.InternalApi; +import com.google.common.base.Preconditions; +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.SubscriptionName; +import com.google.pubsub.v1.TopicName; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.List; + +@InternalApi("For use by the google-cloud-pubsub library only") +public class OpenTelemetryPubsubTracer implements PubsubTracer { + private final Tracer tracer; + + private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; + private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; + private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = + "subscriber concurrency control"; + private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; + + private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; + private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; + private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; + private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = + "messaging.gcp_pubsub.message.exactly_once_delivery"; + private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = + "messaging.gcp_pubsub.message.delivery_attempt"; + private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; + private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; + private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; + + private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; + + OpenTelemetryPubsubTracer(Tracer tracer) { + this.tracer = Preconditions.checkNotNull(tracer, "OpenTelemetry tracer cannot be null"); + } + + private Span startChildSpan(String name, Span parent) { + return tracer.spanBuilder(name).setParent(Context.current().with(parent)).startSpan(); + } + + /** + * Creates and starts the parent span with the appropriate span attributes and injects the span + * context into the {@link PubsubMessage} attributes. + */ + @Override + public void startPublisherSpan(PubsubMessageWrapper message) { + AttributesBuilder attributesBuilder = + OpenTelemetryUtil.createCommonSpanAttributesBuilder( + message.getTopicName(), message.getTopicProject(), "publish", "create"); + + attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()); + if (!message.getOrderingKey().isEmpty()) { + attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); + } + + Span publisherSpan = + tracer + .spanBuilder(message.getTopicName() + " create") + .setSpanKind(SpanKind.PRODUCER) + .setAllAttributes(attributesBuilder.build()) + .startSpan(); + + message.setPublisherSpan(publisherSpan); + if (publisherSpan.getSpanContext().isValid()) { + message.injectSpanContext(); + } + } + + public void endPublisherSpan(PubsubMessageWrapper message) { + message.endPublisherSpan(); + } + + public void setPublisherMessageIdSpanAttribute(PubsubMessageWrapper message, String messageId) { + message.setPublisherMessageIdSpanAttribute(messageId); + } + + /** Creates a span for publish-side flow control as a child of the parent publisher span. */ + @Override + public void startPublishFlowControlSpan(PubsubMessageWrapper message) { + Span publisherSpan = message.getPublisherSpan(); + if (publisherSpan != null) + message.setPublishFlowControlSpan( + startChildSpan(PUBLISH_FLOW_CONTROL_SPAN_NAME, publisherSpan)); + } + + @Override + public void endPublishFlowControlSpan(PubsubMessageWrapper message) { + message.endPublishFlowControlSpan(); + } + + @Override + public void setPublishFlowControlSpanException(PubsubMessageWrapper message, Throwable t) { + message.setPublishFlowControlSpanException(t); + } + + /** Creates a span for publish message batching as a child of the parent publisher span. */ + @Override + public void startPublishBatchingSpan(PubsubMessageWrapper message) { + Span publisherSpan = message.getPublisherSpan(); + if (publisherSpan != null) { + message.setPublishBatchingSpan(startChildSpan(PUBLISH_BATCHING_SPAN_NAME, publisherSpan)); + } + } + + @Override + public void endPublishBatchingSpan(PubsubMessageWrapper message) { + message.endPublishBatchingSpan(); + } + + /** + * Creates, starts, and returns a publish RPC span for the given message batch. Bi-directional + * links with the publisher parent span are created for sampled messages in the batch. + */ + @Override + public Span startPublishRpcSpan(String topic, List messages) { + TopicName topicName = TopicName.parse(topic); + Attributes attributes = + OpenTelemetryUtil.createCommonSpanAttributesBuilder( + topicName.getTopic(), topicName.getProject(), "publishCall", "publish") + .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()) + .build(); + SpanBuilder publishRpcSpanBuilder = + tracer + .spanBuilder(topicName.getTopic() + PUBLISH_RPC_SPAN_SUFFIX) + .setSpanKind(SpanKind.CLIENT) + .setAllAttributes(attributes); + Attributes linkAttributes = + Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, "publish").build(); + for (PubsubMessageWrapper message : messages) { + if (message.getPublisherSpan().getSpanContext().isSampled()) + publishRpcSpanBuilder.addLink(message.getPublisherSpan().getSpanContext(), linkAttributes); + } + Span publishRpcSpan = publishRpcSpanBuilder.startSpan(); + + for (PubsubMessageWrapper message : messages) { + if (message.getPublisherSpan().getSpanContext().isSampled()) { + message.getPublisherSpan().addLink(publishRpcSpan.getSpanContext(), linkAttributes); + message.addPublishStartEvent(); + } + } + return publishRpcSpan; + } + + /** Ends the given publish RPC span if it exists. */ + @Override + public void endPublishRpcSpan(Span publishRpcSpan) { + if (publishRpcSpan != null) { + publishRpcSpan.end(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown when publishing the + * message batch. + */ + @Override + public void setPublishRpcSpanException(Span publishRpcSpan, Throwable t) { + if (publishRpcSpan != null) { + publishRpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on publish RPC."); + publishRpcSpan.recordException(t); + publishRpcSpan.end(); + } + } + + @Override + public void startSubscriberSpan( + PubsubMessageWrapper message, boolean exactlyOnceDeliveryEnabled) { + AttributesBuilder attributesBuilder = + OpenTelemetryUtil.createCommonSpanAttributesBuilder( + message.getSubscriptionName(), message.getSubscriptionProject(), "onResponse", null); + + attributesBuilder + .put(SemanticAttributes.MESSAGING_MESSAGE_ID, message.getMessageId()) + .put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()) + .put(MESSAGE_ACK_ID_ATTR_KEY, message.getAckId()) + .put(MESSAGE_EXACTLY_ONCE_ATTR_KEY, exactlyOnceDeliveryEnabled); + if (!message.getOrderingKey().isEmpty()) { + attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); + } + if (message.getDeliveryAttempt() > 0) { + attributesBuilder.put(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, message.getDeliveryAttempt()); + } + Attributes attributes = attributesBuilder.build(); + Context publisherSpanContext = message.extractSpanContext(attributes); + message.setPublisherSpan(Span.fromContextOrNull(publisherSpanContext)); + message.setSubscriberSpan( + tracer + .spanBuilder(message.getSubscriptionName() + " subscribe") + .setSpanKind(SpanKind.CONSUMER) + .setParent(publisherSpanContext) + .setAllAttributes(attributes) + .startSpan()); + } + + @Override + public void endSubscriberSpan(PubsubMessageWrapper message) { + message.endSubscriberSpan(); + } + + @Override + public void setSubscriberSpanExpirationResult(PubsubMessageWrapper message) { + message.setSubscriberSpanExpirationResult(); + } + + @Override + public void setSubscriberSpanException( + PubsubMessageWrapper message, Throwable t, String exception) { + message.setSubscriberSpanException(t, exception); + } + + /** Creates a span for subscribe concurrency control as a child of the parent subscriber span. */ + @Override + public void startSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { + Span subscriberSpan = message.getSubscriberSpan(); + if (subscriberSpan != null) { + message.setSubscribeConcurrencyControlSpan( + startChildSpan(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME, subscriberSpan)); + } + } + + @Override + public void endSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { + message.endSubscribeConcurrencyControlSpan(); + } + + @Override + public void setSubscribeConcurrencyControlSpanException( + PubsubMessageWrapper message, Throwable t) { + message.setSubscribeConcurrencyControlSpanException(t); + } + + /** + * Creates a span for subscribe ordering key scheduling as a child of the parent subscriber span. + */ + @Override + public void startSubscribeSchedulerSpan(PubsubMessageWrapper message) { + Span subscriberSpan = message.getSubscriberSpan(); + if (subscriberSpan != null) { + message.setSubscribeSchedulerSpan( + startChildSpan(SUBSCRIBE_SCHEDULER_SPAN_NAME, subscriberSpan)); + } + } + + @Override + public void endSubscribeSchedulerSpan(PubsubMessageWrapper message) { + message.endSubscribeSchedulerSpan(); + } + + /** Creates a span for subscribe message processing as a child of the parent subscriber span. */ + @Override + public void startSubscribeProcessSpan(PubsubMessageWrapper message) { + Span subscriberSpan = message.getSubscriberSpan(); + if (subscriberSpan != null) { + Span subscribeProcessSpan = + startChildSpan(message.getSubscriptionName() + " process", subscriberSpan); + subscribeProcessSpan.setAttribute( + SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE); + Span publisherSpan = message.getPublisherSpan(); + if (publisherSpan != null) { + subscribeProcessSpan.addLink(publisherSpan.getSpanContext()); + } + message.setSubscribeProcessSpan(subscribeProcessSpan); + } + } + + @Override + public void endSubscribeProcessSpan(PubsubMessageWrapper message, String action) { + message.endSubscribeProcessSpan(action); + } + + /** + * Creates, starts, and returns spans for ModAck, Nack, and Ack RPC requests. Bi-directional links + * to parent subscribe span for sampled messages are added. + */ + @Override + public Span startSubscribeRpcSpan( + String subscription, + String rpcOperation, + List messages, + int ackDeadline, + boolean isReceiptModack) { + String codeFunction = rpcOperation == "ack" ? "sendAckOperations" : "sendModAckOperations"; + SubscriptionName subscriptionName = SubscriptionName.parse(subscription); + AttributesBuilder attributesBuilder = + OpenTelemetryUtil.createCommonSpanAttributesBuilder( + subscriptionName.getSubscription(), + subscriptionName.getProject(), + codeFunction, + rpcOperation) + .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()); + + // Ack deadline and receipt modack are specific to the modack operation + if (rpcOperation == "modack") { + attributesBuilder + .put(ACK_DEADLINE_ATTR_KEY, ackDeadline) + .put(RECEIPT_MODACK_ATTR_KEY, isReceiptModack); + } + + SpanBuilder rpcSpanBuilder = + tracer + .spanBuilder(subscriptionName.getSubscription() + " " + rpcOperation) + .setSpanKind(SpanKind.CLIENT) + .setAllAttributes(attributesBuilder.build()); + Attributes linkAttributes = + Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, rpcOperation).build(); + for (PubsubMessageWrapper message : messages) { + if (message.getSubscriberSpan().getSpanContext().isSampled()) { + rpcSpanBuilder.addLink(message.getSubscriberSpan().getSpanContext(), linkAttributes); + } + } + Span rpcSpan = rpcSpanBuilder.startSpan(); + + for (PubsubMessageWrapper message : messages) { + if (message.getSubscriberSpan().getSpanContext().isSampled()) { + message.getSubscriberSpan().addLink(rpcSpan.getSpanContext(), linkAttributes); + switch (rpcOperation) { + case "ack": + message.addAckStartEvent(); + break; + case "modack": + message.addModAckStartEvent(); + break; + case "nack": + message.addNackStartEvent(); + break; + } + } + } + return rpcSpan; + } + + /** Ends the given subscribe RPC span if it exists. */ + @Override + public void endSubscribeRpcSpan(Span rpcSpan) { + if (rpcSpan != null) { + rpcSpan.end(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown when handling a + * subscribe-side RPC. + */ + @Override + public void setSubscribeRpcSpanException( + Span rpcSpan, boolean isModack, int ackDeadline, Throwable t) { + if (rpcSpan != null) { + String operation = !isModack ? "ack" : (ackDeadline == 0 ? "nack" : "modack"); + rpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on " + operation + " RPC."); + rpcSpan.recordException(t); + rpcSpan.end(); + } + } + + /** Adds the appropriate subscribe-side RPC end event. */ + @Override + public void addEndRpcEvent(PubsubMessageWrapper message, boolean isModack, int ackDeadline) { + if (!isModack) { + message.addAckEndEvent(); + } else if (ackDeadline == 0) { + message.addNackEndEvent(); + } else { + message.addModAckEndEvent(); + } + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java index c6760f78a..c100675b5 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java @@ -16,25 +16,13 @@ package com.google.cloud.pubsub.v1; -import com.google.pubsub.v1.SubscriptionName; -import com.google.pubsub.v1.TopicName; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanBuilder; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.util.List; public class OpenTelemetryUtil { private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; private static final String PROJECT_ATTR_KEY = "gcp.project_id"; - private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; - private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; - - private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; /** Populates attributes that are common the publisher parent span and publish RPC span. */ protected static final AttributesBuilder createCommonSpanAttributesBuilder( @@ -51,156 +39,4 @@ protected static final AttributesBuilder createCommonSpanAttributesBuilder( return attributesBuilder; } - - /** - * Creates, starts, and returns a publish RPC span for the given message batch. Bi-directional - * links with the publisher parent span are created for sampled messages in the batch. - */ - protected static final Span startPublishRpcSpan( - Tracer tracer, - String topic, - List messages, - boolean enableOpenTelemetryTracing) { - if (enableOpenTelemetryTracing && tracer != null) { - TopicName topicName = TopicName.parse(topic); - Attributes attributes = - createCommonSpanAttributesBuilder( - topicName.getTopic(), topicName.getProject(), "publishCall", "publish") - .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()) - .build(); - SpanBuilder publishRpcSpanBuilder = - tracer - .spanBuilder(topicName.getTopic() + PUBLISH_RPC_SPAN_SUFFIX) - .setSpanKind(SpanKind.CLIENT) - .setAllAttributes(attributes); - Attributes linkAttributes = - Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, "publish").build(); - for (PubsubMessageWrapper message : messages) { - if (message.getPublisherSpan().getSpanContext().isSampled()) - publishRpcSpanBuilder.addLink(message.getPublisherSpan().getSpanContext(), linkAttributes); - } - Span publishRpcSpan = publishRpcSpanBuilder.startSpan(); - - for (PubsubMessageWrapper message : messages) { - if (message.getPublisherSpan().getSpanContext().isSampled()) { - message.getPublisherSpan().addLink(publishRpcSpan.getSpanContext(), linkAttributes); - message.addPublishStartEvent(); - } - } - return publishRpcSpan; - } - return null; - } - - /** Ends the given publish RPC span if it exists. */ - protected static final void endPublishRpcSpan( - Span publishRpcSpan, boolean enableOpenTelemetryTracing) { - if (enableOpenTelemetryTracing && publishRpcSpan != null) { - publishRpcSpan.end(); - } - } - - /** - * Sets an error status and records an exception when an exception is thrown when publishing the - * message batch. - */ - protected static final void setPublishRpcSpanException( - Span publishRpcSpan, Throwable t, boolean enableOpenTelemetryTracing) { - if (enableOpenTelemetryTracing && publishRpcSpan != null) { - publishRpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on publish RPC."); - publishRpcSpan.recordException(t); - publishRpcSpan.end(); - } - } - - /** - * Creates, starts, and returns spans for ModAck, Nack, and Ack RPC requests. Bi-directional links - * to parent subscribe span for sampled messages are added. - */ - protected static final Span startSubscribeRpcSpan( - Tracer tracer, - String subscription, - String rpcOperation, - List messages, - int ackDeadline, - boolean isReceiptModack, - boolean enableOpenTelemetryTracing) { - if (enableOpenTelemetryTracing && tracer != null) { - String codeFunction = - rpcOperation == "ack" - ? "sendAckOperations" - : "sendModAckOperations"; - SubscriptionName subscriptionName = SubscriptionName.parse(subscription); - AttributesBuilder attributesBuilder = - createCommonSpanAttributesBuilder( - subscriptionName.getSubscription(), - subscriptionName.getProject(), - codeFunction, - rpcOperation) - .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()); - - // Ack deadline and receipt modack are specific to the modack operation - if (rpcOperation == "modack") { - attributesBuilder - .put(ACK_DEADLINE_ATTR_KEY, ackDeadline) - .put(RECEIPT_MODACK_ATTR_KEY, isReceiptModack); - } - - SpanBuilder rpcSpanBuilder = - tracer - .spanBuilder(subscriptionName.getSubscription() + " " + rpcOperation) - .setSpanKind(SpanKind.CLIENT) - .setAllAttributes(attributesBuilder.build()); - Attributes linkAttributes = - Attributes.builder() - .put(SemanticAttributes.MESSAGING_OPERATION, rpcOperation) - .build(); - for (PubsubMessageWrapper message : messages) { - if (message.getSubscriberSpan().getSpanContext().isSampled()) { - rpcSpanBuilder.addLink(message.getSubscriberSpan().getSpanContext(), linkAttributes); - } - } - Span rpcSpan = rpcSpanBuilder.startSpan(); - - for (PubsubMessageWrapper message : messages) { - if (message.getSubscriberSpan().getSpanContext().isSampled()) { - message.getSubscriberSpan().addLink(rpcSpan.getSpanContext(), linkAttributes); - switch (rpcOperation) { - case "ack": - message.addAckStartEvent(); - break; - case "modack": - message.addModAckStartEvent(); - break; - case "nack": - message.addNackStartEvent(); - break; - } - } - } - return rpcSpan; - } - return null; - } - - /** Ends the given subscribe RPC span if it exists. */ - protected static final void endSubscribeRpcSpan(Span rpcSpan, boolean enableOpenTelemetryTracing) { - if (enableOpenTelemetryTracing && rpcSpan != null) { - rpcSpan.end(); - } - } - - protected static final void setSubscribeRpcSpanException( - Span rpcSpan, - boolean isModack, - int ackDeadline, - Throwable t, - boolean enableOpenTelemetryTracing) { - if (enableOpenTelemetryTracing && rpcSpan != null) { - String operation = !isModack ? "ack" : (ackDeadline == 0 ? "nack" : "modack"); - rpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on " + operation + " RPC."); - rpcSpan.recordException(t); - rpcSpan.end(); - } - } } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java index 451bf6e89..0aed2428d 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java @@ -131,7 +131,7 @@ public class Publisher implements PublisherInterface { private final boolean enableOpenTelemetryTracing; private final OpenTelemetry openTelemetry; - private Tracer tracer = null; + private PubsubTracer tracer = null; /** The maximum number of messages in one request. Defined by the API. */ public static long getApiMaxRequestElementCount() { @@ -163,8 +163,11 @@ private Publisher(Builder builder) throws IOException { this.compressionBytesThreshold = builder.compressionBytesThreshold; this.enableOpenTelemetryTracing = builder.enableOpenTelemetryTracing; this.openTelemetry = builder.openTelemetry; - if (this.openTelemetry != null) { - this.tracer = builder.openTelemetry.getTracer(OPEN_TELEMETRY_TRACER_NAME); + if (this.openTelemetry != null && this.enableOpenTelemetryTracing) { + Tracer openTelemetryTracer = builder.openTelemetry.getTracer(OPEN_TELEMETRY_TRACER_NAME); + if (openTelemetryTracer != null) { + this.tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer); + } } messagesBatches = new HashMap<>(); @@ -274,24 +277,22 @@ public ApiFuture publish(PubsubMessage message) { + "setEnableMessageOrdering(true) in the builder."); PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder( - messageTransform.apply(message), topicName, enableOpenTelemetryTracing) - .build(); - messageWrapper.startPublisherSpan(tracer); + PubsubMessageWrapper.newBuilder(messageTransform.apply(message), topicName).build(); + tracer.startPublisherSpan(messageWrapper); final OutstandingPublish outstandingPublish = new OutstandingPublish(messageWrapper); if (flowController != null) { - outstandingPublish.messageWrapper.startPublishFlowControlSpan(tracer); + tracer.startPublishFlowControlSpan(messageWrapper); try { flowController.acquire(outstandingPublish.messageSize); - outstandingPublish.messageWrapper.endPublishFlowControlSpan(); + tracer.endPublishFlowControlSpan(messageWrapper); } catch (FlowController.FlowControlException e) { if (!orderingKey.isEmpty()) { sequentialExecutor.stopPublish(orderingKey); } outstandingPublish.publishResult.setException(e); - outstandingPublish.messageWrapper.setPublishFlowControlSpanException(e); + tracer.setPublishFlowControlSpanException(messageWrapper, e); return outstandingPublish.publishResult; } } @@ -299,7 +300,7 @@ public ApiFuture publish(PubsubMessage message) { List batchesToSend; messagesBatchLock.lock(); try { - outstandingPublish.messageWrapper.startPublishBatchingSpan(tracer); + tracer.startPublishBatchingSpan(messageWrapper); if (!orderingKey.isEmpty() && sequentialExecutor.keyHasError(orderingKey)) { outstandingPublish.publishResult.setException( SequentialExecutorService.CallbackExecutor.CANCELLATION_EXCEPTION); @@ -480,13 +481,11 @@ private ApiFuture publishCall(OutstandingBatch outstandingBatch List pubsubMessagesList = new ArrayList(numMessagesInBatch); List messageWrappers = outstandingBatch.getMessageWrappers(); for (PubsubMessageWrapper messageWrapper : messageWrappers) { - messageWrapper.endPublishBatchingSpan(); + tracer.endPublishBatchingSpan(messageWrapper); pubsubMessagesList.add(messageWrapper.getPubsubMessage()); } - outstandingBatch.publishRpcSpan = - OpenTelemetryUtil.startPublishRpcSpan( - tracer, topicName, messageWrappers, enableOpenTelemetryTracing); + outstandingBatch.publishRpcSpan = tracer.startPublishRpcSpan(topicName, messageWrappers); return publisherStub .publishCallable() @@ -601,19 +600,19 @@ private List getMessageWrappers() { } private void onFailure(Throwable t) { - OpenTelemetryUtil.setPublishRpcSpanException(publishRpcSpan, t, enableOpenTelemetryTracing); + tracer.setPublishRpcSpanException(publishRpcSpan, t); for (OutstandingPublish outstandingPublish : outstandingPublishes) { if (flowController != null) { flowController.release(outstandingPublish.messageSize); } outstandingPublish.publishResult.setException(t); - outstandingPublish.messageWrapper.endPublisherSpan(); + tracer.endPublisherSpan(outstandingPublish.messageWrapper); } } private void onSuccess(Iterable results) { - OpenTelemetryUtil.endPublishRpcSpan(publishRpcSpan, enableOpenTelemetryTracing); + tracer.endPublishRpcSpan(publishRpcSpan); Iterator messagesResultsIt = outstandingPublishes.iterator(); for (String messageId : results) { @@ -622,8 +621,8 @@ private void onSuccess(Iterable results) { flowController.release(nextPublish.messageSize); } nextPublish.publishResult.set(messageId); - nextPublish.messageWrapper.setPublisherMessageIdSpanAttribute(messageId); - nextPublish.messageWrapper.endPublisherSpan(); + tracer.setPublisherMessageIdSpanAttribute(nextPublish.messageWrapper, messageId); + tracer.endPublisherSpan(nextPublish.messageWrapper); } } } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java index 4fa899ad3..eefaef724 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java @@ -16,16 +16,14 @@ package com.google.cloud.pubsub.v1; +import com.google.api.core.InternalApi; import com.google.common.base.Preconditions; import com.google.pubsub.v1.PubsubMessage; import com.google.pubsub.v1.SubscriptionName; import com.google.pubsub.v1.TopicName; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.TextMapGetter; @@ -38,7 +36,6 @@ */ public class PubsubMessageWrapper { private PubsubMessage message; - private final boolean enableOpenTelemetryTracing; private final TopicName topicName; private final SubscriptionName subscriptionName; @@ -47,17 +44,9 @@ public class PubsubMessageWrapper { private final String ackId; private final int deliveryAttempt; - private String PUBLISHER_SPAN_NAME; - private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; - private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; private static final String PUBLISH_START_EVENT = "publish start"; private static final String PUBLISH_END_EVENT = "publish end"; - private String SUBSCRIBER_SPAN_NAME; - private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = - "subscriber concurrency control"; - private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; - private String SUBSCRIBE_PROCESS_SPAN_NAME; private static final String MODACK_START_EVENT = "modack start"; private static final String MODACK_END_EVENT = "modack end"; private static final String NACK_START_EVENT = "nack start"; @@ -67,15 +56,7 @@ public class PubsubMessageWrapper { private static final String GOOGCLIENT_PREFIX = "googclient_"; - private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; - private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; - private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; - private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; - private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = - "messaging.gcp_pubsub.message.exactly_once_delivery"; private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; - private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = - "messaging.gcp_pubsub.message.delivery_attempt"; private Span publisherSpan; private Span publishFlowControlSpan; @@ -86,117 +67,123 @@ public class PubsubMessageWrapper { private Span subscribeSchedulerSpan; private Span subscribeProcessSpan; + @InternalApi("For use by the google-cloud-pubsub library only") public PubsubMessageWrapper(Builder builder) { this.message = builder.message; this.topicName = builder.topicName; this.subscriptionName = builder.subscriptionName; this.ackId = builder.ackId; this.deliveryAttempt = builder.deliveryAttempt; - this.enableOpenTelemetryTracing = builder.enableOpenTelemetryTracing; - if (this.topicName != null) { - this.PUBLISHER_SPAN_NAME = builder.topicName.getTopic() + " create"; - } - if (this.subscriptionName != null) { - this.SUBSCRIBER_SPAN_NAME = builder.subscriptionName.getSubscription() + " subscribe"; - this.SUBSCRIBE_PROCESS_SPAN_NAME = builder.subscriptionName.getSubscription() + " process"; - } } - public static Builder newBuilder( - PubsubMessage message, String topicName, boolean enableOpenTelemetryTracing) { - return new Builder(message, topicName, enableOpenTelemetryTracing); + public static Builder newBuilder(PubsubMessage message, String topicName) { + return new Builder(message, topicName); } public static Builder newBuilder( - PubsubMessage message, - String subscriptionName, - String ackId, - int deliveryAttempt, - boolean enableOpenTelemetryTracing) { - return new Builder( - message, subscriptionName, ackId, deliveryAttempt, enableOpenTelemetryTracing); + PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { + return new Builder(message, subscriptionName, ackId, deliveryAttempt); } /** Returns the PubsubMessage associated with this wrapper. */ - public PubsubMessage getPubsubMessage() { + protected PubsubMessage getPubsubMessage() { return message; } - /** Returns the parent publisher span for this message wrapper. */ - public Span getPublisherSpan() { - return publisherSpan; + protected void setPubsubMessage(PubsubMessage message) { + this.message = message; } - /** Returns the parent subscriber span for this message wrapper. */ - public Span getSubscriberSpan() { - return subscriberSpan; + /** Returns the TopicName for this wrapper as a string. */ + protected String getTopicName() { + if (topicName != null) { + return topicName.getTopic(); + } + return ""; + } + + protected String getTopicProject() { + if (topicName != null) { + return topicName.getProject(); + } + return ""; + } + + /** Returns the SubscriptionName for this wrapper as a string. */ + protected String getSubscriptionName() { + if (subscriptionName != null) { + return subscriptionName.getSubscription(); + } + return ""; + } + + protected String getSubscriptionProject() { + if (subscriptionName != null) { + return subscriptionName.getProject(); + } + return ""; + } + + protected String getMessageId() { + return message.getMessageId(); + } + + protected String getAckId() { + return ackId; } - /** Returns the delivery attempt for the received PubsubMessage. */ - public int getDeliveryAttempt() { + protected int getDataSize() { + return message.getData().size(); + } + + protected String getOrderingKey() { + return message.getOrderingKey(); + } + + protected int getDeliveryAttempt() { return deliveryAttempt; } - /** Sets the PubsubMessage for this wrapper. */ - public void setPubsubMessage(PubsubMessage message) { - this.message = message; + protected Span getPublisherSpan() { + return publisherSpan; } - /** - * Creates and starts the parent span with the appropriate span attributes and injects the span - * context into the {@link PubsubMessage} attributes. - */ - public void startPublisherSpan(Tracer tracer) { - if (enableOpenTelemetryTracing && tracer != null) { - AttributesBuilder attributesBuilder = - OpenTelemetryUtil.createCommonSpanAttributesBuilder( - topicName.getTopic(), topicName.getProject(), "publish", "create"); - - attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getData().size()); - if (!message.getOrderingKey().isEmpty()) { - attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); - } + protected void setPublisherSpan(Span span) { + this.publisherSpan = span; + } - publisherSpan = - tracer - .spanBuilder(PUBLISHER_SPAN_NAME) - .setSpanKind(SpanKind.PRODUCER) - .setAllAttributes(attributesBuilder.build()) - .startSpan(); + protected void setPublishFlowControlSpan(Span span) { + this.publishFlowControlSpan = span; + } - if (publisherSpan.getSpanContext().isValid()) { - injectSpanContext(); - } - } + protected void setPublishBatchingSpan(Span span) { + this.publishBatchingSpan = span; } - /** Creates a span for publish-side flow control as a child of the parent publisher span. */ - public void startPublishFlowControlSpan(Tracer tracer) { - if (enableOpenTelemetryTracing && tracer != null) { - publishFlowControlSpan = - startChildSpan(tracer, PUBLISH_FLOW_CONTROL_SPAN_NAME, publisherSpan); - } + protected Span getSubscriberSpan() { + return subscriberSpan; } - /** Creates a span for publish message batching as a child of the parent publisher span. */ - public void startPublishBatchingSpan(Tracer tracer) { - if (enableOpenTelemetryTracing && tracer != null) { - publishBatchingSpan = startChildSpan(tracer, PUBLISH_BATCHING_SPAN_NAME, publisherSpan); - } + protected void setSubscriberSpan(Span span) { + this.subscriberSpan = span; } - /** - * Creates publish start and end events that are tied to the publish RPC span start and end times. - */ - public void addPublishStartEvent() { - if (enableOpenTelemetryTracing && publisherSpan != null) { - publisherSpan.addEvent(PUBLISH_START_EVENT); - } + protected void setSubscribeConcurrencyControlSpan(Span span) { + this.subscribeConcurrencyControlSpan = span; } - public void addPublishEndEvent() { - if (enableOpenTelemetryTracing && publisherSpan != null) { - publisherSpan.addEvent(PUBLISH_END_EVENT); + protected void setSubscribeSchedulerSpan(Span span) { + this.subscribeSchedulerSpan = span; + } + + protected void setSubscribeProcessSpan(Span span) { + this.subscribeProcessSpan = span; + } + + /** Creates a publish start event that is tied to the publish RPC span time. */ + protected void addPublishStartEvent() { + if (publisherSpan != null) { + publisherSpan.addEvent(PUBLISH_START_EVENT); } } @@ -204,30 +191,30 @@ public void addPublishEndEvent() { * Sets the message ID attribute in the publisher parent span. This is called after the publish * RPC returns with a message ID. */ - public void setPublisherMessageIdSpanAttribute(String messageId) { - if (enableOpenTelemetryTracing && publisherSpan != null) { + protected void setPublisherMessageIdSpanAttribute(String messageId) { + if (publisherSpan != null) { publisherSpan.setAttribute(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId); } } /** Ends the publisher parent span if it exists. */ - public void endPublisherSpan() { - if (enableOpenTelemetryTracing && publisherSpan != null) { - addPublishEndEvent(); + protected void endPublisherSpan() { + if (publisherSpan != null) { + publisherSpan.addEvent(PUBLISH_END_EVENT); publisherSpan.end(); } } /** Ends the publish flow control span if it exists. */ - public void endPublishFlowControlSpan() { - if (enableOpenTelemetryTracing && publishFlowControlSpan != null) { + protected void endPublishFlowControlSpan() { + if (publishFlowControlSpan != null) { publishFlowControlSpan.end(); } } /** Ends the publish batching span if it exists. */ - public void endPublishBatchingSpan() { - if (enableOpenTelemetryTracing && publishBatchingSpan != null) { + protected void endPublishBatchingSpan() { + if (publishBatchingSpan != null) { publishBatchingSpan.end(); } } @@ -235,8 +222,8 @@ public void endPublishBatchingSpan() { /** * Sets an error status and records an exception when an exception is thrown during flow control. */ - public void setPublishFlowControlSpanException(Throwable t) { - if (enableOpenTelemetryTracing && publishFlowControlSpan != null) { + protected void setPublishFlowControlSpanException(Throwable t) { + if (publishFlowControlSpan != null) { publishFlowControlSpan.setStatus( StatusCode.ERROR, "Exception thrown during publish flow control."); publishFlowControlSpan.recordException(t); @@ -244,143 +231,63 @@ public void setPublishFlowControlSpanException(Throwable t) { } } - /** - * Sets an error status and records an exception when an exception is thrown during message - * batching. - */ - public void setPublishBatchingSpanException(Throwable t) { - if (enableOpenTelemetryTracing && publishBatchingSpan != null) { - publishBatchingSpan.setStatus(StatusCode.ERROR, "Exception thrown during publish batching."); - publishBatchingSpan.recordException(t); - endAllPublishSpans(); - } - } - - /** - * Creates the subscriber parent span using span context propagated in the message attributes and - * sets the appropriate span attributes. - */ - public void startSubscriberSpan(Tracer tracer, boolean exactlyOnceDeliveryEnabled) { - if (enableOpenTelemetryTracing && tracer != null) { - AttributesBuilder attributesBuilder = - OpenTelemetryUtil.createCommonSpanAttributesBuilder( - subscriptionName.getSubscription(), - subscriptionName.getProject(), - "onResponse", - null); - - attributesBuilder - .put(SemanticAttributes.MESSAGING_MESSAGE_ID, message.getMessageId()) - .put(MESSAGE_SIZE_ATTR_KEY, message.getData().size()) - .put(MESSAGE_ACK_ID_ATTR_KEY, ackId) - .put(MESSAGE_EXACTLY_ONCE_ATTR_KEY, exactlyOnceDeliveryEnabled); - if (!message.getOrderingKey().isEmpty()) { - attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); - } - if (deliveryAttempt > 0) { - attributesBuilder.put(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, deliveryAttempt); - } - subscriberSpan = extractSpanContext(tracer, attributesBuilder.build()); - } - } - - /** Creates a span for subscribe concurrency control as a child of the parent subscriber span. */ - public void startSubscribeConcurrencyControlSpan(Tracer tracer) { - if (enableOpenTelemetryTracing && tracer != null) { - subscribeConcurrencyControlSpan = - startChildSpan(tracer, SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME, subscriberSpan); - } - } - - /** - * Creates a span for subscribe ordering key scheduling as a child of the parent subscriber span. - */ - public void startSubscribeSchedulerSpan(Tracer tracer) { - if (enableOpenTelemetryTracing && tracer != null) { - subscribeSchedulerSpan = - startChildSpan(tracer, SUBSCRIBE_SCHEDULER_SPAN_NAME, subscriberSpan); - } - } - - /** Creates a span for subscribe message processing as a child of the parent subscriber span. */ - public void startSubscribeProcessSpan(Tracer tracer) { - if (enableOpenTelemetryTracing && tracer != null) { - subscribeProcessSpan = startChildSpan(tracer, SUBSCRIBE_PROCESS_SPAN_NAME, subscriberSpan); - subscribeProcessSpan.setAttribute( - SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE); - if (publisherSpan != null) { - subscribeProcessSpan.addLink(publisherSpan.getSpanContext()); - } - } - } - /** * Creates start and end events for ModAcks, Nacks, and Acks that are tied to the corresponding * RPC span start and end times. */ - public void addModAckStartEvent() { - if (enableOpenTelemetryTracing && subscriberSpan != null) { + protected void addModAckStartEvent() { + if (subscriberSpan != null) { subscriberSpan.addEvent(MODACK_START_EVENT); } } - public void addModAckEndEvent() { - if (enableOpenTelemetryTracing && subscriberSpan != null) { + protected void addModAckEndEvent() { + if (subscriberSpan != null) { subscriberSpan.addEvent(MODACK_END_EVENT); } } - public void addNackStartEvent() { - if (enableOpenTelemetryTracing && subscriberSpan != null) { + protected void addNackStartEvent() { + if (subscriberSpan != null) { subscriberSpan.addEvent(NACK_START_EVENT); } } - public void addNackEndEvent() { - if (enableOpenTelemetryTracing && subscriberSpan != null) { + protected void addNackEndEvent() { + if (subscriberSpan != null) { subscriberSpan.addEvent(NACK_END_EVENT); } } - public void addAckStartEvent() { - if (enableOpenTelemetryTracing && subscriberSpan != null) { + protected void addAckStartEvent() { + if (subscriberSpan != null) { subscriberSpan.addEvent(ACK_START_EVENT); } } - public void addAckEndEvent() { - if (enableOpenTelemetryTracing && subscriberSpan != null) { + protected void addAckEndEvent() { + if (subscriberSpan != null) { subscriberSpan.addEvent(ACK_END_EVENT); } } - public void addEndRpcEvent(boolean isModack, int ackDeadline) { - if (!isModack) { - addAckEndEvent(); - } else if (ackDeadline == 0) { - addNackEndEvent(); - } else { - addModAckEndEvent(); - } - } - /** Ends the subscriber parent span if exists. */ - public void endSubscriberSpan() { - if (enableOpenTelemetryTracing && subscriberSpan != null) { + protected void endSubscriberSpan() { + if (subscriberSpan != null) { subscriberSpan.end(); } } /** Ends the subscribe concurreny control span if exists. */ - public void endSubscribeConcurrencyControlSpan() { - if (enableOpenTelemetryTracing && subscribeConcurrencyControlSpan != null) { + protected void endSubscribeConcurrencyControlSpan() { + if (subscribeConcurrencyControlSpan != null) { subscribeConcurrencyControlSpan.end(); } } /** Ends the subscribe scheduler span if exists. */ - public void endSubscribeSchedulerSpan() { - if (enableOpenTelemetryTracing && subscribeSchedulerSpan != null) { + protected void endSubscribeSchedulerSpan() { + if (subscribeSchedulerSpan != null) { subscribeSchedulerSpan.end(); } } @@ -389,8 +296,8 @@ public void endSubscribeSchedulerSpan() { * Ends the subscribe process span if it exists, creates an event with the appropriate result, and * sets the result on the parent subscriber span. */ - public void endSubscribeProcessSpan(String action) { - if (enableOpenTelemetryTracing && subscribeProcessSpan != null) { + protected void endSubscribeProcessSpan(String action) { + if (subscribeProcessSpan != null) { subscribeProcessSpan.addEvent(action + " called"); subscribeProcessSpan.end(); subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, action); @@ -398,8 +305,8 @@ public void endSubscribeProcessSpan(String action) { } /** Sets an exception on the subscriber span during Ack/ModAck/Nack failures */ - public void setSubscriberSpanException(Throwable t, String exception) { - if (enableOpenTelemetryTracing && subscriberSpan != null) { + protected void setSubscriberSpanException(Throwable t, String exception) { + if (subscriberSpan != null) { subscriberSpan.setStatus(StatusCode.ERROR, exception); subscriberSpan.recordException(t); endAllSubscribeSpans(); @@ -407,8 +314,8 @@ public void setSubscriberSpanException(Throwable t, String exception) { } /** Sets result of the parent subscriber span to expired and ends its. */ - public void setSubscriberSpanExpirationResult() { - if (enableOpenTelemetryTracing && subscriberSpan != null) { + protected void setSubscriberSpanExpirationResult() { + if (subscriberSpan != null) { subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, "expired"); endSubscriberSpan(); } @@ -418,8 +325,8 @@ public void setSubscriberSpanExpirationResult() { * Sets an error status and records an exception when an exception is thrown subscriber * concurrency control. */ - public void setSubscribeConcurrencyControlSpanException(Throwable t) { - if (enableOpenTelemetryTracing && subscribeConcurrencyControlSpan != null) { + protected void setSubscribeConcurrencyControlSpanException(Throwable t) { + if (subscribeConcurrencyControlSpan != null) { subscribeConcurrencyControlSpan.setStatus( StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); subscribeConcurrencyControlSpan.recordException(t); @@ -427,11 +334,6 @@ public void setSubscribeConcurrencyControlSpanException(Throwable t) { } } - /** Creates a child span of the given parent span. */ - private Span startChildSpan(Tracer tracer, String name, Span parent) { - return tracer.spanBuilder(name).setParent(Context.current().with(parent)).startSpan(); - } - /** Ends all publisher-side spans associated with this message wrapper. */ private void endAllPublishSpans() { endPublishFlowControlSpan(); @@ -450,7 +352,7 @@ private void endAllSubscribeSpans() { * Injects the span context into the attributes of a Pub/Sub message for propagation to the * subscriber client. */ - private void injectSpanContext() { + protected void injectSpanContext() { TextMapSetter injectMessageAttributes = new TextMapSetter() { @Override @@ -470,7 +372,7 @@ public void set(PubsubMessageWrapper carrier, String key, String value) { * Extracts the span context from the attributes of a Pub/Sub message and creates the parent * subscriber span using that context. */ - private Span extractSpanContext(Tracer tracer, Attributes attributes) { + protected Context extractSpanContext(Attributes attributes) { TextMapGetter extractMessageAttributes = new TextMapGetter() { @Override @@ -485,13 +387,7 @@ public Iterable keys(PubsubMessageWrapper carrier) { Context context = W3CTraceContextPropagator.getInstance() .extract(Context.current(), this, extractMessageAttributes); - publisherSpan = Span.fromContextOrNull(context); - return tracer - .spanBuilder(SUBSCRIBER_SPAN_NAME) - .setSpanKind(SpanKind.CONSUMER) - .setParent(context) - .setAllAttributes(attributes) - .startSpan(); + return context; } /** Builder of {@link PubsubMessageWrapper PubsubMessageWrapper}. */ @@ -501,49 +397,37 @@ protected static final class Builder { private SubscriptionName subscriptionName = null; private String ackId = null; private int deliveryAttempt = 0; - private boolean enableOpenTelemetryTracing = false; - public Builder(PubsubMessage message, String topicName, boolean enableOpenTelemetryTracing) { + public Builder(PubsubMessage message, String topicName) { this.message = message; if (topicName != null) { this.topicName = TopicName.parse(topicName); } - this.enableOpenTelemetryTracing = enableOpenTelemetryTracing; } public Builder( - PubsubMessage message, - String subscriptionName, - String ackId, - int deliveryAttempt, - boolean enableOpenTelemetryTracing) { + PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { this.message = message; if (subscriptionName != null) { this.subscriptionName = SubscriptionName.parse(subscriptionName); } this.ackId = ackId; this.deliveryAttempt = deliveryAttempt; - this.enableOpenTelemetryTracing = enableOpenTelemetryTracing; } public Builder( PubsubMessage message, SubscriptionName subscriptionName, String ackId, - int deliveryAttempt, - boolean enableOpenTelemetryTracing) { + int deliveryAttempt) { this.message = message; this.subscriptionName = subscriptionName; this.ackId = ackId; this.deliveryAttempt = deliveryAttempt; - this.enableOpenTelemetryTracing = enableOpenTelemetryTracing; } public PubsubMessageWrapper build() { - Preconditions.checkArgument( - this.enableOpenTelemetryTracing == false - || this.topicName != null - || this.subscriptionName != null); + Preconditions.checkArgument(this.topicName != null || this.subscriptionName != null); return new PubsubMessageWrapper(this); } } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubTracer.java new file mode 100644 index 000000000..98003235c --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubTracer.java @@ -0,0 +1,137 @@ +/* + * 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.pubsub.v1; + +import io.opentelemetry.api.trace.Span; +import java.util.List; + +public interface PubsubTracer { + default void startPublisherSpan(PubsubMessageWrapper message) { + // noop + } + + default void endPublisherSpan(PubsubMessageWrapper message) { + // noop + } + + default void setPublisherMessageIdSpanAttribute(PubsubMessageWrapper message, String messageId) { + // noop + } + + default void startPublishFlowControlSpan(PubsubMessageWrapper message) { + // noop + } + + default void endPublishFlowControlSpan(PubsubMessageWrapper message) { + // noop + } + + default void setPublishFlowControlSpanException(PubsubMessageWrapper message, Throwable t) { + // noop + } + + default void startPublishBatchingSpan(PubsubMessageWrapper message) { + // noop + } + + default void endPublishBatchingSpan(PubsubMessageWrapper message) { + // noop + } + + default Span startPublishRpcSpan(String topic, List messages) { + // noop + return null; + } + + default void endPublishRpcSpan(Span publishRpcSpan) { + // noop + } + + default void setPublishRpcSpanException(Span publishRpcSpan, Throwable t) { + // noop + } + + default void startSubscriberSpan( + PubsubMessageWrapper message, boolean exactlyOnceDeliveryEnabled) { + // noop + } + + default void endSubscriberSpan(PubsubMessageWrapper message) { + // noop + } + + default void setSubscriberSpanExpirationResult(PubsubMessageWrapper message) { + // noop + } + + default void setSubscriberSpanException( + PubsubMessageWrapper message, Throwable t, String exception) { + // noop + } + + default void startSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { + // noop + } + + default void endSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { + // noop + } + + default void setSubscribeConcurrencyControlSpanException( + PubsubMessageWrapper message, Throwable t) { + // noop + } + + default void startSubscribeSchedulerSpan(PubsubMessageWrapper message) { + // noop + } + + default void endSubscribeSchedulerSpan(PubsubMessageWrapper message) { + // noop + } + + default void startSubscribeProcessSpan(PubsubMessageWrapper message) { + // noop + } + + default void endSubscribeProcessSpan(PubsubMessageWrapper message, String action) { + // noop + } + + default Span startSubscribeRpcSpan( + String subscription, + String rpcOperation, + List messages, + int ackDeadline, + boolean isReceiptModack) { + // noop + return null; + } + + default void endSubscribeRpcSpan(Span rpcSpan) { + // noop + } + + default void setSubscribeRpcSpanException( + Span rpcSpan, boolean isModack, int ackDeadline, Throwable t) { + // noop + } + + default void addEndRpcEvent(PubsubMessageWrapper message, boolean isModack, int ackDeadline) { + // noop + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java index 26f2e0eec..015f78c33 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java @@ -51,7 +51,6 @@ import io.grpc.Status; import io.grpc.protobuf.StatusProto; import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.Tracer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -121,7 +120,7 @@ final class StreamingSubscriberConnection extends AbstractApiService implements private final String clientId = UUID.randomUUID().toString(); private final boolean enableOpenTelemetryTracing; - private final Tracer tracer; + private final PubsubTracer tracer; private StreamingSubscriberConnection(Builder builder) { subscription = builder.subscription; @@ -453,9 +452,7 @@ private void sendAckOperations( } } // Creates an Ack span to be passed to the callback - Span rpcSpan = - OpenTelemetryUtil.startSubscribeRpcSpan( - tracer, subscription, "ack", messagesInRequest, 0, false, enableOpenTelemetryTracing); + Span rpcSpan = tracer.startSubscribeRpcSpan(subscription, "ack", messagesInRequest, 0, false); ApiFutureCallback callback = getCallback(ackRequestDataInRequestList, 0, false, currentBackoffMillis, rpcSpan); ApiFuture ackFuture = @@ -493,14 +490,12 @@ private void sendModackOperations( String rpcOperation = deadlineExtensionSeconds == 0 ? "nack" : "modack"; // Creates either a ModAck span or a Nack span depending on the given ack deadline Span rpcSpan = - OpenTelemetryUtil.startSubscribeRpcSpan( - tracer, + tracer.startSubscribeRpcSpan( subscription, rpcOperation, messagesInRequest, deadlineExtensionSeconds, - modackRequestData.getIsReceiptModack(), - enableOpenTelemetryTracing); + modackRequestData.getIsReceiptModack()); ApiFutureCallback callback = getCallback( modackRequestData.getAckRequestData(), @@ -561,7 +556,7 @@ private ApiFutureCallback getCallback( public void onSuccess(Empty empty) { ackOperationsWaiter.incrementPendingCount(-1); - OpenTelemetryUtil.endSubscribeRpcSpan(rpcSpan, enableOpenTelemetryTracing); + tracer.endSubscribeRpcSpan(rpcSpan); for (AckRequestData ackRequestData : ackRequestDataList) { // This will check if a response is needed, and if it has already been set @@ -569,9 +564,10 @@ public void onSuccess(Empty empty) { messageDispatcher.notifyAckSuccess(ackRequestData); // Remove from our pending operations pendingRequests.remove(ackRequestData); - ackRequestData.getMessageWrapper().addEndRpcEvent(isModack, deadlineExtensionSeconds); + tracer.addEndRpcEvent( + ackRequestData.getMessageWrapper(), isModack, deadlineExtensionSeconds); if (!isModack || deadlineExtensionSeconds == 0) { - ackRequestData.getMessageWrapper().endSubscriberSpan(); + tracer.endSubscriberSpan(ackRequestData.getMessageWrapper()); } } } @@ -584,15 +580,15 @@ public void onFailure(Throwable t) { Level level = isAlive() ? Level.WARNING : Level.FINER; logger.log(level, "failed to send operations", t); - OpenTelemetryUtil.setSubscribeRpcSpanException( - rpcSpan, isModack, deadlineExtensionSeconds, t, enableOpenTelemetryTracing); + tracer.setSubscribeRpcSpanException(rpcSpan, isModack, deadlineExtensionSeconds, t); if (!getExactlyOnceDeliveryEnabled()) { if (enableOpenTelemetryTracing) { for (AckRequestData ackRequestData : ackRequestDataList) { - ackRequestData.getMessageWrapper().addEndRpcEvent(isModack, deadlineExtensionSeconds); + tracer.addEndRpcEvent( + ackRequestData.getMessageWrapper(), isModack, deadlineExtensionSeconds); if (!isModack || deadlineExtensionSeconds == 0) { - ackRequestData.getMessageWrapper().endSubscriberSpan(); + tracer.endSubscriberSpan(ackRequestData.getMessageWrapper()); } } } @@ -619,19 +615,18 @@ public void onFailure(Throwable t) { errorMessage); ackRequestData.setResponse(AckResponse.INVALID, setResponseOnSuccess); messageDispatcher.notifyAckFailed(ackRequestData); - ackRequestData - .getMessageWrapper() - .addEndRpcEvent(isModack, deadlineExtensionSeconds); - ackRequestData - .getMessageWrapper() - .setSubscriberSpanException(t, "Invalid ack ID"); + tracer.addEndRpcEvent( + ackRequestData.getMessageWrapper(), isModack, deadlineExtensionSeconds); + tracer.setSubscriberSpanException( + ackRequestData.getMessageWrapper(), t, "Invalid ack ID"); } else { logger.log(Level.INFO, "Unknown error message, will not resend", errorMessage); ackRequestData.setResponse(AckResponse.OTHER, setResponseOnSuccess); messageDispatcher.notifyAckFailed(ackRequestData); - ackRequestData - .getMessageWrapper() - .addEndRpcEvent(isModack, deadlineExtensionSeconds); + tracer.addEndRpcEvent( + ackRequestData.getMessageWrapper(), isModack, deadlineExtensionSeconds); + tracer.setSubscriberSpanException( + ackRequestData.getMessageWrapper(), t, "Unknown error message"); ackRequestData .getMessageWrapper() .setSubscriberSpanException(t, "Unknown error message"); @@ -639,10 +634,9 @@ public void onFailure(Throwable t) { } else { ackRequestData.setResponse(AckResponse.SUCCESSFUL, setResponseOnSuccess); messageDispatcher.notifyAckSuccess(ackRequestData); - ackRequestData.getMessageWrapper().endSubscriberSpan(); - ackRequestData - .getMessageWrapper() - .addEndRpcEvent(isModack, deadlineExtensionSeconds); + tracer.endSubscriberSpan(ackRequestData.getMessageWrapper()); + tracer.addEndRpcEvent( + ackRequestData.getMessageWrapper(), isModack, deadlineExtensionSeconds); } // Remove from our pending pendingRequests.remove(ackRequestData); @@ -704,7 +698,7 @@ public static final class Builder { private ApiClock clock; private boolean enableOpenTelemetryTracing; - private Tracer tracer; + private PubsubTracer tracer; protected Builder(MessageReceiver receiver) { this.receiver = receiver; @@ -801,7 +795,7 @@ public Builder setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing) return this; } - public Builder setTracer(Tracer tracer) { + public Builder setTracer(PubsubTracer tracer) { this.tracer = tracer; return this; } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java index e581875a7..aceea0c86 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java @@ -151,7 +151,7 @@ public class Subscriber extends AbstractApiService implements SubscriberInterfac private final boolean enableOpenTelemetryTracing; private final OpenTelemetry openTelemetry; - private Tracer tracer = null; + private PubsubTracer tracer = null; private Subscriber(Builder builder) { receiver = builder.receiver; @@ -209,8 +209,11 @@ private Subscriber(Builder builder) { this.enableOpenTelemetryTracing = builder.enableOpenTelemetryTracing; this.openTelemetry = builder.openTelemetry; - if (this.openTelemetry != null) { - this.tracer = builder.openTelemetry.getTracer(OPEN_TELEMETRY_TRACER_NAME); + if (this.openTelemetry != null && this.enableOpenTelemetryTracing) { + Tracer openTelemetryTracer = builder.openTelemetry.getTracer(OPEN_TELEMETRY_TRACER_NAME); + if (openTelemetryTracer != null) { + this.tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer); + } } streamingSubscriberConnections = new ArrayList(numPullers); diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java index 9106f69c4..76f3aba09 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -104,25 +104,23 @@ public void testPublishSpansSuccess() { openTelemetryTesting.clearSpans(); PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) - .build(); + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); List messageWrappers = Arrays.asList(messageWrapper); long messageSize = messageWrapper.getPubsubMessage().getData().size(); - Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + PubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer); // Call all span start/end methods in the expected order - messageWrapper.startPublisherSpan(tracer); - messageWrapper.startPublishFlowControlSpan(tracer); - messageWrapper.endPublishFlowControlSpan(); - messageWrapper.startPublishBatchingSpan(tracer); - messageWrapper.endPublishBatchingSpan(); - Span publishRpcSpan = - OpenTelemetryUtil.startPublishRpcSpan( - tracer, FULL_TOPIC_NAME.toString(), messageWrappers, true); - OpenTelemetryUtil.endPublishRpcSpan(publishRpcSpan, true); - messageWrapper.setPublisherMessageIdSpanAttribute(MESSAGE_ID); - messageWrapper.endPublisherSpan(); + tracer.startPublisherSpan(messageWrapper); + tracer.startPublishFlowControlSpan(messageWrapper); + tracer.endPublishFlowControlSpan(messageWrapper); + tracer.startPublishBatchingSpan(messageWrapper); + tracer.endPublishBatchingSpan(messageWrapper); + Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); + tracer.endPublishRpcSpan(publishRpcSpan); + tracer.setPublisherMessageIdSpanAttribute(messageWrapper, MESSAGE_ID); + tracer.endPublisherSpan(messageWrapper); List allSpans = openTelemetryTesting.getSpans(); assertEquals(4, allSpans.size()); @@ -220,16 +218,16 @@ public void testPublishFlowControlSpanFailure() { openTelemetryTesting.clearSpans(); PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) - .build(); + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); - Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + PubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer); - messageWrapper.startPublisherSpan(tracer); - messageWrapper.startPublishFlowControlSpan(tracer); + tracer.startPublisherSpan(messageWrapper); + tracer.startPublishFlowControlSpan(messageWrapper); Exception e = new Exception("test-exception"); - messageWrapper.setPublishFlowControlSpanException(e); + tracer.setPublishFlowControlSpanException(messageWrapper, e); List allSpans = openTelemetryTesting.getSpans(); assertEquals(2, allSpans.size()); @@ -255,64 +253,23 @@ public void testPublishFlowControlSpanFailure() { .hasEnded(); } - @Test - public void testPublishBatchingSpanFailure() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) - .build(); - - Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - - messageWrapper.startPublisherSpan(tracer); - messageWrapper.startPublishBatchingSpan(tracer); - - Exception e = new Exception("test-exception"); - messageWrapper.setPublishBatchingSpanException(e); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(2, allSpans.size()); - SpanData batchingSpanData = allSpans.get(0); - SpanData publisherSpanData = allSpans.get(1); - - SpanDataAssert batchingSpanDataAssert = OpenTelemetryAssertions.assertThat(batchingSpanData); - StatusData expectedStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown during publish batching."); - batchingSpanDataAssert - .hasName(PUBLISH_BATCHING_SPAN_NAME) - .hasParent(publisherSpanData) - .hasStatus(expectedStatus) - .hasException(e) - .hasEnded(); - - SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); - publisherSpanDataAssert - .hasName(PUBLISHER_SPAN_NAME) - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasEnded(); - } - @Test public void testPublishRpcSpanFailure() { openTelemetryTesting.clearSpans(); PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) - .build(); + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); List messageWrappers = Arrays.asList(messageWrapper); - Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + PubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer); - messageWrapper.startPublisherSpan(tracer); - Span publishRpcSpan = - OpenTelemetryUtil.startPublishRpcSpan( - tracer, FULL_TOPIC_NAME.toString(), messageWrappers, true); + tracer.startPublisherSpan(messageWrapper); + Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); Exception e = new Exception("test-exception"); - OpenTelemetryUtil.setPublishRpcSpanException(publishRpcSpan, e, true); - messageWrapper.endPublisherSpan(); + tracer.setPublishRpcSpanException(publishRpcSpan, e); + tracer.endPublisherSpan(messageWrapper); List allSpans = openTelemetryTesting.getSpans(); assertEquals(2, allSpans.size()); @@ -341,67 +298,53 @@ public void testPublishRpcSpanFailure() { public void testSubscribeSpansSuccess() { openTelemetryTesting.clearSpans(); - Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + PubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer); PubsubMessageWrapper publishMessageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString(), true) - .build(); + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); // Initialize the Publisher span to inject the context in the message - publishMessageWrapper.startPublisherSpan(tracer); - publishMessageWrapper.endPublisherSpan(); + tracer.startPublisherSpan(publishMessageWrapper); + tracer.endPublisherSpan(publishMessageWrapper); PubsubMessage publishedMessage = publishMessageWrapper.getPubsubMessage().toBuilder().setMessageId(MESSAGE_ID).build(); PubsubMessageWrapper subscribeMessageWrapper = PubsubMessageWrapper.newBuilder( - publishedMessage, FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, 1, true) + publishedMessage, FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, 1) .build(); List subscribeMessageWrappers = Arrays.asList(subscribeMessageWrapper); long messageSize = subscribeMessageWrapper.getPubsubMessage().getData().size(); // Call all span start/end methods in the expected order - subscribeMessageWrapper.startSubscriberSpan(tracer, EXACTLY_ONCE_ENABLED); - subscribeMessageWrapper.startSubscribeConcurrencyControlSpan(tracer); - subscribeMessageWrapper.endSubscribeConcurrencyControlSpan(); - subscribeMessageWrapper.startSubscribeSchedulerSpan(tracer); - subscribeMessageWrapper.endSubscribeSchedulerSpan(); - subscribeMessageWrapper.startSubscribeProcessSpan(tracer); - subscribeMessageWrapper.endSubscribeProcessSpan(PROCESS_ACTION); + tracer.startSubscriberSpan(subscribeMessageWrapper, EXACTLY_ONCE_ENABLED); + tracer.startSubscribeConcurrencyControlSpan(subscribeMessageWrapper); + tracer.endSubscribeConcurrencyControlSpan(subscribeMessageWrapper); + tracer.startSubscribeSchedulerSpan(subscribeMessageWrapper); + tracer.endSubscribeSchedulerSpan(subscribeMessageWrapper); + tracer.startSubscribeProcessSpan(subscribeMessageWrapper); + tracer.endSubscribeProcessSpan(subscribeMessageWrapper, PROCESS_ACTION); Span subscribeModackRpcSpan = - OpenTelemetryUtil.startSubscribeRpcSpan( - tracer, + tracer.startSubscribeRpcSpan( FULL_SUBSCRIPTION_NAME.toString(), "modack", subscribeMessageWrappers, ACK_DEADLINE, - true, true); - OpenTelemetryUtil.endSubscribeRpcSpan(subscribeModackRpcSpan, true); - subscribeMessageWrapper.addEndRpcEvent(true, ACK_DEADLINE); + tracer.endSubscribeRpcSpan(subscribeModackRpcSpan); + tracer.addEndRpcEvent(subscribeMessageWrapper, true, ACK_DEADLINE); Span subscribeAckRpcSpan = - OpenTelemetryUtil.startSubscribeRpcSpan( - tracer, - FULL_SUBSCRIPTION_NAME.toString(), - "ack", - subscribeMessageWrappers, - 0, - false, - true); - OpenTelemetryUtil.endSubscribeRpcSpan(subscribeAckRpcSpan, true); - subscribeMessageWrapper.addEndRpcEvent(false, 0); + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "ack", subscribeMessageWrappers, 0, false); + tracer.endSubscribeRpcSpan(subscribeAckRpcSpan); + tracer.addEndRpcEvent(subscribeMessageWrapper, false, 0); Span subscribeNackRpcSpan = - OpenTelemetryUtil.startSubscribeRpcSpan( - tracer, - FULL_SUBSCRIPTION_NAME.toString(), - "nack", - subscribeMessageWrappers, - 0, - false, - true); - OpenTelemetryUtil.endSubscribeRpcSpan(subscribeNackRpcSpan, true); - subscribeMessageWrapper.addEndRpcEvent(true, 0); - subscribeMessageWrapper.endSubscriberSpan(); + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "nack", subscribeMessageWrappers, 0, false); + tracer.endSubscribeRpcSpan(subscribeNackRpcSpan); + tracer.addEndRpcEvent(subscribeMessageWrapper, true, 0); + tracer.endSubscriberSpan(subscribeMessageWrapper); List allSpans = openTelemetryTesting.getSpans(); assertEquals(8, allSpans.size()); @@ -459,8 +402,7 @@ public void testSubscribeSpansSuccess() { .containsEntry( SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry( - SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") + .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "modack") .containsEntry( SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()) @@ -487,8 +429,7 @@ public void testSubscribeSpansSuccess() { .containsEntry( SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry( - SemanticAttributes.CODE_FUNCTION, "sendAckOperations") + .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendAckOperations") .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "ack") .containsEntry( SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); @@ -513,8 +454,7 @@ public void testSubscribeSpansSuccess() { .containsEntry( SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry( - SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") + .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "nack") .containsEntry( SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); @@ -578,20 +518,17 @@ public void testSubscribeConcurrencyControlSpanFailure() { PubsubMessageWrapper messageWrapper = PubsubMessageWrapper.newBuilder( - getPubsubMessage(), - FULL_SUBSCRIPTION_NAME.toString(), - ACK_ID, - DELIVERY_ATTEMPT, - true) + getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) .build(); - Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + PubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer); - messageWrapper.startSubscriberSpan(tracer, EXACTLY_ONCE_ENABLED); - messageWrapper.startSubscribeConcurrencyControlSpan(tracer); + tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); + tracer.startSubscribeConcurrencyControlSpan(messageWrapper); Exception e = new Exception("test-exception"); - messageWrapper.setSubscribeConcurrencyControlSpanException(e); + tracer.setSubscribeConcurrencyControlSpanException(messageWrapper, e); List allSpans = openTelemetryTesting.getSpans(); assertEquals(2, allSpans.size()); @@ -625,19 +562,16 @@ public void testSubscriberSpanFailure() { PubsubMessageWrapper messageWrapper = PubsubMessageWrapper.newBuilder( - getPubsubMessage(), - FULL_SUBSCRIPTION_NAME.toString(), - ACK_ID, - DELIVERY_ATTEMPT, - true) + getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) .build(); - Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + PubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer); - messageWrapper.startSubscriberSpan(tracer, EXACTLY_ONCE_ENABLED); + tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); Exception e = new Exception("test-exception"); - messageWrapper.setSubscriberSpanException(e, "Test exception"); + tracer.setSubscriberSpanException(messageWrapper, e, "Test exception"); List allSpans = openTelemetryTesting.getSpans(); assertEquals(1, allSpans.size()); @@ -661,39 +595,29 @@ public void testSubscribeRpcSpanFailures() { PubsubMessageWrapper messageWrapper = PubsubMessageWrapper.newBuilder( - getPubsubMessage(), - FULL_SUBSCRIPTION_NAME.toString(), - ACK_ID, - DELIVERY_ATTEMPT, - true) + getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) .build(); List messageWrappers = Arrays.asList(messageWrapper); - Tracer tracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + PubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer); - messageWrapper.startSubscriberSpan(tracer, EXACTLY_ONCE_ENABLED); + tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); Span subscribeModackRpcSpan = - OpenTelemetryUtil.startSubscribeRpcSpan( - tracer, - FULL_SUBSCRIPTION_NAME.toString(), - "modack", - messageWrappers, - ACK_DEADLINE, - true, - true); + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "modack", messageWrappers, ACK_DEADLINE, true); Span subscribeAckRpcSpan = - OpenTelemetryUtil.startSubscribeRpcSpan( - tracer, FULL_SUBSCRIPTION_NAME.toString(), "ack", messageWrappers, 0, false, true); + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "ack", messageWrappers, 0, false); Span subscribeNackRpcSpan = - OpenTelemetryUtil.startSubscribeRpcSpan( - tracer, FULL_SUBSCRIPTION_NAME.toString(), "nack", messageWrappers, 0, false, true); + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "nack", messageWrappers, 0, false); Exception e = new Exception("test-exception"); - OpenTelemetryUtil.setSubscribeRpcSpanException( - subscribeModackRpcSpan, true, ACK_DEADLINE, e, true); - OpenTelemetryUtil.setSubscribeRpcSpanException(subscribeAckRpcSpan, false, 0, e, true); - OpenTelemetryUtil.setSubscribeRpcSpanException(subscribeNackRpcSpan, true, 0, e, true); - messageWrapper.endSubscriberSpan(); + tracer.setSubscribeRpcSpanException(subscribeModackRpcSpan, true, ACK_DEADLINE, e); + tracer.setSubscribeRpcSpanException(subscribeAckRpcSpan, false, 0, e); + tracer.setSubscribeRpcSpanException(subscribeNackRpcSpan, true, 0, e); + tracer.endSubscriberSpan(messageWrapper); List allSpans = openTelemetryTesting.getSpans(); assertEquals(4, allSpans.size()); From ca4680f76642cc95acf40f27abf19ea93375f40a Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Thu, 12 Sep 2024 07:13:16 +0000 Subject: [PATCH 24/46] feat: Initialize default no-op PubsubTracer in Publisher and Subscriber --- .../cloud/pubsub/v1/BasePubsubTracer.java | 21 +++++++++++++++++++ .../com/google/cloud/pubsub/v1/Publisher.java | 2 +- .../google/cloud/pubsub/v1/Subscriber.java | 2 +- 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/BasePubsubTracer.java diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/BasePubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/BasePubsubTracer.java new file mode 100644 index 000000000..f574ee163 --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/BasePubsubTracer.java @@ -0,0 +1,21 @@ +/* + * 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.pubsub.v1; + +public class BasePubsubTracer implements PubsubTracer { + BasePubsubTracer() {} +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java index 0aed2428d..5cd20235d 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java @@ -131,7 +131,7 @@ public class Publisher implements PublisherInterface { private final boolean enableOpenTelemetryTracing; private final OpenTelemetry openTelemetry; - private PubsubTracer tracer = null; + private PubsubTracer tracer = new BasePubsubTracer(); /** The maximum number of messages in one request. Defined by the API. */ public static long getApiMaxRequestElementCount() { diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java index aceea0c86..a6413389f 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java @@ -151,7 +151,7 @@ public class Subscriber extends AbstractApiService implements SubscriberInterfac private final boolean enableOpenTelemetryTracing; private final OpenTelemetry openTelemetry; - private PubsubTracer tracer = null; + private PubsubTracer tracer = new BasePubsubTracer(); private Subscriber(Builder builder) { receiver = builder.receiver; From d07b49311cd3e58e2bade35df3a4eee7bc19be95 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Thu, 12 Sep 2024 07:25:24 +0000 Subject: [PATCH 25/46] feat: Ensure SubscriberStreamingConnection and MessageDispatcher have default no-op tracers by default for tests --- .../java/com/google/cloud/pubsub/v1/MessageDispatcher.java | 6 ++++-- .../com/google/cloud/pubsub/v1/PubsubMessageWrapper.java | 2 -- .../cloud/pubsub/v1/StreamingSubscriberConnection.java | 6 ++++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java index 3ebb99733..9695e542d 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java @@ -106,7 +106,7 @@ class MessageDispatcher { private final String subscriptionName; private final boolean enableOpenTelemetryTracing; - private final PubsubTracer tracer; + private PubsubTracer tracer = new BasePubsubTracer(); /** Internal representation of a reply to a Pubsub message, to be sent back to the service. */ public enum AckReply { @@ -227,7 +227,9 @@ private MessageDispatcher(Builder builder) { subscriptionName = builder.subscriptionName; enableOpenTelemetryTracing = builder.enableOpenTelemetryTracing; - tracer = builder.tracer; + if (builder.tracer != null) { + tracer = builder.tracer; + } } private boolean shouldSetMessageFuture() { diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java index eefaef724..53cf95544 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java @@ -17,7 +17,6 @@ package com.google.cloud.pubsub.v1; import com.google.api.core.InternalApi; -import com.google.common.base.Preconditions; import com.google.pubsub.v1.PubsubMessage; import com.google.pubsub.v1.SubscriptionName; import com.google.pubsub.v1.TopicName; @@ -427,7 +426,6 @@ public Builder( } public PubsubMessageWrapper build() { - Preconditions.checkArgument(this.topicName != null || this.subscriptionName != null); return new PubsubMessageWrapper(this); } } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java index 015f78c33..f7b4279bb 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java @@ -120,7 +120,7 @@ final class StreamingSubscriberConnection extends AbstractApiService implements private final String clientId = UUID.randomUUID().toString(); private final boolean enableOpenTelemetryTracing; - private final PubsubTracer tracer; + private PubsubTracer tracer = new BasePubsubTracer(); private StreamingSubscriberConnection(Builder builder) { subscription = builder.subscription; @@ -156,7 +156,9 @@ private StreamingSubscriberConnection(Builder builder) { } enableOpenTelemetryTracing = builder.enableOpenTelemetryTracing; - tracer = builder.tracer; + if (builder.tracer != null) { + tracer = builder.tracer; + } messageDispatcher = messageDispatcherBuilder From 22d95d427b71eb163c23866e33a69f6905c3e35f Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Thu, 19 Sep 2024 04:47:18 +0000 Subject: [PATCH 26/46] samples: Add OpenTelemetry publisher and subscriber samples --- samples/snippets/pom.xml | 5 + .../pubsub/OpenTelemetryPublisherExample.java | 99 +++++++++++++++++ .../OpenTelemetrySubscriberExample.java | 100 ++++++++++++++++++ 3 files changed, 204 insertions(+) create mode 100644 samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java create mode 100644 samples/snippets/src/main/java/pubsub/OpenTelemetrySubscriberExample.java diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index 5a0f18673..a31252af1 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -82,6 +82,11 @@ protobuf-java-util 4.27.4 + + com.google.cloud.opentelemetry + exporter-trace + 0.31.0 + junit diff --git a/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java b/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java new file mode 100644 index 000000000..606c6fe36 --- /dev/null +++ b/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java @@ -0,0 +1,99 @@ +/* + * 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 pubsub; + +// [START pubsub_publish_otel_tracing] + +import com.google.api.core.ApiFuture; +import com.google.cloud.opentelemetry.trace.TraceConfiguration; +import com.google.cloud.opentelemetry.trace.TraceExporter; +import com.google.cloud.pubsub.v1.Publisher; +import com.google.protobuf.ByteString; +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.TopicName; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.opentelemetry.semconv.ResourceAttributes; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +public class OpenTelemetryPublisherExample { + public static void main(String... args) throws Exception { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String topicId = "your-topic-id"; + + openTelemetryPublisherExample(projectId, topicId); + } + + public static void openTelemetryPublisherExample(String projectId, String topicId) + throws IOException, ExecutionException, InterruptedException { + Resource resource = + Resource.getDefault().toBuilder() + .put(ResourceAttributes.SERVICE_NAME, "publisher-example") + .build(); + + // Creates a Cloud Trace exporter. + SpanExporter traceExporter = + TraceExporter.createWithConfiguration( + TraceConfiguration.builder().setProjectId(projectId).build()); + + SdkTracerProvider sdkTracerProvider = + SdkTracerProvider.builder() + .setResource(resource) + .addSpanProcessor(SimpleSpanProcessor.create(traceExporter)) + .setSampler(Sampler.alwaysOn()) + .build(); + + OpenTelemetry openTelemetry = + OpenTelemetrySdk.builder().setTracerProvider(sdkTracerProvider).buildAndRegisterGlobal(); + + TopicName topicName = TopicName.of(projectId, topicId); + + Publisher publisher = null; + try { + // Create a publisher instance with the created OpenTelemetry object and enabling tracing. + publisher = + Publisher.newBuilder(topicName) + .setOpenTelemetry(openTelemetry) + .setEnableOpenTelemetryTracing(true) + .build(); + + String message = "Hello World!"; + ByteString data = ByteString.copyFromUtf8(message); + PubsubMessage pubsubMessage = PubsubMessage.newBuilder().setData(data).build(); + + // Once published, returns a server-assigned message id (unique within the topic) + ApiFuture messageIdFuture = publisher.publish(pubsubMessage); + String messageId = messageIdFuture.get(); + System.out.println("Published message ID: " + messageId); + } finally { + if (publisher != null) { + // When finished with the publisher, shutdown to free up resources. + publisher.shutdown(); + publisher.awaitTermination(1, TimeUnit.MINUTES); + } + } + } +} +// [END pubsub_publish_otel_tracing] diff --git a/samples/snippets/src/main/java/pubsub/OpenTelemetrySubscriberExample.java b/samples/snippets/src/main/java/pubsub/OpenTelemetrySubscriberExample.java new file mode 100644 index 000000000..f78c38d19 --- /dev/null +++ b/samples/snippets/src/main/java/pubsub/OpenTelemetrySubscriberExample.java @@ -0,0 +1,100 @@ +/* + * 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 pubsub; + +// [START pubsub_subscribe_otel_tracing] + +import com.google.cloud.opentelemetry.trace.TraceConfiguration; +import com.google.cloud.opentelemetry.trace.TraceExporter; +import com.google.cloud.pubsub.v1.AckReplyConsumer; +import com.google.cloud.pubsub.v1.MessageReceiver; +import com.google.cloud.pubsub.v1.Subscriber; +import com.google.pubsub.v1.ProjectSubscriptionName; +import com.google.pubsub.v1.PubsubMessage; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.opentelemetry.semconv.ResourceAttributes; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class OpenTelemetrySubscriberExample { + public static void main(String... args) throws Exception { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String subscriptionId = "your-subscription-id"; + + openTelemetrySubscriberExample(projectId, subscriptionId); + } + + public static void openTelemetrySubscriberExample(String projectId, String subscriptionId) { + Resource resource = + Resource.getDefault().toBuilder() + .put(ResourceAttributes.SERVICE_NAME, "subscriber-example") + .build(); + + // Creates a Cloud Trace exporter. + SpanExporter traceExporter = + TraceExporter.createWithConfiguration( + TraceConfiguration.builder().setProjectId(projectId).build()); + + SdkTracerProvider sdkTracerProvider = + SdkTracerProvider.builder() + .setResource(resource) + .addSpanProcessor(SimpleSpanProcessor.create(traceExporter)) + .setSampler(Sampler.alwaysOn()) + .build(); + + OpenTelemetry openTelemetry = + OpenTelemetrySdk.builder().setTracerProvider(sdkTracerProvider).buildAndRegisterGlobal(); + + ProjectSubscriptionName subscriptionName = + ProjectSubscriptionName.of(projectId, subscriptionId); + + // Instantiate an asynchronous message receiver. + MessageReceiver receiver = + (PubsubMessage message, AckReplyConsumer consumer) -> { + // Handle incoming message, then ack the received message. + System.out.println("Id: " + message.getMessageId()); + System.out.println("Data: " + message.getData().toStringUtf8()); + consumer.ack(); + }; + + Subscriber subscriber = null; + try { + subscriber = + Subscriber.newBuilder(subscriptionName, receiver) + .setOpenTelemetry(openTelemetry) + .setEnableOpenTelemetryTracing(true) + .build(); + + // Start the subscriber. + subscriber.startAsync().awaitRunning(); + System.out.printf("Listening for messages on %s:\n", subscriptionName.toString()); + // Allow the subscriber to run for 30s unless an unrecoverable error occurs. + subscriber.awaitTerminated(30, TimeUnit.SECONDS); + } catch (TimeoutException timeoutException) { + // Shut down the subscriber after 30s. Stop receiving messages. + subscriber.stopAsync(); + } + } +} + // [END pubsub_subscribe_otel_tracing] From 43d489f4c6d87736f45aef702b028e4077ce316b Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Thu, 19 Sep 2024 07:22:10 +0000 Subject: [PATCH 27/46] feat: Add additional sampling checks to the Otel implementation --- .../pubsub/v1/OpenTelemetryPubsubTracer.java | 10 ++++--- .../google/cloud/pubsub/v1/PubsubTracer.java | 3 ++- .../v1/StreamingSubscriberConnection.java | 27 +++++++++++++++---- .../cloud/pubsub/v1/OpenTelemetryTest.java | 6 ++--- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java index e8ca33a6f..7fb974f8e 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java @@ -158,7 +158,7 @@ public Span startPublishRpcSpan(String topic, List message Span publishRpcSpan = publishRpcSpanBuilder.startSpan(); for (PubsubMessageWrapper message : messages) { - if (message.getPublisherSpan().getSpanContext().isSampled()) { + if (publishRpcSpan.getSpanContext().isSampled()) { message.getPublisherSpan().addLink(publishRpcSpan.getSpanContext(), linkAttributes); message.addPublishStartEvent(); } @@ -336,7 +336,7 @@ public Span startSubscribeRpcSpan( Span rpcSpan = rpcSpanBuilder.startSpan(); for (PubsubMessageWrapper message : messages) { - if (message.getSubscriberSpan().getSpanContext().isSampled()) { + if (rpcSpan.getSpanContext().isSampled()) { message.getSubscriberSpan().addLink(rpcSpan.getSpanContext(), linkAttributes); switch (rpcOperation) { case "ack": @@ -379,7 +379,11 @@ public void setSubscribeRpcSpanException( /** Adds the appropriate subscribe-side RPC end event. */ @Override - public void addEndRpcEvent(PubsubMessageWrapper message, boolean isModack, int ackDeadline) { + public void addEndRpcEvent( + PubsubMessageWrapper message, boolean rpcSampled, boolean isModack, int ackDeadline) { + if (!rpcSampled) { + return; + } if (!isModack) { message.addAckEndEvent(); } else if (ackDeadline == 0) { diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubTracer.java index 98003235c..70123f98b 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubTracer.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubTracer.java @@ -131,7 +131,8 @@ default void setSubscribeRpcSpanException( // noop } - default void addEndRpcEvent(PubsubMessageWrapper message, boolean isModack, int ackDeadline) { + default void addEndRpcEvent( + PubsubMessageWrapper message, boolean rpcSampled, boolean isModack, int ackDeadline) { // noop } } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java index f7b4279bb..f33d2243d 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java @@ -553,6 +553,8 @@ private ApiFutureCallback getCallback( // Check if ack or nack boolean setResponseOnSuccess = (!isModack || (deadlineExtensionSeconds == 0)) ? true : false; + boolean rpcSpanSampled = rpcSpan == null ? false : rpcSpan.getSpanContext().isSampled(); + return new ApiFutureCallback() { @Override public void onSuccess(Empty empty) { @@ -567,7 +569,10 @@ public void onSuccess(Empty empty) { // Remove from our pending operations pendingRequests.remove(ackRequestData); tracer.addEndRpcEvent( - ackRequestData.getMessageWrapper(), isModack, deadlineExtensionSeconds); + ackRequestData.getMessageWrapper(), + rpcSpanSampled, + isModack, + deadlineExtensionSeconds); if (!isModack || deadlineExtensionSeconds == 0) { tracer.endSubscriberSpan(ackRequestData.getMessageWrapper()); } @@ -588,7 +593,10 @@ public void onFailure(Throwable t) { if (enableOpenTelemetryTracing) { for (AckRequestData ackRequestData : ackRequestDataList) { tracer.addEndRpcEvent( - ackRequestData.getMessageWrapper(), isModack, deadlineExtensionSeconds); + ackRequestData.getMessageWrapper(), + rpcSpanSampled, + isModack, + deadlineExtensionSeconds); if (!isModack || deadlineExtensionSeconds == 0) { tracer.endSubscriberSpan(ackRequestData.getMessageWrapper()); } @@ -618,7 +626,10 @@ public void onFailure(Throwable t) { ackRequestData.setResponse(AckResponse.INVALID, setResponseOnSuccess); messageDispatcher.notifyAckFailed(ackRequestData); tracer.addEndRpcEvent( - ackRequestData.getMessageWrapper(), isModack, deadlineExtensionSeconds); + ackRequestData.getMessageWrapper(), + rpcSpanSampled, + isModack, + deadlineExtensionSeconds); tracer.setSubscriberSpanException( ackRequestData.getMessageWrapper(), t, "Invalid ack ID"); } else { @@ -626,7 +637,10 @@ public void onFailure(Throwable t) { ackRequestData.setResponse(AckResponse.OTHER, setResponseOnSuccess); messageDispatcher.notifyAckFailed(ackRequestData); tracer.addEndRpcEvent( - ackRequestData.getMessageWrapper(), isModack, deadlineExtensionSeconds); + ackRequestData.getMessageWrapper(), + rpcSpanSampled, + isModack, + deadlineExtensionSeconds); tracer.setSubscriberSpanException( ackRequestData.getMessageWrapper(), t, "Unknown error message"); ackRequestData @@ -638,7 +652,10 @@ public void onFailure(Throwable t) { messageDispatcher.notifyAckSuccess(ackRequestData); tracer.endSubscriberSpan(ackRequestData.getMessageWrapper()); tracer.addEndRpcEvent( - ackRequestData.getMessageWrapper(), isModack, deadlineExtensionSeconds); + ackRequestData.getMessageWrapper(), + rpcSpanSampled, + isModack, + deadlineExtensionSeconds); } // Remove from our pending pendingRequests.remove(ackRequestData); diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java index 76f3aba09..204e12e3d 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -333,17 +333,17 @@ public void testSubscribeSpansSuccess() { ACK_DEADLINE, true); tracer.endSubscribeRpcSpan(subscribeModackRpcSpan); - tracer.addEndRpcEvent(subscribeMessageWrapper, true, ACK_DEADLINE); + tracer.addEndRpcEvent(subscribeMessageWrapper, true, true, ACK_DEADLINE); Span subscribeAckRpcSpan = tracer.startSubscribeRpcSpan( FULL_SUBSCRIPTION_NAME.toString(), "ack", subscribeMessageWrappers, 0, false); tracer.endSubscribeRpcSpan(subscribeAckRpcSpan); - tracer.addEndRpcEvent(subscribeMessageWrapper, false, 0); + tracer.addEndRpcEvent(subscribeMessageWrapper, true, false, 0); Span subscribeNackRpcSpan = tracer.startSubscribeRpcSpan( FULL_SUBSCRIPTION_NAME.toString(), "nack", subscribeMessageWrappers, 0, false); tracer.endSubscribeRpcSpan(subscribeNackRpcSpan); - tracer.addEndRpcEvent(subscribeMessageWrapper, true, 0); + tracer.addEndRpcEvent(subscribeMessageWrapper, true, true, 0); tracer.endSubscriberSpan(subscribeMessageWrapper); List allSpans = openTelemetryTesting.getSpans(); From 79dd118f2d071b82260ea5fd5ca628f243f4aa8e Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Thu, 19 Sep 2024 16:09:16 +0000 Subject: [PATCH 28/46] samples: Update pom.xml for samples with Cloud Trace exporter --- samples/install-without-bom/pom.xml | 5 +++++ samples/snapshot/pom.xml | 5 +++++ samples/snippets/pom.xml | 10 +++++----- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 0d38d3809..4452beece 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -93,6 +93,11 @@ google-cloud-storage 2.42.0 + + com.google.cloud.opentelemetry + exporter-trace + 0.31.0 + diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index e3c42ecb2..1d24d0307 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -92,6 +92,11 @@ google-cloud-storage 2.42.0 + + com.google.cloud.opentelemetry + exporter-trace + 0.31.0 + diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index a31252af1..dee52018d 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -67,6 +67,11 @@ com.google.cloud google-cloud-storage + + com.google.cloud.opentelemetry + exporter-trace + 0.31.0 + org.apache.avro avro @@ -82,11 +87,6 @@ protobuf-java-util 4.27.4 - - com.google.cloud.opentelemetry - exporter-trace - 0.31.0 - junit From fe4753f35cf22a082c8b76c0c43d5d7ce3ff295f Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Wed, 25 Sep 2024 00:17:48 +0000 Subject: [PATCH 29/46] feat: Make OTel classes/methods package-private and remove non-generic PubsubTracer interface --- .../cloud/pubsub/v1/BasePubsubTracer.java | 21 -- .../cloud/pubsub/v1/MessageDispatcher.java | 6 +- .../pubsub/v1/OpenTelemetryPubsubTracer.java | 191 ++++++++++++------ .../cloud/pubsub/v1/OpenTelemetryUtil.java | 42 ---- .../com/google/cloud/pubsub/v1/Publisher.java | 4 +- .../cloud/pubsub/v1/PubsubMessageWrapper.java | 92 +++++---- .../google/cloud/pubsub/v1/PubsubTracer.java | 138 ------------- .../v1/StreamingSubscriberConnection.java | 6 +- .../google/cloud/pubsub/v1/Subscriber.java | 4 +- .../cloud/pubsub/v1/OpenTelemetryTest.java | 14 +- 10 files changed, 192 insertions(+), 326 deletions(-) delete mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/BasePubsubTracer.java delete mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java delete mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubTracer.java diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/BasePubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/BasePubsubTracer.java deleted file mode 100644 index f574ee163..000000000 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/BasePubsubTracer.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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.pubsub.v1; - -public class BasePubsubTracer implements PubsubTracer { - BasePubsubTracer() {} -} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java index 9695e542d..860fcbcf9 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageDispatcher.java @@ -106,7 +106,7 @@ class MessageDispatcher { private final String subscriptionName; private final boolean enableOpenTelemetryTracing; - private PubsubTracer tracer = new BasePubsubTracer(); + private OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(null, false); /** Internal representation of a reply to a Pubsub message, to be sent back to the service. */ public enum AckReply { @@ -688,7 +688,7 @@ public static final class Builder { private String subscriptionName; private boolean enableOpenTelemetryTracing; - private PubsubTracer tracer; + private OpenTelemetryPubsubTracer tracer; protected Builder(MessageReceiver receiver) { this.receiver = receiver; @@ -770,7 +770,7 @@ public Builder setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing) return this; } - public Builder setTracer(PubsubTracer tracer) { + public Builder setTracer(OpenTelemetryPubsubTracer tracer) { this.tracer = tracer; return this; } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java index 7fb974f8e..fda28898f 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java @@ -16,8 +16,6 @@ package com.google.cloud.pubsub.v1; -import com.google.api.core.InternalApi; -import com.google.common.base.Preconditions; import com.google.pubsub.v1.PubsubMessage; import com.google.pubsub.v1.SubscriptionName; import com.google.pubsub.v1.TopicName; @@ -32,9 +30,9 @@ import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.List; -@InternalApi("For use by the google-cloud-pubsub library only") -public class OpenTelemetryPubsubTracer implements PubsubTracer { +public class OpenTelemetryPubsubTracer { private final Tracer tracer; + private boolean enabled = false; private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; @@ -51,12 +49,32 @@ public class OpenTelemetryPubsubTracer implements PubsubTracer { "messaging.gcp_pubsub.message.delivery_attempt"; private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; + private static final String PROJECT_ATTR_KEY = "gcp.project_id"; private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; - OpenTelemetryPubsubTracer(Tracer tracer) { - this.tracer = Preconditions.checkNotNull(tracer, "OpenTelemetry tracer cannot be null"); + OpenTelemetryPubsubTracer(Tracer tracer, boolean enableOpenTelemetry) { + this.tracer = tracer; + if (this.tracer != null && enableOpenTelemetry) { + this.enabled = true; + } + } + + /** Populates attributes that are common the publisher parent span and publish RPC span. */ + private static final AttributesBuilder createCommonSpanAttributesBuilder( + String destinationName, String projectName, String codeFunction, String operation) { + AttributesBuilder attributesBuilder = + Attributes.builder() + .put(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .put(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName) + .put(PROJECT_ATTR_KEY, projectName) + .put(SemanticAttributes.CODE_FUNCTION, codeFunction); + if (operation != null) { + attributesBuilder.put(SemanticAttributes.MESSAGING_OPERATION, operation); + } + + return attributesBuilder; } private Span startChildSpan(String name, Span parent) { @@ -67,10 +85,12 @@ private Span startChildSpan(String name, Span parent) { * Creates and starts the parent span with the appropriate span attributes and injects the span * context into the {@link PubsubMessage} attributes. */ - @Override - public void startPublisherSpan(PubsubMessageWrapper message) { + void startPublisherSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } AttributesBuilder attributesBuilder = - OpenTelemetryUtil.createCommonSpanAttributesBuilder( + createCommonSpanAttributesBuilder( message.getTopicName(), message.getTopicProject(), "publish", "create"); attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()); @@ -91,44 +111,60 @@ public void startPublisherSpan(PubsubMessageWrapper message) { } } - public void endPublisherSpan(PubsubMessageWrapper message) { + void endPublisherSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } message.endPublisherSpan(); } - public void setPublisherMessageIdSpanAttribute(PubsubMessageWrapper message, String messageId) { + void setPublisherMessageIdSpanAttribute(PubsubMessageWrapper message, String messageId) { + if (!enabled) { + return; + } message.setPublisherMessageIdSpanAttribute(messageId); } /** Creates a span for publish-side flow control as a child of the parent publisher span. */ - @Override - public void startPublishFlowControlSpan(PubsubMessageWrapper message) { + void startPublishFlowControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } Span publisherSpan = message.getPublisherSpan(); if (publisherSpan != null) message.setPublishFlowControlSpan( startChildSpan(PUBLISH_FLOW_CONTROL_SPAN_NAME, publisherSpan)); } - @Override - public void endPublishFlowControlSpan(PubsubMessageWrapper message) { + void endPublishFlowControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } message.endPublishFlowControlSpan(); } - @Override - public void setPublishFlowControlSpanException(PubsubMessageWrapper message, Throwable t) { + void setPublishFlowControlSpanException(PubsubMessageWrapper message, Throwable t) { + if (!enabled) { + return; + } message.setPublishFlowControlSpanException(t); } /** Creates a span for publish message batching as a child of the parent publisher span. */ - @Override - public void startPublishBatchingSpan(PubsubMessageWrapper message) { + void startPublishBatchingSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } Span publisherSpan = message.getPublisherSpan(); if (publisherSpan != null) { message.setPublishBatchingSpan(startChildSpan(PUBLISH_BATCHING_SPAN_NAME, publisherSpan)); } } - @Override - public void endPublishBatchingSpan(PubsubMessageWrapper message) { + void endPublishBatchingSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } message.endPublishBatchingSpan(); } @@ -136,11 +172,13 @@ public void endPublishBatchingSpan(PubsubMessageWrapper message) { * Creates, starts, and returns a publish RPC span for the given message batch. Bi-directional * links with the publisher parent span are created for sampled messages in the batch. */ - @Override - public Span startPublishRpcSpan(String topic, List messages) { + Span startPublishRpcSpan(String topic, List messages) { + if (!enabled) { + return null; + } TopicName topicName = TopicName.parse(topic); Attributes attributes = - OpenTelemetryUtil.createCommonSpanAttributesBuilder( + createCommonSpanAttributesBuilder( topicName.getTopic(), topicName.getProject(), "publishCall", "publish") .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()) .build(); @@ -167,8 +205,10 @@ public Span startPublishRpcSpan(String topic, List message } /** Ends the given publish RPC span if it exists. */ - @Override - public void endPublishRpcSpan(Span publishRpcSpan) { + void endPublishRpcSpan(Span publishRpcSpan) { + if (!enabled) { + return; + } if (publishRpcSpan != null) { publishRpcSpan.end(); } @@ -178,8 +218,10 @@ public void endPublishRpcSpan(Span publishRpcSpan) { * Sets an error status and records an exception when an exception is thrown when publishing the * message batch. */ - @Override - public void setPublishRpcSpanException(Span publishRpcSpan, Throwable t) { + void setPublishRpcSpanException(Span publishRpcSpan, Throwable t) { + if (!enabled) { + return; + } if (publishRpcSpan != null) { publishRpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on publish RPC."); publishRpcSpan.recordException(t); @@ -187,11 +229,13 @@ public void setPublishRpcSpanException(Span publishRpcSpan, Throwable t) { } } - @Override - public void startSubscriberSpan( + void startSubscriberSpan( PubsubMessageWrapper message, boolean exactlyOnceDeliveryEnabled) { + if (!enabled) { + return; + } AttributesBuilder attributesBuilder = - OpenTelemetryUtil.createCommonSpanAttributesBuilder( + createCommonSpanAttributesBuilder( message.getSubscriptionName(), message.getSubscriptionProject(), "onResponse", null); attributesBuilder @@ -217,25 +261,33 @@ public void startSubscriberSpan( .startSpan()); } - @Override - public void endSubscriberSpan(PubsubMessageWrapper message) { + void endSubscriberSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } message.endSubscriberSpan(); } - @Override - public void setSubscriberSpanExpirationResult(PubsubMessageWrapper message) { + void setSubscriberSpanExpirationResult(PubsubMessageWrapper message) { + if (!enabled) { + return; + } message.setSubscriberSpanExpirationResult(); } - @Override - public void setSubscriberSpanException( + void setSubscriberSpanException( PubsubMessageWrapper message, Throwable t, String exception) { + if (!enabled) { + return; + } message.setSubscriberSpanException(t, exception); } /** Creates a span for subscribe concurrency control as a child of the parent subscriber span. */ - @Override - public void startSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { + void startSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } Span subscriberSpan = message.getSubscriberSpan(); if (subscriberSpan != null) { message.setSubscribeConcurrencyControlSpan( @@ -243,22 +295,28 @@ public void startSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { } } - @Override - public void endSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { + void endSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } message.endSubscribeConcurrencyControlSpan(); } - @Override - public void setSubscribeConcurrencyControlSpanException( + void setSubscribeConcurrencyControlSpanException( PubsubMessageWrapper message, Throwable t) { + if (!enabled) { + return; + } message.setSubscribeConcurrencyControlSpanException(t); } /** * Creates a span for subscribe ordering key scheduling as a child of the parent subscriber span. */ - @Override - public void startSubscribeSchedulerSpan(PubsubMessageWrapper message) { + void startSubscribeSchedulerSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } Span subscriberSpan = message.getSubscriberSpan(); if (subscriberSpan != null) { message.setSubscribeSchedulerSpan( @@ -266,14 +324,18 @@ public void startSubscribeSchedulerSpan(PubsubMessageWrapper message) { } } - @Override - public void endSubscribeSchedulerSpan(PubsubMessageWrapper message) { + void endSubscribeSchedulerSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } message.endSubscribeSchedulerSpan(); } /** Creates a span for subscribe message processing as a child of the parent subscriber span. */ - @Override - public void startSubscribeProcessSpan(PubsubMessageWrapper message) { + void startSubscribeProcessSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } Span subscriberSpan = message.getSubscriberSpan(); if (subscriberSpan != null) { Span subscribeProcessSpan = @@ -288,8 +350,10 @@ public void startSubscribeProcessSpan(PubsubMessageWrapper message) { } } - @Override - public void endSubscribeProcessSpan(PubsubMessageWrapper message, String action) { + void endSubscribeProcessSpan(PubsubMessageWrapper message, String action) { + if (!enabled) { + return; + } message.endSubscribeProcessSpan(action); } @@ -297,17 +361,19 @@ public void endSubscribeProcessSpan(PubsubMessageWrapper message, String action) * Creates, starts, and returns spans for ModAck, Nack, and Ack RPC requests. Bi-directional links * to parent subscribe span for sampled messages are added. */ - @Override - public Span startSubscribeRpcSpan( + Span startSubscribeRpcSpan( String subscription, String rpcOperation, List messages, int ackDeadline, boolean isReceiptModack) { + if (!enabled) { + return null; + } String codeFunction = rpcOperation == "ack" ? "sendAckOperations" : "sendModAckOperations"; SubscriptionName subscriptionName = SubscriptionName.parse(subscription); AttributesBuilder attributesBuilder = - OpenTelemetryUtil.createCommonSpanAttributesBuilder( + createCommonSpanAttributesBuilder( subscriptionName.getSubscription(), subscriptionName.getProject(), codeFunction, @@ -355,8 +421,10 @@ public Span startSubscribeRpcSpan( } /** Ends the given subscribe RPC span if it exists. */ - @Override - public void endSubscribeRpcSpan(Span rpcSpan) { + void endSubscribeRpcSpan(Span rpcSpan) { + if (!enabled) { + return; + } if (rpcSpan != null) { rpcSpan.end(); } @@ -366,9 +434,11 @@ public void endSubscribeRpcSpan(Span rpcSpan) { * Sets an error status and records an exception when an exception is thrown when handling a * subscribe-side RPC. */ - @Override - public void setSubscribeRpcSpanException( + void setSubscribeRpcSpanException( Span rpcSpan, boolean isModack, int ackDeadline, Throwable t) { + if (!enabled) { + return; + } if (rpcSpan != null) { String operation = !isModack ? "ack" : (ackDeadline == 0 ? "nack" : "modack"); rpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on " + operation + " RPC."); @@ -378,10 +448,9 @@ public void setSubscribeRpcSpanException( } /** Adds the appropriate subscribe-side RPC end event. */ - @Override - public void addEndRpcEvent( + void addEndRpcEvent( PubsubMessageWrapper message, boolean rpcSampled, boolean isModack, int ackDeadline) { - if (!rpcSampled) { + if (!enabled || !rpcSampled) { return; } if (!isModack) { diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java deleted file mode 100644 index c100675b5..000000000 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryUtil.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.pubsub.v1; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; - -public class OpenTelemetryUtil { - private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; - private static final String PROJECT_ATTR_KEY = "gcp.project_id"; - - /** Populates attributes that are common the publisher parent span and publish RPC span. */ - protected static final AttributesBuilder createCommonSpanAttributesBuilder( - String destinationName, String projectName, String codeFunction, String operation) { - AttributesBuilder attributesBuilder = - Attributes.builder() - .put(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .put(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName) - .put(PROJECT_ATTR_KEY, projectName) - .put(SemanticAttributes.CODE_FUNCTION, codeFunction); - if (operation != null) { - attributesBuilder.put(SemanticAttributes.MESSAGING_OPERATION, operation); - } - - return attributesBuilder; - } -} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java index 5cd20235d..a4cfdc257 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java @@ -131,7 +131,7 @@ public class Publisher implements PublisherInterface { private final boolean enableOpenTelemetryTracing; private final OpenTelemetry openTelemetry; - private PubsubTracer tracer = new BasePubsubTracer(); + private OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(null, false); /** The maximum number of messages in one request. Defined by the API. */ public static long getApiMaxRequestElementCount() { @@ -166,7 +166,7 @@ private Publisher(Builder builder) throws IOException { if (this.openTelemetry != null && this.enableOpenTelemetryTracing) { Tracer openTelemetryTracer = builder.openTelemetry.getTracer(OPEN_TELEMETRY_TRACER_NAME); if (openTelemetryTracer != null) { - this.tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer); + this.tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, this.enableOpenTelemetryTracing); } } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java index 53cf95544..94fd13085 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java @@ -16,7 +16,6 @@ package com.google.cloud.pubsub.v1; -import com.google.api.core.InternalApi; import com.google.pubsub.v1.PubsubMessage; import com.google.pubsub.v1.SubscriptionName; import com.google.pubsub.v1.TopicName; @@ -66,8 +65,7 @@ public class PubsubMessageWrapper { private Span subscribeSchedulerSpan; private Span subscribeProcessSpan; - @InternalApi("For use by the google-cloud-pubsub library only") - public PubsubMessageWrapper(Builder builder) { + private PubsubMessageWrapper(Builder builder) { this.message = builder.message; this.topicName = builder.topicName; this.subscriptionName = builder.subscriptionName; @@ -75,33 +73,33 @@ public PubsubMessageWrapper(Builder builder) { this.deliveryAttempt = builder.deliveryAttempt; } - public static Builder newBuilder(PubsubMessage message, String topicName) { + static Builder newBuilder(PubsubMessage message, String topicName) { return new Builder(message, topicName); } - public static Builder newBuilder( + static Builder newBuilder( PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { return new Builder(message, subscriptionName, ackId, deliveryAttempt); } /** Returns the PubsubMessage associated with this wrapper. */ - protected PubsubMessage getPubsubMessage() { + PubsubMessage getPubsubMessage() { return message; } - protected void setPubsubMessage(PubsubMessage message) { + void setPubsubMessage(PubsubMessage message) { this.message = message; } /** Returns the TopicName for this wrapper as a string. */ - protected String getTopicName() { + String getTopicName() { if (topicName != null) { return topicName.getTopic(); } return ""; } - protected String getTopicProject() { + String getTopicProject() { if (topicName != null) { return topicName.getProject(); } @@ -109,78 +107,78 @@ protected String getTopicProject() { } /** Returns the SubscriptionName for this wrapper as a string. */ - protected String getSubscriptionName() { + String getSubscriptionName() { if (subscriptionName != null) { return subscriptionName.getSubscription(); } return ""; } - protected String getSubscriptionProject() { + String getSubscriptionProject() { if (subscriptionName != null) { return subscriptionName.getProject(); } return ""; } - protected String getMessageId() { + String getMessageId() { return message.getMessageId(); } - protected String getAckId() { + String getAckId() { return ackId; } - protected int getDataSize() { + int getDataSize() { return message.getData().size(); } - protected String getOrderingKey() { + String getOrderingKey() { return message.getOrderingKey(); } - protected int getDeliveryAttempt() { + int getDeliveryAttempt() { return deliveryAttempt; } - protected Span getPublisherSpan() { + Span getPublisherSpan() { return publisherSpan; } - protected void setPublisherSpan(Span span) { + void setPublisherSpan(Span span) { this.publisherSpan = span; } - protected void setPublishFlowControlSpan(Span span) { + void setPublishFlowControlSpan(Span span) { this.publishFlowControlSpan = span; } - protected void setPublishBatchingSpan(Span span) { + void setPublishBatchingSpan(Span span) { this.publishBatchingSpan = span; } - protected Span getSubscriberSpan() { + Span getSubscriberSpan() { return subscriberSpan; } - protected void setSubscriberSpan(Span span) { + void setSubscriberSpan(Span span) { this.subscriberSpan = span; } - protected void setSubscribeConcurrencyControlSpan(Span span) { + void setSubscribeConcurrencyControlSpan(Span span) { this.subscribeConcurrencyControlSpan = span; } - protected void setSubscribeSchedulerSpan(Span span) { + void setSubscribeSchedulerSpan(Span span) { this.subscribeSchedulerSpan = span; } - protected void setSubscribeProcessSpan(Span span) { + void setSubscribeProcessSpan(Span span) { this.subscribeProcessSpan = span; } /** Creates a publish start event that is tied to the publish RPC span time. */ - protected void addPublishStartEvent() { + void addPublishStartEvent() { if (publisherSpan != null) { publisherSpan.addEvent(PUBLISH_START_EVENT); } @@ -190,14 +188,14 @@ protected void addPublishStartEvent() { * Sets the message ID attribute in the publisher parent span. This is called after the publish * RPC returns with a message ID. */ - protected void setPublisherMessageIdSpanAttribute(String messageId) { + void setPublisherMessageIdSpanAttribute(String messageId) { if (publisherSpan != null) { publisherSpan.setAttribute(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId); } } /** Ends the publisher parent span if it exists. */ - protected void endPublisherSpan() { + void endPublisherSpan() { if (publisherSpan != null) { publisherSpan.addEvent(PUBLISH_END_EVENT); publisherSpan.end(); @@ -205,14 +203,14 @@ protected void endPublisherSpan() { } /** Ends the publish flow control span if it exists. */ - protected void endPublishFlowControlSpan() { + void endPublishFlowControlSpan() { if (publishFlowControlSpan != null) { publishFlowControlSpan.end(); } } /** Ends the publish batching span if it exists. */ - protected void endPublishBatchingSpan() { + void endPublishBatchingSpan() { if (publishBatchingSpan != null) { publishBatchingSpan.end(); } @@ -221,7 +219,7 @@ protected void endPublishBatchingSpan() { /** * Sets an error status and records an exception when an exception is thrown during flow control. */ - protected void setPublishFlowControlSpanException(Throwable t) { + void setPublishFlowControlSpanException(Throwable t) { if (publishFlowControlSpan != null) { publishFlowControlSpan.setStatus( StatusCode.ERROR, "Exception thrown during publish flow control."); @@ -234,58 +232,58 @@ protected void setPublishFlowControlSpanException(Throwable t) { * Creates start and end events for ModAcks, Nacks, and Acks that are tied to the corresponding * RPC span start and end times. */ - protected void addModAckStartEvent() { + void addModAckStartEvent() { if (subscriberSpan != null) { subscriberSpan.addEvent(MODACK_START_EVENT); } } - protected void addModAckEndEvent() { + void addModAckEndEvent() { if (subscriberSpan != null) { subscriberSpan.addEvent(MODACK_END_EVENT); } } - protected void addNackStartEvent() { + void addNackStartEvent() { if (subscriberSpan != null) { subscriberSpan.addEvent(NACK_START_EVENT); } } - protected void addNackEndEvent() { + void addNackEndEvent() { if (subscriberSpan != null) { subscriberSpan.addEvent(NACK_END_EVENT); } } - protected void addAckStartEvent() { + void addAckStartEvent() { if (subscriberSpan != null) { subscriberSpan.addEvent(ACK_START_EVENT); } } - protected void addAckEndEvent() { + void addAckEndEvent() { if (subscriberSpan != null) { subscriberSpan.addEvent(ACK_END_EVENT); } } /** Ends the subscriber parent span if exists. */ - protected void endSubscriberSpan() { + void endSubscriberSpan() { if (subscriberSpan != null) { subscriberSpan.end(); } } /** Ends the subscribe concurreny control span if exists. */ - protected void endSubscribeConcurrencyControlSpan() { + void endSubscribeConcurrencyControlSpan() { if (subscribeConcurrencyControlSpan != null) { subscribeConcurrencyControlSpan.end(); } } /** Ends the subscribe scheduler span if exists. */ - protected void endSubscribeSchedulerSpan() { + void endSubscribeSchedulerSpan() { if (subscribeSchedulerSpan != null) { subscribeSchedulerSpan.end(); } @@ -295,7 +293,7 @@ protected void endSubscribeSchedulerSpan() { * Ends the subscribe process span if it exists, creates an event with the appropriate result, and * sets the result on the parent subscriber span. */ - protected void endSubscribeProcessSpan(String action) { + void endSubscribeProcessSpan(String action) { if (subscribeProcessSpan != null) { subscribeProcessSpan.addEvent(action + " called"); subscribeProcessSpan.end(); @@ -304,7 +302,7 @@ protected void endSubscribeProcessSpan(String action) { } /** Sets an exception on the subscriber span during Ack/ModAck/Nack failures */ - protected void setSubscriberSpanException(Throwable t, String exception) { + void setSubscriberSpanException(Throwable t, String exception) { if (subscriberSpan != null) { subscriberSpan.setStatus(StatusCode.ERROR, exception); subscriberSpan.recordException(t); @@ -313,7 +311,7 @@ protected void setSubscriberSpanException(Throwable t, String exception) { } /** Sets result of the parent subscriber span to expired and ends its. */ - protected void setSubscriberSpanExpirationResult() { + void setSubscriberSpanExpirationResult() { if (subscriberSpan != null) { subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, "expired"); endSubscriberSpan(); @@ -324,7 +322,7 @@ protected void setSubscriberSpanExpirationResult() { * Sets an error status and records an exception when an exception is thrown subscriber * concurrency control. */ - protected void setSubscribeConcurrencyControlSpanException(Throwable t) { + void setSubscribeConcurrencyControlSpanException(Throwable t) { if (subscribeConcurrencyControlSpan != null) { subscribeConcurrencyControlSpan.setStatus( StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); @@ -351,7 +349,7 @@ private void endAllSubscribeSpans() { * Injects the span context into the attributes of a Pub/Sub message for propagation to the * subscriber client. */ - protected void injectSpanContext() { + void injectSpanContext() { TextMapSetter injectMessageAttributes = new TextMapSetter() { @Override @@ -371,7 +369,7 @@ public void set(PubsubMessageWrapper carrier, String key, String value) { * Extracts the span context from the attributes of a Pub/Sub message and creates the parent * subscriber span using that context. */ - protected Context extractSpanContext(Attributes attributes) { + Context extractSpanContext(Attributes attributes) { TextMapGetter extractMessageAttributes = new TextMapGetter() { @Override @@ -390,7 +388,7 @@ public Iterable keys(PubsubMessageWrapper carrier) { } /** Builder of {@link PubsubMessageWrapper PubsubMessageWrapper}. */ - protected static final class Builder { + static final class Builder { private PubsubMessage message = null; private TopicName topicName = null; private SubscriptionName subscriptionName = null; diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubTracer.java deleted file mode 100644 index 70123f98b..000000000 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubTracer.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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.pubsub.v1; - -import io.opentelemetry.api.trace.Span; -import java.util.List; - -public interface PubsubTracer { - default void startPublisherSpan(PubsubMessageWrapper message) { - // noop - } - - default void endPublisherSpan(PubsubMessageWrapper message) { - // noop - } - - default void setPublisherMessageIdSpanAttribute(PubsubMessageWrapper message, String messageId) { - // noop - } - - default void startPublishFlowControlSpan(PubsubMessageWrapper message) { - // noop - } - - default void endPublishFlowControlSpan(PubsubMessageWrapper message) { - // noop - } - - default void setPublishFlowControlSpanException(PubsubMessageWrapper message, Throwable t) { - // noop - } - - default void startPublishBatchingSpan(PubsubMessageWrapper message) { - // noop - } - - default void endPublishBatchingSpan(PubsubMessageWrapper message) { - // noop - } - - default Span startPublishRpcSpan(String topic, List messages) { - // noop - return null; - } - - default void endPublishRpcSpan(Span publishRpcSpan) { - // noop - } - - default void setPublishRpcSpanException(Span publishRpcSpan, Throwable t) { - // noop - } - - default void startSubscriberSpan( - PubsubMessageWrapper message, boolean exactlyOnceDeliveryEnabled) { - // noop - } - - default void endSubscriberSpan(PubsubMessageWrapper message) { - // noop - } - - default void setSubscriberSpanExpirationResult(PubsubMessageWrapper message) { - // noop - } - - default void setSubscriberSpanException( - PubsubMessageWrapper message, Throwable t, String exception) { - // noop - } - - default void startSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { - // noop - } - - default void endSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { - // noop - } - - default void setSubscribeConcurrencyControlSpanException( - PubsubMessageWrapper message, Throwable t) { - // noop - } - - default void startSubscribeSchedulerSpan(PubsubMessageWrapper message) { - // noop - } - - default void endSubscribeSchedulerSpan(PubsubMessageWrapper message) { - // noop - } - - default void startSubscribeProcessSpan(PubsubMessageWrapper message) { - // noop - } - - default void endSubscribeProcessSpan(PubsubMessageWrapper message, String action) { - // noop - } - - default Span startSubscribeRpcSpan( - String subscription, - String rpcOperation, - List messages, - int ackDeadline, - boolean isReceiptModack) { - // noop - return null; - } - - default void endSubscribeRpcSpan(Span rpcSpan) { - // noop - } - - default void setSubscribeRpcSpanException( - Span rpcSpan, boolean isModack, int ackDeadline, Throwable t) { - // noop - } - - default void addEndRpcEvent( - PubsubMessageWrapper message, boolean rpcSampled, boolean isModack, int ackDeadline) { - // noop - } -} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java index f33d2243d..60da55cee 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java @@ -120,7 +120,7 @@ final class StreamingSubscriberConnection extends AbstractApiService implements private final String clientId = UUID.randomUUID().toString(); private final boolean enableOpenTelemetryTracing; - private PubsubTracer tracer = new BasePubsubTracer(); + private OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(null, false); private StreamingSubscriberConnection(Builder builder) { subscription = builder.subscription; @@ -717,7 +717,7 @@ public static final class Builder { private ApiClock clock; private boolean enableOpenTelemetryTracing; - private PubsubTracer tracer; + private OpenTelemetryPubsubTracer tracer; protected Builder(MessageReceiver receiver) { this.receiver = receiver; @@ -814,7 +814,7 @@ public Builder setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing) return this; } - public Builder setTracer(PubsubTracer tracer) { + public Builder setTracer(OpenTelemetryPubsubTracer tracer) { this.tracer = tracer; return this; } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java index a6413389f..8f4e1a464 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java @@ -151,7 +151,7 @@ public class Subscriber extends AbstractApiService implements SubscriberInterfac private final boolean enableOpenTelemetryTracing; private final OpenTelemetry openTelemetry; - private PubsubTracer tracer = new BasePubsubTracer(); + private OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(null, false); private Subscriber(Builder builder) { receiver = builder.receiver; @@ -212,7 +212,7 @@ private Subscriber(Builder builder) { if (this.openTelemetry != null && this.enableOpenTelemetryTracing) { Tracer openTelemetryTracer = builder.openTelemetry.getTracer(OPEN_TELEMETRY_TRACER_NAME); if (openTelemetryTracer != null) { - this.tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer); + this.tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, this.enableOpenTelemetryTracing); } } diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java index 204e12e3d..b4433f41e 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -109,7 +109,7 @@ public void testPublishSpansSuccess() { long messageSize = messageWrapper.getPubsubMessage().getData().size(); Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - PubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); // Call all span start/end methods in the expected order tracer.startPublisherSpan(messageWrapper); @@ -221,7 +221,7 @@ public void testPublishFlowControlSpanFailure() { PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - PubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); tracer.startPublisherSpan(messageWrapper); tracer.startPublishFlowControlSpan(messageWrapper); @@ -262,7 +262,7 @@ public void testPublishRpcSpanFailure() { List messageWrappers = Arrays.asList(messageWrapper); Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - PubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); tracer.startPublisherSpan(messageWrapper); Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); @@ -299,7 +299,7 @@ public void testSubscribeSpansSuccess() { openTelemetryTesting.clearSpans(); Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - PubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); PubsubMessageWrapper publishMessageWrapper = PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); @@ -522,7 +522,7 @@ public void testSubscribeConcurrencyControlSpanFailure() { .build(); Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - PubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); tracer.startSubscribeConcurrencyControlSpan(messageWrapper); @@ -566,7 +566,7 @@ public void testSubscriberSpanFailure() { .build(); Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - PubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); @@ -600,7 +600,7 @@ public void testSubscribeRpcSpanFailures() { List messageWrappers = Arrays.asList(messageWrapper); Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - PubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); Span subscribeModackRpcSpan = From a08d169365d0fbdc9a9984d810e1bdc454f8c485 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Wed, 25 Sep 2024 00:22:49 +0000 Subject: [PATCH 30/46] feat: Lint fixes for Pub/Sub --- .../cloud/pubsub/v1/OpenTelemetryPubsubTracer.java | 12 ++++-------- .../java/com/google/cloud/pubsub/v1/Publisher.java | 3 ++- .../java/com/google/cloud/pubsub/v1/Subscriber.java | 5 +++-- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java index fda28898f..b946f44bf 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java @@ -229,8 +229,7 @@ void setPublishRpcSpanException(Span publishRpcSpan, Throwable t) { } } - void startSubscriberSpan( - PubsubMessageWrapper message, boolean exactlyOnceDeliveryEnabled) { + void startSubscriberSpan(PubsubMessageWrapper message, boolean exactlyOnceDeliveryEnabled) { if (!enabled) { return; } @@ -275,8 +274,7 @@ void setSubscriberSpanExpirationResult(PubsubMessageWrapper message) { message.setSubscriberSpanExpirationResult(); } - void setSubscriberSpanException( - PubsubMessageWrapper message, Throwable t, String exception) { + void setSubscriberSpanException(PubsubMessageWrapper message, Throwable t, String exception) { if (!enabled) { return; } @@ -302,8 +300,7 @@ void endSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { message.endSubscribeConcurrencyControlSpan(); } - void setSubscribeConcurrencyControlSpanException( - PubsubMessageWrapper message, Throwable t) { + void setSubscribeConcurrencyControlSpanException(PubsubMessageWrapper message, Throwable t) { if (!enabled) { return; } @@ -434,8 +431,7 @@ void endSubscribeRpcSpan(Span rpcSpan) { * Sets an error status and records an exception when an exception is thrown when handling a * subscribe-side RPC. */ - void setSubscribeRpcSpanException( - Span rpcSpan, boolean isModack, int ackDeadline, Throwable t) { + void setSubscribeRpcSpanException(Span rpcSpan, boolean isModack, int ackDeadline, Throwable t) { if (!enabled) { return; } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java index a4cfdc257..1d0276287 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java @@ -166,7 +166,8 @@ private Publisher(Builder builder) throws IOException { if (this.openTelemetry != null && this.enableOpenTelemetryTracing) { Tracer openTelemetryTracer = builder.openTelemetry.getTracer(OPEN_TELEMETRY_TRACER_NAME); if (openTelemetryTracer != null) { - this.tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, this.enableOpenTelemetryTracing); + this.tracer = + new OpenTelemetryPubsubTracer(openTelemetryTracer, this.enableOpenTelemetryTracing); } } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java index 8f4e1a464..c45c9cb89 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java @@ -151,7 +151,7 @@ public class Subscriber extends AbstractApiService implements SubscriberInterfac private final boolean enableOpenTelemetryTracing; private final OpenTelemetry openTelemetry; - private OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(null, false); + private OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(null, false); private Subscriber(Builder builder) { receiver = builder.receiver; @@ -212,7 +212,8 @@ private Subscriber(Builder builder) { if (this.openTelemetry != null && this.enableOpenTelemetryTracing) { Tracer openTelemetryTracer = builder.openTelemetry.getTracer(OPEN_TELEMETRY_TRACER_NAME); if (openTelemetryTracer != null) { - this.tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, this.enableOpenTelemetryTracing); + this.tracer = + new OpenTelemetryPubsubTracer(openTelemetryTracer, this.enableOpenTelemetryTracing); } } From 305610e5a23f4f128c0750970a9b6f86540cbabe Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Thu, 26 Sep 2024 14:54:51 +0000 Subject: [PATCH 31/46] feat: Use MessagingIncubatingAttributes for gcp_pubsub attribute names --- google-cloud-pubsub/pom.xml | 5 +++++ .../pubsub/v1/OpenTelemetryPubsubTracer.java | 21 +++++++------------ .../com/google/cloud/pubsub/v1/Publisher.java | 9 ++++++++ .../google/cloud/pubsub/v1/Subscriber.java | 10 ++++++++- .../cloud/pubsub/v1/OpenTelemetryTest.java | 21 +++++++------------ 5 files changed, 39 insertions(+), 27 deletions(-) diff --git a/google-cloud-pubsub/pom.xml b/google-cloud-pubsub/pom.xml index b40f4ddde..85bb97b97 100644 --- a/google-cloud-pubsub/pom.xml +++ b/google-cloud-pubsub/pom.xml @@ -112,6 +112,11 @@ io.opentelemetry opentelemetry-semconv + + io.opentelemetry.semconv + opentelemetry-semconv-incubating + 1.27.0-alpha + diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java index b946f44bf..8b27f2a56 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java @@ -27,6 +27,7 @@ import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; +import io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.List; @@ -40,14 +41,8 @@ public class OpenTelemetryPubsubTracer { "subscriber concurrency control"; private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; - private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; - private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; - private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = "messaging.gcp_pubsub.message.exactly_once_delivery"; - private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = - "messaging.gcp_pubsub.message.delivery_attempt"; - private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; private static final String PROJECT_ATTR_KEY = "gcp.project_id"; private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; @@ -93,9 +88,9 @@ void startPublisherSpan(PubsubMessageWrapper message) { createCommonSpanAttributesBuilder( message.getTopicName(), message.getTopicProject(), "publish", "create"); - attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()); + attributesBuilder.put(MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE, message.getDataSize()); if (!message.getOrderingKey().isEmpty()) { - attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); + attributesBuilder.put(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ORDERING_KEY, message.getOrderingKey()); } Span publisherSpan = @@ -239,14 +234,14 @@ void startSubscriberSpan(PubsubMessageWrapper message, boolean exactlyOnceDelive attributesBuilder .put(SemanticAttributes.MESSAGING_MESSAGE_ID, message.getMessageId()) - .put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()) - .put(MESSAGE_ACK_ID_ATTR_KEY, message.getAckId()) + .put(MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE, message.getDataSize()) + .put(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ACK_ID, message.getAckId()) .put(MESSAGE_EXACTLY_ONCE_ATTR_KEY, exactlyOnceDeliveryEnabled); if (!message.getOrderingKey().isEmpty()) { - attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); + attributesBuilder.put(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ORDERING_KEY, message.getOrderingKey()); } if (message.getDeliveryAttempt() > 0) { - attributesBuilder.put(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, message.getDeliveryAttempt()); + attributesBuilder.put(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_DELIVERY_ATTEMPT, message.getDeliveryAttempt()); } Attributes attributes = attributesBuilder.build(); Context publisherSpanContext = message.extractSpanContext(attributes); @@ -380,7 +375,7 @@ Span startSubscribeRpcSpan( // Ack deadline and receipt modack are specific to the modack operation if (rpcOperation == "modack") { attributesBuilder - .put(ACK_DEADLINE_ATTR_KEY, ackDeadline) + .put(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ACK_DEADLINE, ackDeadline) .put(RECEIPT_MODACK_ATTR_KEY, isReceiptModack); } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java index 1d0276287..ff8288785 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java @@ -927,6 +927,15 @@ public Builder setCompressionBytesThreshold(long compressionBytesThreshold) { return this; } + + /** + * OpenTelemetry will be enabled if setEnableOpenTelemetry is true and and instance of OpenTelemetry has been provied. + Warning: traces are subject to change. The name and attributes of a span might + change without notice. Only use run traces interactively. Don't use in + automation. Running non-interactive traces can cause problems if the underlying + trace architecture changes without notice. + */ + /** Gives the ability to enable Open Telemetry Tracing */ public Builder setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing) { this.enableOpenTelemetryTracing = enableOpenTelemetryTracing; diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java index c45c9cb89..c69f0273c 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java @@ -707,13 +707,21 @@ Builder setClock(ApiClock clock) { return this; } + /** + * OpenTelemetry will be enabled if setEnableOpenTelemetry is true and and instance of OpenTelemetry has been provied. + Warning: traces are subject to change. The name and attributes of a span might + change without notice. Only use run traces interactively. Don't use in + automation. Running non-interactive traces can cause problems if the underlying + trace architecture changes without notice. + */ + /** Gives the ability to enable Open Telemetry Tracing */ public Builder setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing) { this.enableOpenTelemetryTracing = enableOpenTelemetryTracing; return this; } - /** Sets the instance of OpenTelemetry for the Publisher class. */ + /** Sets the instance of OpenTelemetry for the Subscriber class. */ public Builder setOpenTelemetry(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; return this; diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java index b4433f41e..c242d9a63 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -35,6 +35,7 @@ import io.opentelemetry.sdk.trace.data.LinkData; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.data.StatusData; +import io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.Arrays; import java.util.List; @@ -84,16 +85,10 @@ public class OpenTelemetryTest { private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; private static final String PROJECT_ATTR_KEY = "gcp.project_id"; - private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; - private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; - private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; - private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = "messaging.gcp_pubsub.message.exactly_once_delivery"; private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; - private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = - "messaging.gcp_pubsub.message.delivery_attempt"; private static final String TRACEPARENT_ATTRIBUTE = "googclient_traceparent"; @@ -195,8 +190,8 @@ public void testPublishSpansSuccess() { .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) .containsEntry(SemanticAttributes.CODE_FUNCTION, "publish") .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "create") - .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) - .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) + .containsEntry(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ORDERING_KEY, ORDERING_KEY) + .containsEntry(MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE, messageSize) .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); // Check that the message has the attribute containing the trace context. @@ -406,7 +401,7 @@ public void testSubscribeSpansSuccess() { .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "modack") .containsEntry( SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()) - .containsEntry(ACK_DEADLINE_ATTR_KEY, 10) + .containsEntry(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ACK_DEADLINE, 10) .containsEntry(RECEIPT_MODACK_ATTR_KEY, true); // Check span data, links, and attributes for the ack RPC span @@ -503,10 +498,10 @@ public void testSubscribeSpansSuccess() { SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) .containsEntry(SemanticAttributes.CODE_FUNCTION, "onResponse") - .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) - .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) - .containsEntry(MESSAGE_ACK_ID_ATTR_KEY, ACK_ID) - .containsEntry(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, DELIVERY_ATTEMPT) + .containsEntry(MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE, messageSize) + .containsEntry(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ORDERING_KEY, ORDERING_KEY) + .containsEntry(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ACK_ID, ACK_ID) + .containsEntry(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_DELIVERY_ATTEMPT, DELIVERY_ATTEMPT) .containsEntry(MESSAGE_EXACTLY_ONCE_ATTR_KEY, EXACTLY_ONCE_ENABLED) .containsEntry(MESSAGE_RESULT_ATTR_KEY, PROCESS_ACTION) .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); From e8dce751e03e2777577156f9d44c2b95b04080b1 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Thu, 26 Sep 2024 14:57:00 +0000 Subject: [PATCH 32/46] feat: Format OTel changes --- .../pubsub/v1/OpenTelemetryPubsubTracer.java | 15 +++++++++++---- .../com/google/cloud/pubsub/v1/Publisher.java | 13 ++++++------- .../com/google/cloud/pubsub/v1/Subscriber.java | 12 ++++++------ .../google/cloud/pubsub/v1/OpenTelemetryTest.java | 10 +++++++--- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java index 8b27f2a56..cc7192a1f 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java @@ -88,9 +88,12 @@ void startPublisherSpan(PubsubMessageWrapper message) { createCommonSpanAttributesBuilder( message.getTopicName(), message.getTopicProject(), "publish", "create"); - attributesBuilder.put(MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE, message.getDataSize()); + attributesBuilder.put( + MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE, message.getDataSize()); if (!message.getOrderingKey().isEmpty()) { - attributesBuilder.put(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ORDERING_KEY, message.getOrderingKey()); + attributesBuilder.put( + MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ORDERING_KEY, + message.getOrderingKey()); } Span publisherSpan = @@ -238,10 +241,14 @@ void startSubscriberSpan(PubsubMessageWrapper message, boolean exactlyOnceDelive .put(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ACK_ID, message.getAckId()) .put(MESSAGE_EXACTLY_ONCE_ATTR_KEY, exactlyOnceDeliveryEnabled); if (!message.getOrderingKey().isEmpty()) { - attributesBuilder.put(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ORDERING_KEY, message.getOrderingKey()); + attributesBuilder.put( + MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ORDERING_KEY, + message.getOrderingKey()); } if (message.getDeliveryAttempt() > 0) { - attributesBuilder.put(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_DELIVERY_ATTEMPT, message.getDeliveryAttempt()); + attributesBuilder.put( + MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_DELIVERY_ATTEMPT, + message.getDeliveryAttempt()); } Attributes attributes = attributesBuilder.build(); Context publisherSpanContext = message.extractSpanContext(attributes); diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java index ff8288785..99d0be17b 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java @@ -927,14 +927,13 @@ public Builder setCompressionBytesThreshold(long compressionBytesThreshold) { return this; } - /** - * OpenTelemetry will be enabled if setEnableOpenTelemetry is true and and instance of OpenTelemetry has been provied. - Warning: traces are subject to change. The name and attributes of a span might - change without notice. Only use run traces interactively. Don't use in - automation. Running non-interactive traces can cause problems if the underlying - trace architecture changes without notice. - */ + * OpenTelemetry will be enabled if setEnableOpenTelemetry is true and and instance of + * OpenTelemetry has been provied. Warning: traces are subject to change. The name and + * attributes of a span might change without notice. Only use run traces interactively. Don't + * use in automation. Running non-interactive traces can cause problems if the underlying trace + * architecture changes without notice. + */ /** Gives the ability to enable Open Telemetry Tracing */ public Builder setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing) { diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java index c69f0273c..953b2d571 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java @@ -708,12 +708,12 @@ Builder setClock(ApiClock clock) { } /** - * OpenTelemetry will be enabled if setEnableOpenTelemetry is true and and instance of OpenTelemetry has been provied. - Warning: traces are subject to change. The name and attributes of a span might - change without notice. Only use run traces interactively. Don't use in - automation. Running non-interactive traces can cause problems if the underlying - trace architecture changes without notice. - */ + * OpenTelemetry will be enabled if setEnableOpenTelemetry is true and and instance of + * OpenTelemetry has been provied. Warning: traces are subject to change. The name and + * attributes of a span might change without notice. Only use run traces interactively. Don't + * use in automation. Running non-interactive traces can cause problems if the underlying trace + * architecture changes without notice. + */ /** Gives the ability to enable Open Telemetry Tracing */ public Builder setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing) { diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java index c242d9a63..f6c56e896 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -190,7 +190,8 @@ public void testPublishSpansSuccess() { .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) .containsEntry(SemanticAttributes.CODE_FUNCTION, "publish") .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "create") - .containsEntry(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ORDERING_KEY, ORDERING_KEY) + .containsEntry( + MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ORDERING_KEY, ORDERING_KEY) .containsEntry(MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE, messageSize) .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); @@ -499,9 +500,12 @@ public void testSubscribeSpansSuccess() { .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) .containsEntry(SemanticAttributes.CODE_FUNCTION, "onResponse") .containsEntry(MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE, messageSize) - .containsEntry(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ORDERING_KEY, ORDERING_KEY) + .containsEntry( + MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ORDERING_KEY, ORDERING_KEY) .containsEntry(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ACK_ID, ACK_ID) - .containsEntry(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_DELIVERY_ATTEMPT, DELIVERY_ATTEMPT) + .containsEntry( + MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_DELIVERY_ATTEMPT, + DELIVERY_ATTEMPT) .containsEntry(MESSAGE_EXACTLY_ONCE_ATTR_KEY, EXACTLY_ONCE_ENABLED) .containsEntry(MESSAGE_RESULT_ATTR_KEY, PROCESS_ACTION) .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); From acd208c2894493deb2717c0e23fc360c90561610 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 30 Sep 2024 20:00:07 +0000 Subject: [PATCH 33/46] Revert "feat: Use MessagingIncubatingAttributes for gcp_pubsub attribute names" This reverts commit 305610e5a23f4f128c0750970a9b6f86540cbabe. --- google-cloud-pubsub/pom.xml | 5 ---- .../pubsub/v1/OpenTelemetryPubsubTracer.java | 28 +++++++++---------- .../google/cloud/pubsub/v1/Subscriber.java | 2 +- .../cloud/pubsub/v1/OpenTelemetryTest.java | 25 +++++++++-------- 4 files changed, 27 insertions(+), 33 deletions(-) diff --git a/google-cloud-pubsub/pom.xml b/google-cloud-pubsub/pom.xml index 85bb97b97..b40f4ddde 100644 --- a/google-cloud-pubsub/pom.xml +++ b/google-cloud-pubsub/pom.xml @@ -112,11 +112,6 @@ io.opentelemetry opentelemetry-semconv - - io.opentelemetry.semconv - opentelemetry-semconv-incubating - 1.27.0-alpha - diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java index cc7192a1f..b946f44bf 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java @@ -27,7 +27,6 @@ import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; -import io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.List; @@ -41,8 +40,14 @@ public class OpenTelemetryPubsubTracer { "subscriber concurrency control"; private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; + private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; + private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; + private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = "messaging.gcp_pubsub.message.exactly_once_delivery"; + private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = + "messaging.gcp_pubsub.message.delivery_attempt"; + private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; private static final String PROJECT_ATTR_KEY = "gcp.project_id"; private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; @@ -88,12 +93,9 @@ void startPublisherSpan(PubsubMessageWrapper message) { createCommonSpanAttributesBuilder( message.getTopicName(), message.getTopicProject(), "publish", "create"); - attributesBuilder.put( - MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE, message.getDataSize()); + attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()); if (!message.getOrderingKey().isEmpty()) { - attributesBuilder.put( - MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ORDERING_KEY, - message.getOrderingKey()); + attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); } Span publisherSpan = @@ -237,18 +239,14 @@ void startSubscriberSpan(PubsubMessageWrapper message, boolean exactlyOnceDelive attributesBuilder .put(SemanticAttributes.MESSAGING_MESSAGE_ID, message.getMessageId()) - .put(MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE, message.getDataSize()) - .put(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ACK_ID, message.getAckId()) + .put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()) + .put(MESSAGE_ACK_ID_ATTR_KEY, message.getAckId()) .put(MESSAGE_EXACTLY_ONCE_ATTR_KEY, exactlyOnceDeliveryEnabled); if (!message.getOrderingKey().isEmpty()) { - attributesBuilder.put( - MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ORDERING_KEY, - message.getOrderingKey()); + attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); } if (message.getDeliveryAttempt() > 0) { - attributesBuilder.put( - MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_DELIVERY_ATTEMPT, - message.getDeliveryAttempt()); + attributesBuilder.put(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, message.getDeliveryAttempt()); } Attributes attributes = attributesBuilder.build(); Context publisherSpanContext = message.extractSpanContext(attributes); @@ -382,7 +380,7 @@ Span startSubscribeRpcSpan( // Ack deadline and receipt modack are specific to the modack operation if (rpcOperation == "modack") { attributesBuilder - .put(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ACK_DEADLINE, ackDeadline) + .put(ACK_DEADLINE_ATTR_KEY, ackDeadline) .put(RECEIPT_MODACK_ATTR_KEY, isReceiptModack); } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java index 953b2d571..e9926fa58 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Subscriber.java @@ -721,7 +721,7 @@ public Builder setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing) return this; } - /** Sets the instance of OpenTelemetry for the Subscriber class. */ + /** Sets the instance of OpenTelemetry for the Publisher class. */ public Builder setOpenTelemetry(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; return this; diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java index f6c56e896..b4433f41e 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -35,7 +35,6 @@ import io.opentelemetry.sdk.trace.data.LinkData; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.data.StatusData; -import io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.Arrays; import java.util.List; @@ -85,10 +84,16 @@ public class OpenTelemetryTest { private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; private static final String PROJECT_ATTR_KEY = "gcp.project_id"; + private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; + private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; + private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; + private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = "messaging.gcp_pubsub.message.exactly_once_delivery"; private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; + private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = + "messaging.gcp_pubsub.message.delivery_attempt"; private static final String TRACEPARENT_ATTRIBUTE = "googclient_traceparent"; @@ -190,9 +195,8 @@ public void testPublishSpansSuccess() { .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) .containsEntry(SemanticAttributes.CODE_FUNCTION, "publish") .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "create") - .containsEntry( - MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ORDERING_KEY, ORDERING_KEY) - .containsEntry(MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE, messageSize) + .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) + .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); // Check that the message has the attribute containing the trace context. @@ -402,7 +406,7 @@ public void testSubscribeSpansSuccess() { .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "modack") .containsEntry( SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()) - .containsEntry(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ACK_DEADLINE, 10) + .containsEntry(ACK_DEADLINE_ATTR_KEY, 10) .containsEntry(RECEIPT_MODACK_ATTR_KEY, true); // Check span data, links, and attributes for the ack RPC span @@ -499,13 +503,10 @@ public void testSubscribeSpansSuccess() { SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) .containsEntry(SemanticAttributes.CODE_FUNCTION, "onResponse") - .containsEntry(MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE, messageSize) - .containsEntry( - MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ORDERING_KEY, ORDERING_KEY) - .containsEntry(MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_ACK_ID, ACK_ID) - .containsEntry( - MessagingIncubatingAttributes.MESSAGING_GCP_PUBSUB_MESSAGE_DELIVERY_ATTEMPT, - DELIVERY_ATTEMPT) + .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) + .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) + .containsEntry(MESSAGE_ACK_ID_ATTR_KEY, ACK_ID) + .containsEntry(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, DELIVERY_ATTEMPT) .containsEntry(MESSAGE_EXACTLY_ONCE_ATTR_KEY, EXACTLY_ONCE_ENABLED) .containsEntry(MESSAGE_RESULT_ATTR_KEY, PROCESS_ACTION) .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); From 462dd8e748acdd3c0f4ce254ba54c5424573ddc3 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 30 Sep 2024 20:25:46 +0000 Subject: [PATCH 34/46] feat: trigger build From c4ff119a526c97c5e71e3aac3d4a89ac700038d0 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Mon, 30 Sep 2024 20:39:39 +0000 Subject: [PATCH 35/46] chore: generate libraries at Mon Sep 30 20:37:03 UTC 2024 --- README.md | 2 + .../pubsub/v1/OpenTelemetryPubsubTracer.java | 460 ------------ .../cloud/pubsub/v1/PubsubMessageWrapper.java | 430 ----------- .../cloud/pubsub/v1/OpenTelemetryTest.java | 669 ------------------ 4 files changed, 2 insertions(+), 1559 deletions(-) delete mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java delete mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java delete mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java diff --git a/README.md b/README.md index c5729ff0e..c107aeac3 100644 --- a/README.md +++ b/README.md @@ -273,6 +273,8 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-pubsub/tree/m | List Subscriptions In Project Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/ListSubscriptionsInProjectExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/ListSubscriptionsInProjectExample.java) | | List Subscriptions In Topic Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/ListSubscriptionsInTopicExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/ListSubscriptionsInTopicExample.java) | | List Topics Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/ListTopicsExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/ListTopicsExample.java) | +| Open Telemetry Publisher Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java) | +| Open Telemetry Subscriber Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/OpenTelemetrySubscriberExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/OpenTelemetrySubscriberExample.java) | | Optimistic Subscribe Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/OptimisticSubscribeExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/OptimisticSubscribeExample.java) | | Publish Avro Records Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/PublishAvroRecordsExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/PublishAvroRecordsExample.java) | | Publish Protobuf Messages Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/PublishProtobufMessagesExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/PublishProtobufMessagesExample.java) | diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java deleted file mode 100644 index b946f44bf..000000000 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java +++ /dev/null @@ -1,460 +0,0 @@ -/* - * 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.pubsub.v1; - -import com.google.pubsub.v1.PubsubMessage; -import com.google.pubsub.v1.SubscriptionName; -import com.google.pubsub.v1.TopicName; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanBuilder; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.context.Context; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.util.List; - -public class OpenTelemetryPubsubTracer { - private final Tracer tracer; - private boolean enabled = false; - - private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; - private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; - private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = - "subscriber concurrency control"; - private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; - - private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; - private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; - private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; - private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = - "messaging.gcp_pubsub.message.exactly_once_delivery"; - private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = - "messaging.gcp_pubsub.message.delivery_attempt"; - private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; - private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; - private static final String PROJECT_ATTR_KEY = "gcp.project_id"; - private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; - - private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; - - OpenTelemetryPubsubTracer(Tracer tracer, boolean enableOpenTelemetry) { - this.tracer = tracer; - if (this.tracer != null && enableOpenTelemetry) { - this.enabled = true; - } - } - - /** Populates attributes that are common the publisher parent span and publish RPC span. */ - private static final AttributesBuilder createCommonSpanAttributesBuilder( - String destinationName, String projectName, String codeFunction, String operation) { - AttributesBuilder attributesBuilder = - Attributes.builder() - .put(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .put(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName) - .put(PROJECT_ATTR_KEY, projectName) - .put(SemanticAttributes.CODE_FUNCTION, codeFunction); - if (operation != null) { - attributesBuilder.put(SemanticAttributes.MESSAGING_OPERATION, operation); - } - - return attributesBuilder; - } - - private Span startChildSpan(String name, Span parent) { - return tracer.spanBuilder(name).setParent(Context.current().with(parent)).startSpan(); - } - - /** - * Creates and starts the parent span with the appropriate span attributes and injects the span - * context into the {@link PubsubMessage} attributes. - */ - void startPublisherSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - AttributesBuilder attributesBuilder = - createCommonSpanAttributesBuilder( - message.getTopicName(), message.getTopicProject(), "publish", "create"); - - attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()); - if (!message.getOrderingKey().isEmpty()) { - attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); - } - - Span publisherSpan = - tracer - .spanBuilder(message.getTopicName() + " create") - .setSpanKind(SpanKind.PRODUCER) - .setAllAttributes(attributesBuilder.build()) - .startSpan(); - - message.setPublisherSpan(publisherSpan); - if (publisherSpan.getSpanContext().isValid()) { - message.injectSpanContext(); - } - } - - void endPublisherSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endPublisherSpan(); - } - - void setPublisherMessageIdSpanAttribute(PubsubMessageWrapper message, String messageId) { - if (!enabled) { - return; - } - message.setPublisherMessageIdSpanAttribute(messageId); - } - - /** Creates a span for publish-side flow control as a child of the parent publisher span. */ - void startPublishFlowControlSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span publisherSpan = message.getPublisherSpan(); - if (publisherSpan != null) - message.setPublishFlowControlSpan( - startChildSpan(PUBLISH_FLOW_CONTROL_SPAN_NAME, publisherSpan)); - } - - void endPublishFlowControlSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endPublishFlowControlSpan(); - } - - void setPublishFlowControlSpanException(PubsubMessageWrapper message, Throwable t) { - if (!enabled) { - return; - } - message.setPublishFlowControlSpanException(t); - } - - /** Creates a span for publish message batching as a child of the parent publisher span. */ - void startPublishBatchingSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span publisherSpan = message.getPublisherSpan(); - if (publisherSpan != null) { - message.setPublishBatchingSpan(startChildSpan(PUBLISH_BATCHING_SPAN_NAME, publisherSpan)); - } - } - - void endPublishBatchingSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endPublishBatchingSpan(); - } - - /** - * Creates, starts, and returns a publish RPC span for the given message batch. Bi-directional - * links with the publisher parent span are created for sampled messages in the batch. - */ - Span startPublishRpcSpan(String topic, List messages) { - if (!enabled) { - return null; - } - TopicName topicName = TopicName.parse(topic); - Attributes attributes = - createCommonSpanAttributesBuilder( - topicName.getTopic(), topicName.getProject(), "publishCall", "publish") - .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()) - .build(); - SpanBuilder publishRpcSpanBuilder = - tracer - .spanBuilder(topicName.getTopic() + PUBLISH_RPC_SPAN_SUFFIX) - .setSpanKind(SpanKind.CLIENT) - .setAllAttributes(attributes); - Attributes linkAttributes = - Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, "publish").build(); - for (PubsubMessageWrapper message : messages) { - if (message.getPublisherSpan().getSpanContext().isSampled()) - publishRpcSpanBuilder.addLink(message.getPublisherSpan().getSpanContext(), linkAttributes); - } - Span publishRpcSpan = publishRpcSpanBuilder.startSpan(); - - for (PubsubMessageWrapper message : messages) { - if (publishRpcSpan.getSpanContext().isSampled()) { - message.getPublisherSpan().addLink(publishRpcSpan.getSpanContext(), linkAttributes); - message.addPublishStartEvent(); - } - } - return publishRpcSpan; - } - - /** Ends the given publish RPC span if it exists. */ - void endPublishRpcSpan(Span publishRpcSpan) { - if (!enabled) { - return; - } - if (publishRpcSpan != null) { - publishRpcSpan.end(); - } - } - - /** - * Sets an error status and records an exception when an exception is thrown when publishing the - * message batch. - */ - void setPublishRpcSpanException(Span publishRpcSpan, Throwable t) { - if (!enabled) { - return; - } - if (publishRpcSpan != null) { - publishRpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on publish RPC."); - publishRpcSpan.recordException(t); - publishRpcSpan.end(); - } - } - - void startSubscriberSpan(PubsubMessageWrapper message, boolean exactlyOnceDeliveryEnabled) { - if (!enabled) { - return; - } - AttributesBuilder attributesBuilder = - createCommonSpanAttributesBuilder( - message.getSubscriptionName(), message.getSubscriptionProject(), "onResponse", null); - - attributesBuilder - .put(SemanticAttributes.MESSAGING_MESSAGE_ID, message.getMessageId()) - .put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()) - .put(MESSAGE_ACK_ID_ATTR_KEY, message.getAckId()) - .put(MESSAGE_EXACTLY_ONCE_ATTR_KEY, exactlyOnceDeliveryEnabled); - if (!message.getOrderingKey().isEmpty()) { - attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); - } - if (message.getDeliveryAttempt() > 0) { - attributesBuilder.put(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, message.getDeliveryAttempt()); - } - Attributes attributes = attributesBuilder.build(); - Context publisherSpanContext = message.extractSpanContext(attributes); - message.setPublisherSpan(Span.fromContextOrNull(publisherSpanContext)); - message.setSubscriberSpan( - tracer - .spanBuilder(message.getSubscriptionName() + " subscribe") - .setSpanKind(SpanKind.CONSUMER) - .setParent(publisherSpanContext) - .setAllAttributes(attributes) - .startSpan()); - } - - void endSubscriberSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endSubscriberSpan(); - } - - void setSubscriberSpanExpirationResult(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.setSubscriberSpanExpirationResult(); - } - - void setSubscriberSpanException(PubsubMessageWrapper message, Throwable t, String exception) { - if (!enabled) { - return; - } - message.setSubscriberSpanException(t, exception); - } - - /** Creates a span for subscribe concurrency control as a child of the parent subscriber span. */ - void startSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span subscriberSpan = message.getSubscriberSpan(); - if (subscriberSpan != null) { - message.setSubscribeConcurrencyControlSpan( - startChildSpan(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME, subscriberSpan)); - } - } - - void endSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endSubscribeConcurrencyControlSpan(); - } - - void setSubscribeConcurrencyControlSpanException(PubsubMessageWrapper message, Throwable t) { - if (!enabled) { - return; - } - message.setSubscribeConcurrencyControlSpanException(t); - } - - /** - * Creates a span for subscribe ordering key scheduling as a child of the parent subscriber span. - */ - void startSubscribeSchedulerSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span subscriberSpan = message.getSubscriberSpan(); - if (subscriberSpan != null) { - message.setSubscribeSchedulerSpan( - startChildSpan(SUBSCRIBE_SCHEDULER_SPAN_NAME, subscriberSpan)); - } - } - - void endSubscribeSchedulerSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endSubscribeSchedulerSpan(); - } - - /** Creates a span for subscribe message processing as a child of the parent subscriber span. */ - void startSubscribeProcessSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span subscriberSpan = message.getSubscriberSpan(); - if (subscriberSpan != null) { - Span subscribeProcessSpan = - startChildSpan(message.getSubscriptionName() + " process", subscriberSpan); - subscribeProcessSpan.setAttribute( - SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE); - Span publisherSpan = message.getPublisherSpan(); - if (publisherSpan != null) { - subscribeProcessSpan.addLink(publisherSpan.getSpanContext()); - } - message.setSubscribeProcessSpan(subscribeProcessSpan); - } - } - - void endSubscribeProcessSpan(PubsubMessageWrapper message, String action) { - if (!enabled) { - return; - } - message.endSubscribeProcessSpan(action); - } - - /** - * Creates, starts, and returns spans for ModAck, Nack, and Ack RPC requests. Bi-directional links - * to parent subscribe span for sampled messages are added. - */ - Span startSubscribeRpcSpan( - String subscription, - String rpcOperation, - List messages, - int ackDeadline, - boolean isReceiptModack) { - if (!enabled) { - return null; - } - String codeFunction = rpcOperation == "ack" ? "sendAckOperations" : "sendModAckOperations"; - SubscriptionName subscriptionName = SubscriptionName.parse(subscription); - AttributesBuilder attributesBuilder = - createCommonSpanAttributesBuilder( - subscriptionName.getSubscription(), - subscriptionName.getProject(), - codeFunction, - rpcOperation) - .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()); - - // Ack deadline and receipt modack are specific to the modack operation - if (rpcOperation == "modack") { - attributesBuilder - .put(ACK_DEADLINE_ATTR_KEY, ackDeadline) - .put(RECEIPT_MODACK_ATTR_KEY, isReceiptModack); - } - - SpanBuilder rpcSpanBuilder = - tracer - .spanBuilder(subscriptionName.getSubscription() + " " + rpcOperation) - .setSpanKind(SpanKind.CLIENT) - .setAllAttributes(attributesBuilder.build()); - Attributes linkAttributes = - Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, rpcOperation).build(); - for (PubsubMessageWrapper message : messages) { - if (message.getSubscriberSpan().getSpanContext().isSampled()) { - rpcSpanBuilder.addLink(message.getSubscriberSpan().getSpanContext(), linkAttributes); - } - } - Span rpcSpan = rpcSpanBuilder.startSpan(); - - for (PubsubMessageWrapper message : messages) { - if (rpcSpan.getSpanContext().isSampled()) { - message.getSubscriberSpan().addLink(rpcSpan.getSpanContext(), linkAttributes); - switch (rpcOperation) { - case "ack": - message.addAckStartEvent(); - break; - case "modack": - message.addModAckStartEvent(); - break; - case "nack": - message.addNackStartEvent(); - break; - } - } - } - return rpcSpan; - } - - /** Ends the given subscribe RPC span if it exists. */ - void endSubscribeRpcSpan(Span rpcSpan) { - if (!enabled) { - return; - } - if (rpcSpan != null) { - rpcSpan.end(); - } - } - - /** - * Sets an error status and records an exception when an exception is thrown when handling a - * subscribe-side RPC. - */ - void setSubscribeRpcSpanException(Span rpcSpan, boolean isModack, int ackDeadline, Throwable t) { - if (!enabled) { - return; - } - if (rpcSpan != null) { - String operation = !isModack ? "ack" : (ackDeadline == 0 ? "nack" : "modack"); - rpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on " + operation + " RPC."); - rpcSpan.recordException(t); - rpcSpan.end(); - } - } - - /** Adds the appropriate subscribe-side RPC end event. */ - void addEndRpcEvent( - PubsubMessageWrapper message, boolean rpcSampled, boolean isModack, int ackDeadline) { - if (!enabled || !rpcSampled) { - return; - } - if (!isModack) { - message.addAckEndEvent(); - } else if (ackDeadline == 0) { - message.addNackEndEvent(); - } else { - message.addModAckEndEvent(); - } - } -} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java deleted file mode 100644 index 94fd13085..000000000 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java +++ /dev/null @@ -1,430 +0,0 @@ -/* - * 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.pubsub.v1; - -import com.google.pubsub.v1.PubsubMessage; -import com.google.pubsub.v1.SubscriptionName; -import com.google.pubsub.v1.TopicName; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.propagation.TextMapGetter; -import io.opentelemetry.context.propagation.TextMapSetter; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; - -/** - * A wrapper class for a {@link PubsubMessage} object that handles creation and tracking of - * OpenTelemetry {@link Span} objects for different operations that occur during publishing. - */ -public class PubsubMessageWrapper { - private PubsubMessage message; - - private final TopicName topicName; - private final SubscriptionName subscriptionName; - - // Attributes set only for messages received from a streaming pull response. - private final String ackId; - private final int deliveryAttempt; - - private static final String PUBLISH_START_EVENT = "publish start"; - private static final String PUBLISH_END_EVENT = "publish end"; - - private static final String MODACK_START_EVENT = "modack start"; - private static final String MODACK_END_EVENT = "modack end"; - private static final String NACK_START_EVENT = "nack start"; - private static final String NACK_END_EVENT = "nack end"; - private static final String ACK_START_EVENT = "ack start"; - private static final String ACK_END_EVENT = "ack end"; - - private static final String GOOGCLIENT_PREFIX = "googclient_"; - - private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; - - private Span publisherSpan; - private Span publishFlowControlSpan; - private Span publishBatchingSpan; - - private Span subscriberSpan; - private Span subscribeConcurrencyControlSpan; - private Span subscribeSchedulerSpan; - private Span subscribeProcessSpan; - - private PubsubMessageWrapper(Builder builder) { - this.message = builder.message; - this.topicName = builder.topicName; - this.subscriptionName = builder.subscriptionName; - this.ackId = builder.ackId; - this.deliveryAttempt = builder.deliveryAttempt; - } - - static Builder newBuilder(PubsubMessage message, String topicName) { - return new Builder(message, topicName); - } - - static Builder newBuilder( - PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { - return new Builder(message, subscriptionName, ackId, deliveryAttempt); - } - - /** Returns the PubsubMessage associated with this wrapper. */ - PubsubMessage getPubsubMessage() { - return message; - } - - void setPubsubMessage(PubsubMessage message) { - this.message = message; - } - - /** Returns the TopicName for this wrapper as a string. */ - String getTopicName() { - if (topicName != null) { - return topicName.getTopic(); - } - return ""; - } - - String getTopicProject() { - if (topicName != null) { - return topicName.getProject(); - } - return ""; - } - - /** Returns the SubscriptionName for this wrapper as a string. */ - String getSubscriptionName() { - if (subscriptionName != null) { - return subscriptionName.getSubscription(); - } - return ""; - } - - String getSubscriptionProject() { - if (subscriptionName != null) { - return subscriptionName.getProject(); - } - return ""; - } - - String getMessageId() { - return message.getMessageId(); - } - - String getAckId() { - return ackId; - } - - int getDataSize() { - return message.getData().size(); - } - - String getOrderingKey() { - return message.getOrderingKey(); - } - - int getDeliveryAttempt() { - return deliveryAttempt; - } - - Span getPublisherSpan() { - return publisherSpan; - } - - void setPublisherSpan(Span span) { - this.publisherSpan = span; - } - - void setPublishFlowControlSpan(Span span) { - this.publishFlowControlSpan = span; - } - - void setPublishBatchingSpan(Span span) { - this.publishBatchingSpan = span; - } - - Span getSubscriberSpan() { - return subscriberSpan; - } - - void setSubscriberSpan(Span span) { - this.subscriberSpan = span; - } - - void setSubscribeConcurrencyControlSpan(Span span) { - this.subscribeConcurrencyControlSpan = span; - } - - void setSubscribeSchedulerSpan(Span span) { - this.subscribeSchedulerSpan = span; - } - - void setSubscribeProcessSpan(Span span) { - this.subscribeProcessSpan = span; - } - - /** Creates a publish start event that is tied to the publish RPC span time. */ - void addPublishStartEvent() { - if (publisherSpan != null) { - publisherSpan.addEvent(PUBLISH_START_EVENT); - } - } - - /** - * Sets the message ID attribute in the publisher parent span. This is called after the publish - * RPC returns with a message ID. - */ - void setPublisherMessageIdSpanAttribute(String messageId) { - if (publisherSpan != null) { - publisherSpan.setAttribute(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId); - } - } - - /** Ends the publisher parent span if it exists. */ - void endPublisherSpan() { - if (publisherSpan != null) { - publisherSpan.addEvent(PUBLISH_END_EVENT); - publisherSpan.end(); - } - } - - /** Ends the publish flow control span if it exists. */ - void endPublishFlowControlSpan() { - if (publishFlowControlSpan != null) { - publishFlowControlSpan.end(); - } - } - - /** Ends the publish batching span if it exists. */ - void endPublishBatchingSpan() { - if (publishBatchingSpan != null) { - publishBatchingSpan.end(); - } - } - - /** - * Sets an error status and records an exception when an exception is thrown during flow control. - */ - void setPublishFlowControlSpanException(Throwable t) { - if (publishFlowControlSpan != null) { - publishFlowControlSpan.setStatus( - StatusCode.ERROR, "Exception thrown during publish flow control."); - publishFlowControlSpan.recordException(t); - endAllPublishSpans(); - } - } - - /** - * Creates start and end events for ModAcks, Nacks, and Acks that are tied to the corresponding - * RPC span start and end times. - */ - void addModAckStartEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(MODACK_START_EVENT); - } - } - - void addModAckEndEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(MODACK_END_EVENT); - } - } - - void addNackStartEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(NACK_START_EVENT); - } - } - - void addNackEndEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(NACK_END_EVENT); - } - } - - void addAckStartEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(ACK_START_EVENT); - } - } - - void addAckEndEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(ACK_END_EVENT); - } - } - - /** Ends the subscriber parent span if exists. */ - void endSubscriberSpan() { - if (subscriberSpan != null) { - subscriberSpan.end(); - } - } - - /** Ends the subscribe concurreny control span if exists. */ - void endSubscribeConcurrencyControlSpan() { - if (subscribeConcurrencyControlSpan != null) { - subscribeConcurrencyControlSpan.end(); - } - } - - /** Ends the subscribe scheduler span if exists. */ - void endSubscribeSchedulerSpan() { - if (subscribeSchedulerSpan != null) { - subscribeSchedulerSpan.end(); - } - } - - /** - * Ends the subscribe process span if it exists, creates an event with the appropriate result, and - * sets the result on the parent subscriber span. - */ - void endSubscribeProcessSpan(String action) { - if (subscribeProcessSpan != null) { - subscribeProcessSpan.addEvent(action + " called"); - subscribeProcessSpan.end(); - subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, action); - } - } - - /** Sets an exception on the subscriber span during Ack/ModAck/Nack failures */ - void setSubscriberSpanException(Throwable t, String exception) { - if (subscriberSpan != null) { - subscriberSpan.setStatus(StatusCode.ERROR, exception); - subscriberSpan.recordException(t); - endAllSubscribeSpans(); - } - } - - /** Sets result of the parent subscriber span to expired and ends its. */ - void setSubscriberSpanExpirationResult() { - if (subscriberSpan != null) { - subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, "expired"); - endSubscriberSpan(); - } - } - - /** - * Sets an error status and records an exception when an exception is thrown subscriber - * concurrency control. - */ - void setSubscribeConcurrencyControlSpanException(Throwable t) { - if (subscribeConcurrencyControlSpan != null) { - subscribeConcurrencyControlSpan.setStatus( - StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); - subscribeConcurrencyControlSpan.recordException(t); - endAllSubscribeSpans(); - } - } - - /** Ends all publisher-side spans associated with this message wrapper. */ - private void endAllPublishSpans() { - endPublishFlowControlSpan(); - endPublishBatchingSpan(); - endPublisherSpan(); - } - - /** Ends all subscriber-side spans associated with this message wrapper. */ - private void endAllSubscribeSpans() { - endSubscribeConcurrencyControlSpan(); - endSubscribeSchedulerSpan(); - endSubscriberSpan(); - } - - /** - * Injects the span context into the attributes of a Pub/Sub message for propagation to the - * subscriber client. - */ - void injectSpanContext() { - TextMapSetter injectMessageAttributes = - new TextMapSetter() { - @Override - public void set(PubsubMessageWrapper carrier, String key, String value) { - PubsubMessage newMessage = - PubsubMessage.newBuilder(carrier.message) - .putAttributes(GOOGCLIENT_PREFIX + key, value) - .build(); - carrier.message = newMessage; - } - }; - W3CTraceContextPropagator.getInstance() - .inject(Context.current().with(publisherSpan), this, injectMessageAttributes); - } - - /** - * Extracts the span context from the attributes of a Pub/Sub message and creates the parent - * subscriber span using that context. - */ - Context extractSpanContext(Attributes attributes) { - TextMapGetter extractMessageAttributes = - new TextMapGetter() { - @Override - public String get(PubsubMessageWrapper carrier, String key) { - return carrier.message.getAttributesOrDefault(GOOGCLIENT_PREFIX + key, ""); - } - - public Iterable keys(PubsubMessageWrapper carrier) { - return carrier.message.getAttributesMap().keySet(); - } - }; - Context context = - W3CTraceContextPropagator.getInstance() - .extract(Context.current(), this, extractMessageAttributes); - return context; - } - - /** Builder of {@link PubsubMessageWrapper PubsubMessageWrapper}. */ - static final class Builder { - private PubsubMessage message = null; - private TopicName topicName = null; - private SubscriptionName subscriptionName = null; - private String ackId = null; - private int deliveryAttempt = 0; - - public Builder(PubsubMessage message, String topicName) { - this.message = message; - if (topicName != null) { - this.topicName = TopicName.parse(topicName); - } - } - - public Builder( - PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { - this.message = message; - if (subscriptionName != null) { - this.subscriptionName = SubscriptionName.parse(subscriptionName); - } - this.ackId = ackId; - this.deliveryAttempt = deliveryAttempt; - } - - public Builder( - PubsubMessage message, - SubscriptionName subscriptionName, - String ackId, - int deliveryAttempt) { - this.message = message; - this.subscriptionName = subscriptionName; - this.ackId = ackId; - this.deliveryAttempt = deliveryAttempt; - } - - public PubsubMessageWrapper build() { - return new PubsubMessageWrapper(this); - } - } -} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java deleted file mode 100644 index b4433f41e..000000000 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ /dev/null @@ -1,669 +0,0 @@ -/* - * 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.pubsub.v1; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import com.google.protobuf.ByteString; -import com.google.pubsub.v1.PubsubMessage; -import com.google.pubsub.v1.SubscriptionName; -import com.google.pubsub.v1.TopicName; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.sdk.testing.assertj.AttributesAssert; -import io.opentelemetry.sdk.testing.assertj.EventDataAssert; -import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; -import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; -import io.opentelemetry.sdk.testing.junit4.OpenTelemetryRule; -import io.opentelemetry.sdk.trace.data.LinkData; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.data.StatusData; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.util.Arrays; -import java.util.List; -import org.junit.Test; - -public class OpenTelemetryTest { - private static final TopicName FULL_TOPIC_NAME = - TopicName.parse("projects/test-project/topics/test-topic"); - private static final SubscriptionName FULL_SUBSCRIPTION_NAME = - SubscriptionName.parse("projects/test-project/subscriptions/test-sub"); - private static final String PROJECT_NAME = "test-project"; - private static final String ORDERING_KEY = "abc"; - private static final String MESSAGE_ID = "m0"; - private static final String ACK_ID = "def"; - private static final int DELIVERY_ATTEMPT = 1; - private static final int ACK_DEADLINE = 10; - private static final boolean EXACTLY_ONCE_ENABLED = true; - - private static final String PUBLISHER_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " create"; - private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; - private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; - private static final String PUBLISH_RPC_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " publish"; - private static final String PUBLISH_START_EVENT = "publish start"; - private static final String PUBLISH_END_EVENT = "publish end"; - - private static final String SUBSCRIBER_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " subscribe"; - private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = - "subscriber concurrency control"; - private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; - private static final String SUBSCRIBE_PROCESS_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " process"; - private static final String SUBSCRIBE_MODACK_RPC_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " modack"; - private static final String SUBSCRIBE_ACK_RPC_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " ack"; - private static final String SUBSCRIBE_NACK_RPC_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " nack"; - - private static final String PROCESS_ACTION = "ack"; - private static final String MODACK_START_EVENT = "modack start"; - private static final String MODACK_END_EVENT = "modack end"; - private static final String NACK_START_EVENT = "nack start"; - private static final String NACK_END_EVENT = "nack end"; - private static final String ACK_START_EVENT = "ack start"; - private static final String ACK_END_EVENT = "ack end"; - - private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; - private static final String PROJECT_ATTR_KEY = "gcp.project_id"; - private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; - private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; - private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; - private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; - private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; - private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = - "messaging.gcp_pubsub.message.exactly_once_delivery"; - private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; - private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = - "messaging.gcp_pubsub.message.delivery_attempt"; - - private static final String TRACEPARENT_ATTRIBUTE = "googclient_traceparent"; - - private static final OpenTelemetryRule openTelemetryTesting = OpenTelemetryRule.create(); - - @Test - public void testPublishSpansSuccess() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); - List messageWrappers = Arrays.asList(messageWrapper); - - long messageSize = messageWrapper.getPubsubMessage().getData().size(); - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - // Call all span start/end methods in the expected order - tracer.startPublisherSpan(messageWrapper); - tracer.startPublishFlowControlSpan(messageWrapper); - tracer.endPublishFlowControlSpan(messageWrapper); - tracer.startPublishBatchingSpan(messageWrapper); - tracer.endPublishBatchingSpan(messageWrapper); - Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); - tracer.endPublishRpcSpan(publishRpcSpan); - tracer.setPublisherMessageIdSpanAttribute(messageWrapper, MESSAGE_ID); - tracer.endPublisherSpan(messageWrapper); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(4, allSpans.size()); - SpanData flowControlSpanData = allSpans.get(0); - SpanData batchingSpanData = allSpans.get(1); - SpanData publishRpcSpanData = allSpans.get(2); - SpanData publisherSpanData = allSpans.get(3); - - SpanDataAssert flowControlSpanDataAssert = - OpenTelemetryAssertions.assertThat(flowControlSpanData); - flowControlSpanDataAssert - .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) - .hasParent(publisherSpanData) - .hasEnded(); - - SpanDataAssert batchingSpanDataAssert = OpenTelemetryAssertions.assertThat(batchingSpanData); - batchingSpanDataAssert - .hasName(PUBLISH_BATCHING_SPAN_NAME) - .hasParent(publisherSpanData) - .hasEnded(); - - // Check span data, links, and attributes for the publish RPC span - SpanDataAssert publishRpcSpanDataAssert = - OpenTelemetryAssertions.assertThat(publishRpcSpanData); - publishRpcSpanDataAssert - .hasName(PUBLISH_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasEnded(); - - List publishRpcLinks = publishRpcSpanData.getLinks(); - assertEquals(messageWrappers.size(), publishRpcLinks.size()); - assertEquals(publisherSpanData.getSpanContext(), publishRpcLinks.get(0).getSpanContext()); - - assertEquals(6, publishRpcSpanData.getAttributes().size()); - AttributesAssert publishRpcSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(publishRpcSpanData.getAttributes()); - publishRpcSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) - .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "publishCall") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "publish") - .containsEntry(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messageWrappers.size()); - - // Check span data, events, links, and attributes for the publisher create span - SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); - publisherSpanDataAssert - .hasName(PUBLISHER_SPAN_NAME) - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasEnded(); - - assertEquals(2, publisherSpanData.getEvents().size()); - EventDataAssert startEventAssert = - OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(0)); - startEventAssert.hasName(PUBLISH_START_EVENT); - EventDataAssert endEventAssert = - OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(1)); - endEventAssert.hasName(PUBLISH_END_EVENT); - - List publisherLinks = publisherSpanData.getLinks(); - assertEquals(1, publisherLinks.size()); - assertEquals(publishRpcSpanData.getSpanContext(), publisherLinks.get(0).getSpanContext()); - - assertEquals(8, publisherSpanData.getAttributes().size()); - AttributesAssert publisherSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(publisherSpanData.getAttributes()); - publisherSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) - .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "publish") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "create") - .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) - .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) - .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); - - // Check that the message has the attribute containing the trace context. - PubsubMessage message = messageWrapper.getPubsubMessage(); - assertEquals(1, message.getAttributesMap().size()); - assertTrue(message.containsAttributes(TRACEPARENT_ATTRIBUTE)); - assertTrue( - message - .getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "") - .contains(publisherSpanData.getTraceId())); - assertTrue( - message - .getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "") - .contains(publisherSpanData.getSpanId())); - } - - @Test - public void testPublishFlowControlSpanFailure() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startPublisherSpan(messageWrapper); - tracer.startPublishFlowControlSpan(messageWrapper); - - Exception e = new Exception("test-exception"); - tracer.setPublishFlowControlSpanException(messageWrapper, e); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(2, allSpans.size()); - SpanData flowControlSpanData = allSpans.get(0); - SpanData publisherSpanData = allSpans.get(1); - - SpanDataAssert flowControlSpanDataAssert = - OpenTelemetryAssertions.assertThat(flowControlSpanData); - StatusData expectedStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown during publish flow control."); - flowControlSpanDataAssert - .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) - .hasParent(publisherSpanData) - .hasStatus(expectedStatus) - .hasException(e) - .hasEnded(); - - SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); - publisherSpanDataAssert - .hasName(PUBLISHER_SPAN_NAME) - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasEnded(); - } - - @Test - public void testPublishRpcSpanFailure() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); - - List messageWrappers = Arrays.asList(messageWrapper); - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startPublisherSpan(messageWrapper); - Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); - - Exception e = new Exception("test-exception"); - tracer.setPublishRpcSpanException(publishRpcSpan, e); - tracer.endPublisherSpan(messageWrapper); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(2, allSpans.size()); - SpanData rpcSpanData = allSpans.get(0); - SpanData publisherSpanData = allSpans.get(1); - - SpanDataAssert rpcSpanDataAssert = OpenTelemetryAssertions.assertThat(rpcSpanData); - StatusData expectedStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown on publish RPC."); - rpcSpanDataAssert - .hasName(PUBLISH_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasStatus(expectedStatus) - .hasException(e) - .hasEnded(); - - SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); - publisherSpanDataAssert - .hasName(PUBLISHER_SPAN_NAME) - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasEnded(); - } - - @Test - public void testSubscribeSpansSuccess() { - openTelemetryTesting.clearSpans(); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - PubsubMessageWrapper publishMessageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); - // Initialize the Publisher span to inject the context in the message - tracer.startPublisherSpan(publishMessageWrapper); - tracer.endPublisherSpan(publishMessageWrapper); - - PubsubMessage publishedMessage = - publishMessageWrapper.getPubsubMessage().toBuilder().setMessageId(MESSAGE_ID).build(); - PubsubMessageWrapper subscribeMessageWrapper = - PubsubMessageWrapper.newBuilder( - publishedMessage, FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, 1) - .build(); - List subscribeMessageWrappers = Arrays.asList(subscribeMessageWrapper); - - long messageSize = subscribeMessageWrapper.getPubsubMessage().getData().size(); - - // Call all span start/end methods in the expected order - tracer.startSubscriberSpan(subscribeMessageWrapper, EXACTLY_ONCE_ENABLED); - tracer.startSubscribeConcurrencyControlSpan(subscribeMessageWrapper); - tracer.endSubscribeConcurrencyControlSpan(subscribeMessageWrapper); - tracer.startSubscribeSchedulerSpan(subscribeMessageWrapper); - tracer.endSubscribeSchedulerSpan(subscribeMessageWrapper); - tracer.startSubscribeProcessSpan(subscribeMessageWrapper); - tracer.endSubscribeProcessSpan(subscribeMessageWrapper, PROCESS_ACTION); - Span subscribeModackRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), - "modack", - subscribeMessageWrappers, - ACK_DEADLINE, - true); - tracer.endSubscribeRpcSpan(subscribeModackRpcSpan); - tracer.addEndRpcEvent(subscribeMessageWrapper, true, true, ACK_DEADLINE); - Span subscribeAckRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "ack", subscribeMessageWrappers, 0, false); - tracer.endSubscribeRpcSpan(subscribeAckRpcSpan); - tracer.addEndRpcEvent(subscribeMessageWrapper, true, false, 0); - Span subscribeNackRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "nack", subscribeMessageWrappers, 0, false); - tracer.endSubscribeRpcSpan(subscribeNackRpcSpan); - tracer.addEndRpcEvent(subscribeMessageWrapper, true, true, 0); - tracer.endSubscriberSpan(subscribeMessageWrapper); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(8, allSpans.size()); - - SpanData publisherSpanData = allSpans.get(0); - SpanData concurrencyControlSpanData = allSpans.get(1); - SpanData schedulerSpanData = allSpans.get(2); - SpanData processSpanData = allSpans.get(3); - SpanData modackRpcSpanData = allSpans.get(4); - SpanData ackRpcSpanData = allSpans.get(5); - SpanData nackRpcSpanData = allSpans.get(6); - SpanData subscriberSpanData = allSpans.get(7); - - SpanDataAssert concurrencyControlSpanDataAssert = - OpenTelemetryAssertions.assertThat(concurrencyControlSpanData); - concurrencyControlSpanDataAssert - .hasName(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME) - .hasParent(subscriberSpanData) - .hasEnded(); - - SpanDataAssert schedulerSpanDataAssert = OpenTelemetryAssertions.assertThat(schedulerSpanData); - schedulerSpanDataAssert - .hasName(SUBSCRIBE_SCHEDULER_SPAN_NAME) - .hasParent(subscriberSpanData) - .hasEnded(); - - SpanDataAssert processSpanDataAssert = OpenTelemetryAssertions.assertThat(processSpanData); - processSpanDataAssert - .hasName(SUBSCRIBE_PROCESS_SPAN_NAME) - .hasParent(subscriberSpanData) - .hasEnded(); - - assertEquals(1, processSpanData.getEvents().size()); - EventDataAssert actionCalledEventAssert = - OpenTelemetryAssertions.assertThat(processSpanData.getEvents().get(0)); - actionCalledEventAssert.hasName(PROCESS_ACTION + " called"); - - // Check span data, links, and attributes for the modack RPC span - SpanDataAssert modackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(modackRpcSpanData); - modackRpcSpanDataAssert - .hasName(SUBSCRIBE_MODACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasEnded(); - - List modackRpcLinks = modackRpcSpanData.getLinks(); - assertEquals(subscribeMessageWrappers.size(), modackRpcLinks.size()); - assertEquals(subscriberSpanData.getSpanContext(), modackRpcLinks.get(0).getSpanContext()); - - assertEquals(8, modackRpcSpanData.getAttributes().size()); - AttributesAssert modackRpcSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(modackRpcSpanData.getAttributes()); - modackRpcSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry( - SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) - .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "modack") - .containsEntry( - SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()) - .containsEntry(ACK_DEADLINE_ATTR_KEY, 10) - .containsEntry(RECEIPT_MODACK_ATTR_KEY, true); - - // Check span data, links, and attributes for the ack RPC span - SpanDataAssert ackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(ackRpcSpanData); - ackRpcSpanDataAssert - .hasName(SUBSCRIBE_ACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasEnded(); - - List ackRpcLinks = ackRpcSpanData.getLinks(); - assertEquals(subscribeMessageWrappers.size(), ackRpcLinks.size()); - assertEquals(subscriberSpanData.getSpanContext(), ackRpcLinks.get(0).getSpanContext()); - - assertEquals(6, ackRpcSpanData.getAttributes().size()); - AttributesAssert ackRpcSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(ackRpcSpanData.getAttributes()); - ackRpcSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry( - SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) - .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendAckOperations") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "ack") - .containsEntry( - SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); - - // Check span data, links, and attributes for the nack RPC span - SpanDataAssert nackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(nackRpcSpanData); - nackRpcSpanDataAssert - .hasName(SUBSCRIBE_NACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasEnded(); - - List nackRpcLinks = nackRpcSpanData.getLinks(); - assertEquals(subscribeMessageWrappers.size(), nackRpcLinks.size()); - assertEquals(subscriberSpanData.getSpanContext(), nackRpcLinks.get(0).getSpanContext()); - - assertEquals(6, nackRpcSpanData.getAttributes().size()); - AttributesAssert nackRpcSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(nackRpcSpanData.getAttributes()); - nackRpcSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry( - SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) - .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "nack") - .containsEntry( - SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); - - // Check span data, events, links, and attributes for the publisher create span - SpanDataAssert subscriberSpanDataAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData); - subscriberSpanDataAssert - .hasName(SUBSCRIBER_SPAN_NAME) - .hasKind(SpanKind.CONSUMER) - .hasParent(publisherSpanData) - .hasEnded(); - - assertEquals(6, subscriberSpanData.getEvents().size()); - EventDataAssert startModackEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(0)); - startModackEventAssert.hasName(MODACK_START_EVENT); - EventDataAssert endModackEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(1)); - endModackEventAssert.hasName(MODACK_END_EVENT); - EventDataAssert startAckEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(2)); - startAckEventAssert.hasName(ACK_START_EVENT); - EventDataAssert endAckEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(3)); - endAckEventAssert.hasName(ACK_END_EVENT); - EventDataAssert startNackEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(4)); - startNackEventAssert.hasName(NACK_START_EVENT); - EventDataAssert endNackEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(5)); - endNackEventAssert.hasName(NACK_END_EVENT); - - List subscriberLinks = subscriberSpanData.getLinks(); - assertEquals(3, subscriberLinks.size()); - assertEquals(modackRpcSpanData.getSpanContext(), subscriberLinks.get(0).getSpanContext()); - assertEquals(ackRpcSpanData.getSpanContext(), subscriberLinks.get(1).getSpanContext()); - assertEquals(nackRpcSpanData.getSpanContext(), subscriberLinks.get(2).getSpanContext()); - - assertEquals(11, subscriberSpanData.getAttributes().size()); - AttributesAssert subscriberSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getAttributes()); - subscriberSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry( - SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) - .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "onResponse") - .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) - .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) - .containsEntry(MESSAGE_ACK_ID_ATTR_KEY, ACK_ID) - .containsEntry(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, DELIVERY_ATTEMPT) - .containsEntry(MESSAGE_EXACTLY_ONCE_ATTR_KEY, EXACTLY_ONCE_ENABLED) - .containsEntry(MESSAGE_RESULT_ATTR_KEY, PROCESS_ACTION) - .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); - } - - @Test - public void testSubscribeConcurrencyControlSpanFailure() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder( - getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) - .build(); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); - tracer.startSubscribeConcurrencyControlSpan(messageWrapper); - - Exception e = new Exception("test-exception"); - tracer.setSubscribeConcurrencyControlSpanException(messageWrapper, e); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(2, allSpans.size()); - SpanData concurrencyControlSpanData = allSpans.get(0); - SpanData subscriberSpanData = allSpans.get(1); - - SpanDataAssert concurrencyControlSpanDataAssert = - OpenTelemetryAssertions.assertThat(concurrencyControlSpanData); - StatusData expectedStatus = - StatusData.create( - StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); - concurrencyControlSpanDataAssert - .hasName(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME) - .hasParent(subscriberSpanData) - .hasStatus(expectedStatus) - .hasException(e) - .hasEnded(); - - SpanDataAssert subscriberSpanDataAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData); - subscriberSpanDataAssert - .hasName(SUBSCRIBER_SPAN_NAME) - .hasKind(SpanKind.CONSUMER) - .hasNoParent() - .hasEnded(); - } - - @Test - public void testSubscriberSpanFailure() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder( - getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) - .build(); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); - - Exception e = new Exception("test-exception"); - tracer.setSubscriberSpanException(messageWrapper, e, "Test exception"); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(1, allSpans.size()); - SpanData subscriberSpanData = allSpans.get(0); - - StatusData expectedStatus = StatusData.create(StatusCode.ERROR, "Test exception"); - SpanDataAssert subscriberSpanDataAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData); - subscriberSpanDataAssert - .hasName(SUBSCRIBER_SPAN_NAME) - .hasKind(SpanKind.CONSUMER) - .hasNoParent() - .hasStatus(expectedStatus) - .hasException(e) - .hasEnded(); - } - - @Test - public void testSubscribeRpcSpanFailures() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder( - getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) - .build(); - List messageWrappers = Arrays.asList(messageWrapper); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); - Span subscribeModackRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "modack", messageWrappers, ACK_DEADLINE, true); - Span subscribeAckRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "ack", messageWrappers, 0, false); - Span subscribeNackRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "nack", messageWrappers, 0, false); - - Exception e = new Exception("test-exception"); - tracer.setSubscribeRpcSpanException(subscribeModackRpcSpan, true, ACK_DEADLINE, e); - tracer.setSubscribeRpcSpanException(subscribeAckRpcSpan, false, 0, e); - tracer.setSubscribeRpcSpanException(subscribeNackRpcSpan, true, 0, e); - tracer.endSubscriberSpan(messageWrapper); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(4, allSpans.size()); - SpanData modackSpanData = allSpans.get(0); - SpanData ackSpanData = allSpans.get(1); - SpanData nackSpanData = allSpans.get(2); - SpanData subscriberSpanData = allSpans.get(3); - - StatusData expectedModackStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown on modack RPC."); - SpanDataAssert modackSpanDataAssert = OpenTelemetryAssertions.assertThat(modackSpanData); - modackSpanDataAssert - .hasName(SUBSCRIBE_MODACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasStatus(expectedModackStatus) - .hasException(e) - .hasEnded(); - - StatusData expectedAckStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown on ack RPC."); - SpanDataAssert ackSpanDataAssert = OpenTelemetryAssertions.assertThat(ackSpanData); - ackSpanDataAssert - .hasName(SUBSCRIBE_ACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasStatus(expectedAckStatus) - .hasException(e) - .hasEnded(); - - StatusData expectedNackStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown on nack RPC."); - SpanDataAssert nackSpanDataAssert = OpenTelemetryAssertions.assertThat(nackSpanData); - nackSpanDataAssert - .hasName(SUBSCRIBE_NACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasStatus(expectedNackStatus) - .hasException(e) - .hasEnded(); - } - - private PubsubMessage getPubsubMessage() { - return PubsubMessage.newBuilder() - .setData(ByteString.copyFromUtf8("test-data")) - .setOrderingKey(ORDERING_KEY) - .build(); - } -} From c711ea7a059afea87bbf0f591bd9d6d0b121fef8 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 30 Sep 2024 20:46:21 +0000 Subject: [PATCH 36/46] feat: trigger build From 8490bdfc54136505bcc0c189647829c0bb9503ea Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 30 Sep 2024 20:49:19 +0000 Subject: [PATCH 37/46] feat: Fix file overwrite from bad merge --- .../pubsub/v1/OpenTelemetryPubsubTracer.java | 460 ++++++++++++ .../cloud/pubsub/v1/PubsubMessageWrapper.java | 430 +++++++++++ .../cloud/pubsub/v1/OpenTelemetryTest.java | 669 ++++++++++++++++++ 3 files changed, 1559 insertions(+) create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java new file mode 100644 index 000000000..b946f44bf --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java @@ -0,0 +1,460 @@ +/* + * 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.pubsub.v1; + +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.SubscriptionName; +import com.google.pubsub.v1.TopicName; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.List; + +public class OpenTelemetryPubsubTracer { + private final Tracer tracer; + private boolean enabled = false; + + private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; + private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; + private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = + "subscriber concurrency control"; + private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; + + private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; + private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; + private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; + private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = + "messaging.gcp_pubsub.message.exactly_once_delivery"; + private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = + "messaging.gcp_pubsub.message.delivery_attempt"; + private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; + private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; + private static final String PROJECT_ATTR_KEY = "gcp.project_id"; + private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; + + private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; + + OpenTelemetryPubsubTracer(Tracer tracer, boolean enableOpenTelemetry) { + this.tracer = tracer; + if (this.tracer != null && enableOpenTelemetry) { + this.enabled = true; + } + } + + /** Populates attributes that are common the publisher parent span and publish RPC span. */ + private static final AttributesBuilder createCommonSpanAttributesBuilder( + String destinationName, String projectName, String codeFunction, String operation) { + AttributesBuilder attributesBuilder = + Attributes.builder() + .put(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .put(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName) + .put(PROJECT_ATTR_KEY, projectName) + .put(SemanticAttributes.CODE_FUNCTION, codeFunction); + if (operation != null) { + attributesBuilder.put(SemanticAttributes.MESSAGING_OPERATION, operation); + } + + return attributesBuilder; + } + + private Span startChildSpan(String name, Span parent) { + return tracer.spanBuilder(name).setParent(Context.current().with(parent)).startSpan(); + } + + /** + * Creates and starts the parent span with the appropriate span attributes and injects the span + * context into the {@link PubsubMessage} attributes. + */ + void startPublisherSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + AttributesBuilder attributesBuilder = + createCommonSpanAttributesBuilder( + message.getTopicName(), message.getTopicProject(), "publish", "create"); + + attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()); + if (!message.getOrderingKey().isEmpty()) { + attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); + } + + Span publisherSpan = + tracer + .spanBuilder(message.getTopicName() + " create") + .setSpanKind(SpanKind.PRODUCER) + .setAllAttributes(attributesBuilder.build()) + .startSpan(); + + message.setPublisherSpan(publisherSpan); + if (publisherSpan.getSpanContext().isValid()) { + message.injectSpanContext(); + } + } + + void endPublisherSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endPublisherSpan(); + } + + void setPublisherMessageIdSpanAttribute(PubsubMessageWrapper message, String messageId) { + if (!enabled) { + return; + } + message.setPublisherMessageIdSpanAttribute(messageId); + } + + /** Creates a span for publish-side flow control as a child of the parent publisher span. */ + void startPublishFlowControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span publisherSpan = message.getPublisherSpan(); + if (publisherSpan != null) + message.setPublishFlowControlSpan( + startChildSpan(PUBLISH_FLOW_CONTROL_SPAN_NAME, publisherSpan)); + } + + void endPublishFlowControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endPublishFlowControlSpan(); + } + + void setPublishFlowControlSpanException(PubsubMessageWrapper message, Throwable t) { + if (!enabled) { + return; + } + message.setPublishFlowControlSpanException(t); + } + + /** Creates a span for publish message batching as a child of the parent publisher span. */ + void startPublishBatchingSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span publisherSpan = message.getPublisherSpan(); + if (publisherSpan != null) { + message.setPublishBatchingSpan(startChildSpan(PUBLISH_BATCHING_SPAN_NAME, publisherSpan)); + } + } + + void endPublishBatchingSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endPublishBatchingSpan(); + } + + /** + * Creates, starts, and returns a publish RPC span for the given message batch. Bi-directional + * links with the publisher parent span are created for sampled messages in the batch. + */ + Span startPublishRpcSpan(String topic, List messages) { + if (!enabled) { + return null; + } + TopicName topicName = TopicName.parse(topic); + Attributes attributes = + createCommonSpanAttributesBuilder( + topicName.getTopic(), topicName.getProject(), "publishCall", "publish") + .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()) + .build(); + SpanBuilder publishRpcSpanBuilder = + tracer + .spanBuilder(topicName.getTopic() + PUBLISH_RPC_SPAN_SUFFIX) + .setSpanKind(SpanKind.CLIENT) + .setAllAttributes(attributes); + Attributes linkAttributes = + Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, "publish").build(); + for (PubsubMessageWrapper message : messages) { + if (message.getPublisherSpan().getSpanContext().isSampled()) + publishRpcSpanBuilder.addLink(message.getPublisherSpan().getSpanContext(), linkAttributes); + } + Span publishRpcSpan = publishRpcSpanBuilder.startSpan(); + + for (PubsubMessageWrapper message : messages) { + if (publishRpcSpan.getSpanContext().isSampled()) { + message.getPublisherSpan().addLink(publishRpcSpan.getSpanContext(), linkAttributes); + message.addPublishStartEvent(); + } + } + return publishRpcSpan; + } + + /** Ends the given publish RPC span if it exists. */ + void endPublishRpcSpan(Span publishRpcSpan) { + if (!enabled) { + return; + } + if (publishRpcSpan != null) { + publishRpcSpan.end(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown when publishing the + * message batch. + */ + void setPublishRpcSpanException(Span publishRpcSpan, Throwable t) { + if (!enabled) { + return; + } + if (publishRpcSpan != null) { + publishRpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on publish RPC."); + publishRpcSpan.recordException(t); + publishRpcSpan.end(); + } + } + + void startSubscriberSpan(PubsubMessageWrapper message, boolean exactlyOnceDeliveryEnabled) { + if (!enabled) { + return; + } + AttributesBuilder attributesBuilder = + createCommonSpanAttributesBuilder( + message.getSubscriptionName(), message.getSubscriptionProject(), "onResponse", null); + + attributesBuilder + .put(SemanticAttributes.MESSAGING_MESSAGE_ID, message.getMessageId()) + .put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()) + .put(MESSAGE_ACK_ID_ATTR_KEY, message.getAckId()) + .put(MESSAGE_EXACTLY_ONCE_ATTR_KEY, exactlyOnceDeliveryEnabled); + if (!message.getOrderingKey().isEmpty()) { + attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); + } + if (message.getDeliveryAttempt() > 0) { + attributesBuilder.put(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, message.getDeliveryAttempt()); + } + Attributes attributes = attributesBuilder.build(); + Context publisherSpanContext = message.extractSpanContext(attributes); + message.setPublisherSpan(Span.fromContextOrNull(publisherSpanContext)); + message.setSubscriberSpan( + tracer + .spanBuilder(message.getSubscriptionName() + " subscribe") + .setSpanKind(SpanKind.CONSUMER) + .setParent(publisherSpanContext) + .setAllAttributes(attributes) + .startSpan()); + } + + void endSubscriberSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endSubscriberSpan(); + } + + void setSubscriberSpanExpirationResult(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.setSubscriberSpanExpirationResult(); + } + + void setSubscriberSpanException(PubsubMessageWrapper message, Throwable t, String exception) { + if (!enabled) { + return; + } + message.setSubscriberSpanException(t, exception); + } + + /** Creates a span for subscribe concurrency control as a child of the parent subscriber span. */ + void startSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span subscriberSpan = message.getSubscriberSpan(); + if (subscriberSpan != null) { + message.setSubscribeConcurrencyControlSpan( + startChildSpan(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME, subscriberSpan)); + } + } + + void endSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endSubscribeConcurrencyControlSpan(); + } + + void setSubscribeConcurrencyControlSpanException(PubsubMessageWrapper message, Throwable t) { + if (!enabled) { + return; + } + message.setSubscribeConcurrencyControlSpanException(t); + } + + /** + * Creates a span for subscribe ordering key scheduling as a child of the parent subscriber span. + */ + void startSubscribeSchedulerSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span subscriberSpan = message.getSubscriberSpan(); + if (subscriberSpan != null) { + message.setSubscribeSchedulerSpan( + startChildSpan(SUBSCRIBE_SCHEDULER_SPAN_NAME, subscriberSpan)); + } + } + + void endSubscribeSchedulerSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endSubscribeSchedulerSpan(); + } + + /** Creates a span for subscribe message processing as a child of the parent subscriber span. */ + void startSubscribeProcessSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span subscriberSpan = message.getSubscriberSpan(); + if (subscriberSpan != null) { + Span subscribeProcessSpan = + startChildSpan(message.getSubscriptionName() + " process", subscriberSpan); + subscribeProcessSpan.setAttribute( + SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE); + Span publisherSpan = message.getPublisherSpan(); + if (publisherSpan != null) { + subscribeProcessSpan.addLink(publisherSpan.getSpanContext()); + } + message.setSubscribeProcessSpan(subscribeProcessSpan); + } + } + + void endSubscribeProcessSpan(PubsubMessageWrapper message, String action) { + if (!enabled) { + return; + } + message.endSubscribeProcessSpan(action); + } + + /** + * Creates, starts, and returns spans for ModAck, Nack, and Ack RPC requests. Bi-directional links + * to parent subscribe span for sampled messages are added. + */ + Span startSubscribeRpcSpan( + String subscription, + String rpcOperation, + List messages, + int ackDeadline, + boolean isReceiptModack) { + if (!enabled) { + return null; + } + String codeFunction = rpcOperation == "ack" ? "sendAckOperations" : "sendModAckOperations"; + SubscriptionName subscriptionName = SubscriptionName.parse(subscription); + AttributesBuilder attributesBuilder = + createCommonSpanAttributesBuilder( + subscriptionName.getSubscription(), + subscriptionName.getProject(), + codeFunction, + rpcOperation) + .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()); + + // Ack deadline and receipt modack are specific to the modack operation + if (rpcOperation == "modack") { + attributesBuilder + .put(ACK_DEADLINE_ATTR_KEY, ackDeadline) + .put(RECEIPT_MODACK_ATTR_KEY, isReceiptModack); + } + + SpanBuilder rpcSpanBuilder = + tracer + .spanBuilder(subscriptionName.getSubscription() + " " + rpcOperation) + .setSpanKind(SpanKind.CLIENT) + .setAllAttributes(attributesBuilder.build()); + Attributes linkAttributes = + Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, rpcOperation).build(); + for (PubsubMessageWrapper message : messages) { + if (message.getSubscriberSpan().getSpanContext().isSampled()) { + rpcSpanBuilder.addLink(message.getSubscriberSpan().getSpanContext(), linkAttributes); + } + } + Span rpcSpan = rpcSpanBuilder.startSpan(); + + for (PubsubMessageWrapper message : messages) { + if (rpcSpan.getSpanContext().isSampled()) { + message.getSubscriberSpan().addLink(rpcSpan.getSpanContext(), linkAttributes); + switch (rpcOperation) { + case "ack": + message.addAckStartEvent(); + break; + case "modack": + message.addModAckStartEvent(); + break; + case "nack": + message.addNackStartEvent(); + break; + } + } + } + return rpcSpan; + } + + /** Ends the given subscribe RPC span if it exists. */ + void endSubscribeRpcSpan(Span rpcSpan) { + if (!enabled) { + return; + } + if (rpcSpan != null) { + rpcSpan.end(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown when handling a + * subscribe-side RPC. + */ + void setSubscribeRpcSpanException(Span rpcSpan, boolean isModack, int ackDeadline, Throwable t) { + if (!enabled) { + return; + } + if (rpcSpan != null) { + String operation = !isModack ? "ack" : (ackDeadline == 0 ? "nack" : "modack"); + rpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on " + operation + " RPC."); + rpcSpan.recordException(t); + rpcSpan.end(); + } + } + + /** Adds the appropriate subscribe-side RPC end event. */ + void addEndRpcEvent( + PubsubMessageWrapper message, boolean rpcSampled, boolean isModack, int ackDeadline) { + if (!enabled || !rpcSampled) { + return; + } + if (!isModack) { + message.addAckEndEvent(); + } else if (ackDeadline == 0) { + message.addNackEndEvent(); + } else { + message.addModAckEndEvent(); + } + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java new file mode 100644 index 000000000..94fd13085 --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java @@ -0,0 +1,430 @@ +/* + * 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.pubsub.v1; + +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.SubscriptionName; +import com.google.pubsub.v1.TopicName; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.context.propagation.TextMapSetter; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; + +/** + * A wrapper class for a {@link PubsubMessage} object that handles creation and tracking of + * OpenTelemetry {@link Span} objects for different operations that occur during publishing. + */ +public class PubsubMessageWrapper { + private PubsubMessage message; + + private final TopicName topicName; + private final SubscriptionName subscriptionName; + + // Attributes set only for messages received from a streaming pull response. + private final String ackId; + private final int deliveryAttempt; + + private static final String PUBLISH_START_EVENT = "publish start"; + private static final String PUBLISH_END_EVENT = "publish end"; + + private static final String MODACK_START_EVENT = "modack start"; + private static final String MODACK_END_EVENT = "modack end"; + private static final String NACK_START_EVENT = "nack start"; + private static final String NACK_END_EVENT = "nack end"; + private static final String ACK_START_EVENT = "ack start"; + private static final String ACK_END_EVENT = "ack end"; + + private static final String GOOGCLIENT_PREFIX = "googclient_"; + + private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; + + private Span publisherSpan; + private Span publishFlowControlSpan; + private Span publishBatchingSpan; + + private Span subscriberSpan; + private Span subscribeConcurrencyControlSpan; + private Span subscribeSchedulerSpan; + private Span subscribeProcessSpan; + + private PubsubMessageWrapper(Builder builder) { + this.message = builder.message; + this.topicName = builder.topicName; + this.subscriptionName = builder.subscriptionName; + this.ackId = builder.ackId; + this.deliveryAttempt = builder.deliveryAttempt; + } + + static Builder newBuilder(PubsubMessage message, String topicName) { + return new Builder(message, topicName); + } + + static Builder newBuilder( + PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { + return new Builder(message, subscriptionName, ackId, deliveryAttempt); + } + + /** Returns the PubsubMessage associated with this wrapper. */ + PubsubMessage getPubsubMessage() { + return message; + } + + void setPubsubMessage(PubsubMessage message) { + this.message = message; + } + + /** Returns the TopicName for this wrapper as a string. */ + String getTopicName() { + if (topicName != null) { + return topicName.getTopic(); + } + return ""; + } + + String getTopicProject() { + if (topicName != null) { + return topicName.getProject(); + } + return ""; + } + + /** Returns the SubscriptionName for this wrapper as a string. */ + String getSubscriptionName() { + if (subscriptionName != null) { + return subscriptionName.getSubscription(); + } + return ""; + } + + String getSubscriptionProject() { + if (subscriptionName != null) { + return subscriptionName.getProject(); + } + return ""; + } + + String getMessageId() { + return message.getMessageId(); + } + + String getAckId() { + return ackId; + } + + int getDataSize() { + return message.getData().size(); + } + + String getOrderingKey() { + return message.getOrderingKey(); + } + + int getDeliveryAttempt() { + return deliveryAttempt; + } + + Span getPublisherSpan() { + return publisherSpan; + } + + void setPublisherSpan(Span span) { + this.publisherSpan = span; + } + + void setPublishFlowControlSpan(Span span) { + this.publishFlowControlSpan = span; + } + + void setPublishBatchingSpan(Span span) { + this.publishBatchingSpan = span; + } + + Span getSubscriberSpan() { + return subscriberSpan; + } + + void setSubscriberSpan(Span span) { + this.subscriberSpan = span; + } + + void setSubscribeConcurrencyControlSpan(Span span) { + this.subscribeConcurrencyControlSpan = span; + } + + void setSubscribeSchedulerSpan(Span span) { + this.subscribeSchedulerSpan = span; + } + + void setSubscribeProcessSpan(Span span) { + this.subscribeProcessSpan = span; + } + + /** Creates a publish start event that is tied to the publish RPC span time. */ + void addPublishStartEvent() { + if (publisherSpan != null) { + publisherSpan.addEvent(PUBLISH_START_EVENT); + } + } + + /** + * Sets the message ID attribute in the publisher parent span. This is called after the publish + * RPC returns with a message ID. + */ + void setPublisherMessageIdSpanAttribute(String messageId) { + if (publisherSpan != null) { + publisherSpan.setAttribute(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId); + } + } + + /** Ends the publisher parent span if it exists. */ + void endPublisherSpan() { + if (publisherSpan != null) { + publisherSpan.addEvent(PUBLISH_END_EVENT); + publisherSpan.end(); + } + } + + /** Ends the publish flow control span if it exists. */ + void endPublishFlowControlSpan() { + if (publishFlowControlSpan != null) { + publishFlowControlSpan.end(); + } + } + + /** Ends the publish batching span if it exists. */ + void endPublishBatchingSpan() { + if (publishBatchingSpan != null) { + publishBatchingSpan.end(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown during flow control. + */ + void setPublishFlowControlSpanException(Throwable t) { + if (publishFlowControlSpan != null) { + publishFlowControlSpan.setStatus( + StatusCode.ERROR, "Exception thrown during publish flow control."); + publishFlowControlSpan.recordException(t); + endAllPublishSpans(); + } + } + + /** + * Creates start and end events for ModAcks, Nacks, and Acks that are tied to the corresponding + * RPC span start and end times. + */ + void addModAckStartEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(MODACK_START_EVENT); + } + } + + void addModAckEndEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(MODACK_END_EVENT); + } + } + + void addNackStartEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(NACK_START_EVENT); + } + } + + void addNackEndEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(NACK_END_EVENT); + } + } + + void addAckStartEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(ACK_START_EVENT); + } + } + + void addAckEndEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(ACK_END_EVENT); + } + } + + /** Ends the subscriber parent span if exists. */ + void endSubscriberSpan() { + if (subscriberSpan != null) { + subscriberSpan.end(); + } + } + + /** Ends the subscribe concurreny control span if exists. */ + void endSubscribeConcurrencyControlSpan() { + if (subscribeConcurrencyControlSpan != null) { + subscribeConcurrencyControlSpan.end(); + } + } + + /** Ends the subscribe scheduler span if exists. */ + void endSubscribeSchedulerSpan() { + if (subscribeSchedulerSpan != null) { + subscribeSchedulerSpan.end(); + } + } + + /** + * Ends the subscribe process span if it exists, creates an event with the appropriate result, and + * sets the result on the parent subscriber span. + */ + void endSubscribeProcessSpan(String action) { + if (subscribeProcessSpan != null) { + subscribeProcessSpan.addEvent(action + " called"); + subscribeProcessSpan.end(); + subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, action); + } + } + + /** Sets an exception on the subscriber span during Ack/ModAck/Nack failures */ + void setSubscriberSpanException(Throwable t, String exception) { + if (subscriberSpan != null) { + subscriberSpan.setStatus(StatusCode.ERROR, exception); + subscriberSpan.recordException(t); + endAllSubscribeSpans(); + } + } + + /** Sets result of the parent subscriber span to expired and ends its. */ + void setSubscriberSpanExpirationResult() { + if (subscriberSpan != null) { + subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, "expired"); + endSubscriberSpan(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown subscriber + * concurrency control. + */ + void setSubscribeConcurrencyControlSpanException(Throwable t) { + if (subscribeConcurrencyControlSpan != null) { + subscribeConcurrencyControlSpan.setStatus( + StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); + subscribeConcurrencyControlSpan.recordException(t); + endAllSubscribeSpans(); + } + } + + /** Ends all publisher-side spans associated with this message wrapper. */ + private void endAllPublishSpans() { + endPublishFlowControlSpan(); + endPublishBatchingSpan(); + endPublisherSpan(); + } + + /** Ends all subscriber-side spans associated with this message wrapper. */ + private void endAllSubscribeSpans() { + endSubscribeConcurrencyControlSpan(); + endSubscribeSchedulerSpan(); + endSubscriberSpan(); + } + + /** + * Injects the span context into the attributes of a Pub/Sub message for propagation to the + * subscriber client. + */ + void injectSpanContext() { + TextMapSetter injectMessageAttributes = + new TextMapSetter() { + @Override + public void set(PubsubMessageWrapper carrier, String key, String value) { + PubsubMessage newMessage = + PubsubMessage.newBuilder(carrier.message) + .putAttributes(GOOGCLIENT_PREFIX + key, value) + .build(); + carrier.message = newMessage; + } + }; + W3CTraceContextPropagator.getInstance() + .inject(Context.current().with(publisherSpan), this, injectMessageAttributes); + } + + /** + * Extracts the span context from the attributes of a Pub/Sub message and creates the parent + * subscriber span using that context. + */ + Context extractSpanContext(Attributes attributes) { + TextMapGetter extractMessageAttributes = + new TextMapGetter() { + @Override + public String get(PubsubMessageWrapper carrier, String key) { + return carrier.message.getAttributesOrDefault(GOOGCLIENT_PREFIX + key, ""); + } + + public Iterable keys(PubsubMessageWrapper carrier) { + return carrier.message.getAttributesMap().keySet(); + } + }; + Context context = + W3CTraceContextPropagator.getInstance() + .extract(Context.current(), this, extractMessageAttributes); + return context; + } + + /** Builder of {@link PubsubMessageWrapper PubsubMessageWrapper}. */ + static final class Builder { + private PubsubMessage message = null; + private TopicName topicName = null; + private SubscriptionName subscriptionName = null; + private String ackId = null; + private int deliveryAttempt = 0; + + public Builder(PubsubMessage message, String topicName) { + this.message = message; + if (topicName != null) { + this.topicName = TopicName.parse(topicName); + } + } + + public Builder( + PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { + this.message = message; + if (subscriptionName != null) { + this.subscriptionName = SubscriptionName.parse(subscriptionName); + } + this.ackId = ackId; + this.deliveryAttempt = deliveryAttempt; + } + + public Builder( + PubsubMessage message, + SubscriptionName subscriptionName, + String ackId, + int deliveryAttempt) { + this.message = message; + this.subscriptionName = subscriptionName; + this.ackId = ackId; + this.deliveryAttempt = deliveryAttempt; + } + + public PubsubMessageWrapper build() { + return new PubsubMessageWrapper(this); + } + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java new file mode 100644 index 000000000..b4433f41e --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -0,0 +1,669 @@ +/* + * 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.pubsub.v1; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.google.protobuf.ByteString; +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.SubscriptionName; +import com.google.pubsub.v1.TopicName; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.sdk.testing.assertj.AttributesAssert; +import io.opentelemetry.sdk.testing.assertj.EventDataAssert; +import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.testing.junit4.OpenTelemetryRule; +import io.opentelemetry.sdk.trace.data.LinkData; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.data.StatusData; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.Arrays; +import java.util.List; +import org.junit.Test; + +public class OpenTelemetryTest { + private static final TopicName FULL_TOPIC_NAME = + TopicName.parse("projects/test-project/topics/test-topic"); + private static final SubscriptionName FULL_SUBSCRIPTION_NAME = + SubscriptionName.parse("projects/test-project/subscriptions/test-sub"); + private static final String PROJECT_NAME = "test-project"; + private static final String ORDERING_KEY = "abc"; + private static final String MESSAGE_ID = "m0"; + private static final String ACK_ID = "def"; + private static final int DELIVERY_ATTEMPT = 1; + private static final int ACK_DEADLINE = 10; + private static final boolean EXACTLY_ONCE_ENABLED = true; + + private static final String PUBLISHER_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " create"; + private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; + private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; + private static final String PUBLISH_RPC_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " publish"; + private static final String PUBLISH_START_EVENT = "publish start"; + private static final String PUBLISH_END_EVENT = "publish end"; + + private static final String SUBSCRIBER_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " subscribe"; + private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = + "subscriber concurrency control"; + private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; + private static final String SUBSCRIBE_PROCESS_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " process"; + private static final String SUBSCRIBE_MODACK_RPC_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " modack"; + private static final String SUBSCRIBE_ACK_RPC_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " ack"; + private static final String SUBSCRIBE_NACK_RPC_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " nack"; + + private static final String PROCESS_ACTION = "ack"; + private static final String MODACK_START_EVENT = "modack start"; + private static final String MODACK_END_EVENT = "modack end"; + private static final String NACK_START_EVENT = "nack start"; + private static final String NACK_END_EVENT = "nack end"; + private static final String ACK_START_EVENT = "ack start"; + private static final String ACK_END_EVENT = "ack end"; + + private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; + private static final String PROJECT_ATTR_KEY = "gcp.project_id"; + private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; + private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; + private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; + private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; + private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; + private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = + "messaging.gcp_pubsub.message.exactly_once_delivery"; + private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; + private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = + "messaging.gcp_pubsub.message.delivery_attempt"; + + private static final String TRACEPARENT_ATTRIBUTE = "googclient_traceparent"; + + private static final OpenTelemetryRule openTelemetryTesting = OpenTelemetryRule.create(); + + @Test + public void testPublishSpansSuccess() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); + List messageWrappers = Arrays.asList(messageWrapper); + + long messageSize = messageWrapper.getPubsubMessage().getData().size(); + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + // Call all span start/end methods in the expected order + tracer.startPublisherSpan(messageWrapper); + tracer.startPublishFlowControlSpan(messageWrapper); + tracer.endPublishFlowControlSpan(messageWrapper); + tracer.startPublishBatchingSpan(messageWrapper); + tracer.endPublishBatchingSpan(messageWrapper); + Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); + tracer.endPublishRpcSpan(publishRpcSpan); + tracer.setPublisherMessageIdSpanAttribute(messageWrapper, MESSAGE_ID); + tracer.endPublisherSpan(messageWrapper); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(4, allSpans.size()); + SpanData flowControlSpanData = allSpans.get(0); + SpanData batchingSpanData = allSpans.get(1); + SpanData publishRpcSpanData = allSpans.get(2); + SpanData publisherSpanData = allSpans.get(3); + + SpanDataAssert flowControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(flowControlSpanData); + flowControlSpanDataAssert + .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) + .hasParent(publisherSpanData) + .hasEnded(); + + SpanDataAssert batchingSpanDataAssert = OpenTelemetryAssertions.assertThat(batchingSpanData); + batchingSpanDataAssert + .hasName(PUBLISH_BATCHING_SPAN_NAME) + .hasParent(publisherSpanData) + .hasEnded(); + + // Check span data, links, and attributes for the publish RPC span + SpanDataAssert publishRpcSpanDataAssert = + OpenTelemetryAssertions.assertThat(publishRpcSpanData); + publishRpcSpanDataAssert + .hasName(PUBLISH_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List publishRpcLinks = publishRpcSpanData.getLinks(); + assertEquals(messageWrappers.size(), publishRpcLinks.size()); + assertEquals(publisherSpanData.getSpanContext(), publishRpcLinks.get(0).getSpanContext()); + + assertEquals(6, publishRpcSpanData.getAttributes().size()); + AttributesAssert publishRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(publishRpcSpanData.getAttributes()); + publishRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "publishCall") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "publish") + .containsEntry(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messageWrappers.size()); + + // Check span data, events, links, and attributes for the publisher create span + SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publisherSpanDataAssert + .hasName(PUBLISHER_SPAN_NAME) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasEnded(); + + assertEquals(2, publisherSpanData.getEvents().size()); + EventDataAssert startEventAssert = + OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(0)); + startEventAssert.hasName(PUBLISH_START_EVENT); + EventDataAssert endEventAssert = + OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(1)); + endEventAssert.hasName(PUBLISH_END_EVENT); + + List publisherLinks = publisherSpanData.getLinks(); + assertEquals(1, publisherLinks.size()); + assertEquals(publishRpcSpanData.getSpanContext(), publisherLinks.get(0).getSpanContext()); + + assertEquals(8, publisherSpanData.getAttributes().size()); + AttributesAssert publisherSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(publisherSpanData.getAttributes()); + publisherSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) + .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "publish") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "create") + .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) + .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) + .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); + + // Check that the message has the attribute containing the trace context. + PubsubMessage message = messageWrapper.getPubsubMessage(); + assertEquals(1, message.getAttributesMap().size()); + assertTrue(message.containsAttributes(TRACEPARENT_ATTRIBUTE)); + assertTrue( + message + .getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "") + .contains(publisherSpanData.getTraceId())); + assertTrue( + message + .getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "") + .contains(publisherSpanData.getSpanId())); + } + + @Test + public void testPublishFlowControlSpanFailure() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startPublisherSpan(messageWrapper); + tracer.startPublishFlowControlSpan(messageWrapper); + + Exception e = new Exception("test-exception"); + tracer.setPublishFlowControlSpanException(messageWrapper, e); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(2, allSpans.size()); + SpanData flowControlSpanData = allSpans.get(0); + SpanData publisherSpanData = allSpans.get(1); + + SpanDataAssert flowControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(flowControlSpanData); + StatusData expectedStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown during publish flow control."); + flowControlSpanDataAssert + .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) + .hasParent(publisherSpanData) + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + + SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publisherSpanDataAssert + .hasName(PUBLISHER_SPAN_NAME) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasEnded(); + } + + @Test + public void testPublishRpcSpanFailure() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); + + List messageWrappers = Arrays.asList(messageWrapper); + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startPublisherSpan(messageWrapper); + Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); + + Exception e = new Exception("test-exception"); + tracer.setPublishRpcSpanException(publishRpcSpan, e); + tracer.endPublisherSpan(messageWrapper); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(2, allSpans.size()); + SpanData rpcSpanData = allSpans.get(0); + SpanData publisherSpanData = allSpans.get(1); + + SpanDataAssert rpcSpanDataAssert = OpenTelemetryAssertions.assertThat(rpcSpanData); + StatusData expectedStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on publish RPC."); + rpcSpanDataAssert + .hasName(PUBLISH_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + + SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publisherSpanDataAssert + .hasName(PUBLISHER_SPAN_NAME) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasEnded(); + } + + @Test + public void testSubscribeSpansSuccess() { + openTelemetryTesting.clearSpans(); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + PubsubMessageWrapper publishMessageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); + // Initialize the Publisher span to inject the context in the message + tracer.startPublisherSpan(publishMessageWrapper); + tracer.endPublisherSpan(publishMessageWrapper); + + PubsubMessage publishedMessage = + publishMessageWrapper.getPubsubMessage().toBuilder().setMessageId(MESSAGE_ID).build(); + PubsubMessageWrapper subscribeMessageWrapper = + PubsubMessageWrapper.newBuilder( + publishedMessage, FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, 1) + .build(); + List subscribeMessageWrappers = Arrays.asList(subscribeMessageWrapper); + + long messageSize = subscribeMessageWrapper.getPubsubMessage().getData().size(); + + // Call all span start/end methods in the expected order + tracer.startSubscriberSpan(subscribeMessageWrapper, EXACTLY_ONCE_ENABLED); + tracer.startSubscribeConcurrencyControlSpan(subscribeMessageWrapper); + tracer.endSubscribeConcurrencyControlSpan(subscribeMessageWrapper); + tracer.startSubscribeSchedulerSpan(subscribeMessageWrapper); + tracer.endSubscribeSchedulerSpan(subscribeMessageWrapper); + tracer.startSubscribeProcessSpan(subscribeMessageWrapper); + tracer.endSubscribeProcessSpan(subscribeMessageWrapper, PROCESS_ACTION); + Span subscribeModackRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), + "modack", + subscribeMessageWrappers, + ACK_DEADLINE, + true); + tracer.endSubscribeRpcSpan(subscribeModackRpcSpan); + tracer.addEndRpcEvent(subscribeMessageWrapper, true, true, ACK_DEADLINE); + Span subscribeAckRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "ack", subscribeMessageWrappers, 0, false); + tracer.endSubscribeRpcSpan(subscribeAckRpcSpan); + tracer.addEndRpcEvent(subscribeMessageWrapper, true, false, 0); + Span subscribeNackRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "nack", subscribeMessageWrappers, 0, false); + tracer.endSubscribeRpcSpan(subscribeNackRpcSpan); + tracer.addEndRpcEvent(subscribeMessageWrapper, true, true, 0); + tracer.endSubscriberSpan(subscribeMessageWrapper); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(8, allSpans.size()); + + SpanData publisherSpanData = allSpans.get(0); + SpanData concurrencyControlSpanData = allSpans.get(1); + SpanData schedulerSpanData = allSpans.get(2); + SpanData processSpanData = allSpans.get(3); + SpanData modackRpcSpanData = allSpans.get(4); + SpanData ackRpcSpanData = allSpans.get(5); + SpanData nackRpcSpanData = allSpans.get(6); + SpanData subscriberSpanData = allSpans.get(7); + + SpanDataAssert concurrencyControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(concurrencyControlSpanData); + concurrencyControlSpanDataAssert + .hasName(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasEnded(); + + SpanDataAssert schedulerSpanDataAssert = OpenTelemetryAssertions.assertThat(schedulerSpanData); + schedulerSpanDataAssert + .hasName(SUBSCRIBE_SCHEDULER_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasEnded(); + + SpanDataAssert processSpanDataAssert = OpenTelemetryAssertions.assertThat(processSpanData); + processSpanDataAssert + .hasName(SUBSCRIBE_PROCESS_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasEnded(); + + assertEquals(1, processSpanData.getEvents().size()); + EventDataAssert actionCalledEventAssert = + OpenTelemetryAssertions.assertThat(processSpanData.getEvents().get(0)); + actionCalledEventAssert.hasName(PROCESS_ACTION + " called"); + + // Check span data, links, and attributes for the modack RPC span + SpanDataAssert modackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(modackRpcSpanData); + modackRpcSpanDataAssert + .hasName(SUBSCRIBE_MODACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List modackRpcLinks = modackRpcSpanData.getLinks(); + assertEquals(subscribeMessageWrappers.size(), modackRpcLinks.size()); + assertEquals(subscriberSpanData.getSpanContext(), modackRpcLinks.get(0).getSpanContext()); + + assertEquals(8, modackRpcSpanData.getAttributes().size()); + AttributesAssert modackRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(modackRpcSpanData.getAttributes()); + modackRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "modack") + .containsEntry( + SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()) + .containsEntry(ACK_DEADLINE_ATTR_KEY, 10) + .containsEntry(RECEIPT_MODACK_ATTR_KEY, true); + + // Check span data, links, and attributes for the ack RPC span + SpanDataAssert ackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(ackRpcSpanData); + ackRpcSpanDataAssert + .hasName(SUBSCRIBE_ACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List ackRpcLinks = ackRpcSpanData.getLinks(); + assertEquals(subscribeMessageWrappers.size(), ackRpcLinks.size()); + assertEquals(subscriberSpanData.getSpanContext(), ackRpcLinks.get(0).getSpanContext()); + + assertEquals(6, ackRpcSpanData.getAttributes().size()); + AttributesAssert ackRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(ackRpcSpanData.getAttributes()); + ackRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendAckOperations") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "ack") + .containsEntry( + SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); + + // Check span data, links, and attributes for the nack RPC span + SpanDataAssert nackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(nackRpcSpanData); + nackRpcSpanDataAssert + .hasName(SUBSCRIBE_NACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List nackRpcLinks = nackRpcSpanData.getLinks(); + assertEquals(subscribeMessageWrappers.size(), nackRpcLinks.size()); + assertEquals(subscriberSpanData.getSpanContext(), nackRpcLinks.get(0).getSpanContext()); + + assertEquals(6, nackRpcSpanData.getAttributes().size()); + AttributesAssert nackRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(nackRpcSpanData.getAttributes()); + nackRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "nack") + .containsEntry( + SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); + + // Check span data, events, links, and attributes for the publisher create span + SpanDataAssert subscriberSpanDataAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData); + subscriberSpanDataAssert + .hasName(SUBSCRIBER_SPAN_NAME) + .hasKind(SpanKind.CONSUMER) + .hasParent(publisherSpanData) + .hasEnded(); + + assertEquals(6, subscriberSpanData.getEvents().size()); + EventDataAssert startModackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(0)); + startModackEventAssert.hasName(MODACK_START_EVENT); + EventDataAssert endModackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(1)); + endModackEventAssert.hasName(MODACK_END_EVENT); + EventDataAssert startAckEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(2)); + startAckEventAssert.hasName(ACK_START_EVENT); + EventDataAssert endAckEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(3)); + endAckEventAssert.hasName(ACK_END_EVENT); + EventDataAssert startNackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(4)); + startNackEventAssert.hasName(NACK_START_EVENT); + EventDataAssert endNackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(5)); + endNackEventAssert.hasName(NACK_END_EVENT); + + List subscriberLinks = subscriberSpanData.getLinks(); + assertEquals(3, subscriberLinks.size()); + assertEquals(modackRpcSpanData.getSpanContext(), subscriberLinks.get(0).getSpanContext()); + assertEquals(ackRpcSpanData.getSpanContext(), subscriberLinks.get(1).getSpanContext()); + assertEquals(nackRpcSpanData.getSpanContext(), subscriberLinks.get(2).getSpanContext()); + + assertEquals(11, subscriberSpanData.getAttributes().size()); + AttributesAssert subscriberSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getAttributes()); + subscriberSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "onResponse") + .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) + .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) + .containsEntry(MESSAGE_ACK_ID_ATTR_KEY, ACK_ID) + .containsEntry(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, DELIVERY_ATTEMPT) + .containsEntry(MESSAGE_EXACTLY_ONCE_ATTR_KEY, EXACTLY_ONCE_ENABLED) + .containsEntry(MESSAGE_RESULT_ATTR_KEY, PROCESS_ACTION) + .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); + } + + @Test + public void testSubscribeConcurrencyControlSpanFailure() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder( + getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) + .build(); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); + tracer.startSubscribeConcurrencyControlSpan(messageWrapper); + + Exception e = new Exception("test-exception"); + tracer.setSubscribeConcurrencyControlSpanException(messageWrapper, e); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(2, allSpans.size()); + SpanData concurrencyControlSpanData = allSpans.get(0); + SpanData subscriberSpanData = allSpans.get(1); + + SpanDataAssert concurrencyControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(concurrencyControlSpanData); + StatusData expectedStatus = + StatusData.create( + StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); + concurrencyControlSpanDataAssert + .hasName(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + + SpanDataAssert subscriberSpanDataAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData); + subscriberSpanDataAssert + .hasName(SUBSCRIBER_SPAN_NAME) + .hasKind(SpanKind.CONSUMER) + .hasNoParent() + .hasEnded(); + } + + @Test + public void testSubscriberSpanFailure() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder( + getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) + .build(); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); + + Exception e = new Exception("test-exception"); + tracer.setSubscriberSpanException(messageWrapper, e, "Test exception"); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(1, allSpans.size()); + SpanData subscriberSpanData = allSpans.get(0); + + StatusData expectedStatus = StatusData.create(StatusCode.ERROR, "Test exception"); + SpanDataAssert subscriberSpanDataAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData); + subscriberSpanDataAssert + .hasName(SUBSCRIBER_SPAN_NAME) + .hasKind(SpanKind.CONSUMER) + .hasNoParent() + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + } + + @Test + public void testSubscribeRpcSpanFailures() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder( + getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) + .build(); + List messageWrappers = Arrays.asList(messageWrapper); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); + Span subscribeModackRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "modack", messageWrappers, ACK_DEADLINE, true); + Span subscribeAckRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "ack", messageWrappers, 0, false); + Span subscribeNackRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "nack", messageWrappers, 0, false); + + Exception e = new Exception("test-exception"); + tracer.setSubscribeRpcSpanException(subscribeModackRpcSpan, true, ACK_DEADLINE, e); + tracer.setSubscribeRpcSpanException(subscribeAckRpcSpan, false, 0, e); + tracer.setSubscribeRpcSpanException(subscribeNackRpcSpan, true, 0, e); + tracer.endSubscriberSpan(messageWrapper); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(4, allSpans.size()); + SpanData modackSpanData = allSpans.get(0); + SpanData ackSpanData = allSpans.get(1); + SpanData nackSpanData = allSpans.get(2); + SpanData subscriberSpanData = allSpans.get(3); + + StatusData expectedModackStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on modack RPC."); + SpanDataAssert modackSpanDataAssert = OpenTelemetryAssertions.assertThat(modackSpanData); + modackSpanDataAssert + .hasName(SUBSCRIBE_MODACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasStatus(expectedModackStatus) + .hasException(e) + .hasEnded(); + + StatusData expectedAckStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on ack RPC."); + SpanDataAssert ackSpanDataAssert = OpenTelemetryAssertions.assertThat(ackSpanData); + ackSpanDataAssert + .hasName(SUBSCRIBE_ACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasStatus(expectedAckStatus) + .hasException(e) + .hasEnded(); + + StatusData expectedNackStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on nack RPC."); + SpanDataAssert nackSpanDataAssert = OpenTelemetryAssertions.assertThat(nackSpanData); + nackSpanDataAssert + .hasName(SUBSCRIBE_NACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasStatus(expectedNackStatus) + .hasException(e) + .hasEnded(); + } + + private PubsubMessage getPubsubMessage() { + return PubsubMessage.newBuilder() + .setData(ByteString.copyFromUtf8("test-data")) + .setOrderingKey(ORDERING_KEY) + .build(); + } +} From 5ebbbf933b79f7f3c56bba5da2b3c334f544dd4d Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Mon, 30 Sep 2024 20:52:19 +0000 Subject: [PATCH 38/46] chore: generate libraries at Mon Sep 30 20:49:40 UTC 2024 --- .../pubsub/v1/OpenTelemetryPubsubTracer.java | 460 ------------ .../cloud/pubsub/v1/PubsubMessageWrapper.java | 430 ----------- .../cloud/pubsub/v1/OpenTelemetryTest.java | 669 ------------------ 3 files changed, 1559 deletions(-) delete mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java delete mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java delete mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java deleted file mode 100644 index b946f44bf..000000000 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java +++ /dev/null @@ -1,460 +0,0 @@ -/* - * 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.pubsub.v1; - -import com.google.pubsub.v1.PubsubMessage; -import com.google.pubsub.v1.SubscriptionName; -import com.google.pubsub.v1.TopicName; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanBuilder; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.context.Context; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.util.List; - -public class OpenTelemetryPubsubTracer { - private final Tracer tracer; - private boolean enabled = false; - - private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; - private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; - private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = - "subscriber concurrency control"; - private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; - - private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; - private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; - private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; - private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = - "messaging.gcp_pubsub.message.exactly_once_delivery"; - private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = - "messaging.gcp_pubsub.message.delivery_attempt"; - private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; - private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; - private static final String PROJECT_ATTR_KEY = "gcp.project_id"; - private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; - - private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; - - OpenTelemetryPubsubTracer(Tracer tracer, boolean enableOpenTelemetry) { - this.tracer = tracer; - if (this.tracer != null && enableOpenTelemetry) { - this.enabled = true; - } - } - - /** Populates attributes that are common the publisher parent span and publish RPC span. */ - private static final AttributesBuilder createCommonSpanAttributesBuilder( - String destinationName, String projectName, String codeFunction, String operation) { - AttributesBuilder attributesBuilder = - Attributes.builder() - .put(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .put(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName) - .put(PROJECT_ATTR_KEY, projectName) - .put(SemanticAttributes.CODE_FUNCTION, codeFunction); - if (operation != null) { - attributesBuilder.put(SemanticAttributes.MESSAGING_OPERATION, operation); - } - - return attributesBuilder; - } - - private Span startChildSpan(String name, Span parent) { - return tracer.spanBuilder(name).setParent(Context.current().with(parent)).startSpan(); - } - - /** - * Creates and starts the parent span with the appropriate span attributes and injects the span - * context into the {@link PubsubMessage} attributes. - */ - void startPublisherSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - AttributesBuilder attributesBuilder = - createCommonSpanAttributesBuilder( - message.getTopicName(), message.getTopicProject(), "publish", "create"); - - attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()); - if (!message.getOrderingKey().isEmpty()) { - attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); - } - - Span publisherSpan = - tracer - .spanBuilder(message.getTopicName() + " create") - .setSpanKind(SpanKind.PRODUCER) - .setAllAttributes(attributesBuilder.build()) - .startSpan(); - - message.setPublisherSpan(publisherSpan); - if (publisherSpan.getSpanContext().isValid()) { - message.injectSpanContext(); - } - } - - void endPublisherSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endPublisherSpan(); - } - - void setPublisherMessageIdSpanAttribute(PubsubMessageWrapper message, String messageId) { - if (!enabled) { - return; - } - message.setPublisherMessageIdSpanAttribute(messageId); - } - - /** Creates a span for publish-side flow control as a child of the parent publisher span. */ - void startPublishFlowControlSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span publisherSpan = message.getPublisherSpan(); - if (publisherSpan != null) - message.setPublishFlowControlSpan( - startChildSpan(PUBLISH_FLOW_CONTROL_SPAN_NAME, publisherSpan)); - } - - void endPublishFlowControlSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endPublishFlowControlSpan(); - } - - void setPublishFlowControlSpanException(PubsubMessageWrapper message, Throwable t) { - if (!enabled) { - return; - } - message.setPublishFlowControlSpanException(t); - } - - /** Creates a span for publish message batching as a child of the parent publisher span. */ - void startPublishBatchingSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span publisherSpan = message.getPublisherSpan(); - if (publisherSpan != null) { - message.setPublishBatchingSpan(startChildSpan(PUBLISH_BATCHING_SPAN_NAME, publisherSpan)); - } - } - - void endPublishBatchingSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endPublishBatchingSpan(); - } - - /** - * Creates, starts, and returns a publish RPC span for the given message batch. Bi-directional - * links with the publisher parent span are created for sampled messages in the batch. - */ - Span startPublishRpcSpan(String topic, List messages) { - if (!enabled) { - return null; - } - TopicName topicName = TopicName.parse(topic); - Attributes attributes = - createCommonSpanAttributesBuilder( - topicName.getTopic(), topicName.getProject(), "publishCall", "publish") - .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()) - .build(); - SpanBuilder publishRpcSpanBuilder = - tracer - .spanBuilder(topicName.getTopic() + PUBLISH_RPC_SPAN_SUFFIX) - .setSpanKind(SpanKind.CLIENT) - .setAllAttributes(attributes); - Attributes linkAttributes = - Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, "publish").build(); - for (PubsubMessageWrapper message : messages) { - if (message.getPublisherSpan().getSpanContext().isSampled()) - publishRpcSpanBuilder.addLink(message.getPublisherSpan().getSpanContext(), linkAttributes); - } - Span publishRpcSpan = publishRpcSpanBuilder.startSpan(); - - for (PubsubMessageWrapper message : messages) { - if (publishRpcSpan.getSpanContext().isSampled()) { - message.getPublisherSpan().addLink(publishRpcSpan.getSpanContext(), linkAttributes); - message.addPublishStartEvent(); - } - } - return publishRpcSpan; - } - - /** Ends the given publish RPC span if it exists. */ - void endPublishRpcSpan(Span publishRpcSpan) { - if (!enabled) { - return; - } - if (publishRpcSpan != null) { - publishRpcSpan.end(); - } - } - - /** - * Sets an error status and records an exception when an exception is thrown when publishing the - * message batch. - */ - void setPublishRpcSpanException(Span publishRpcSpan, Throwable t) { - if (!enabled) { - return; - } - if (publishRpcSpan != null) { - publishRpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on publish RPC."); - publishRpcSpan.recordException(t); - publishRpcSpan.end(); - } - } - - void startSubscriberSpan(PubsubMessageWrapper message, boolean exactlyOnceDeliveryEnabled) { - if (!enabled) { - return; - } - AttributesBuilder attributesBuilder = - createCommonSpanAttributesBuilder( - message.getSubscriptionName(), message.getSubscriptionProject(), "onResponse", null); - - attributesBuilder - .put(SemanticAttributes.MESSAGING_MESSAGE_ID, message.getMessageId()) - .put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()) - .put(MESSAGE_ACK_ID_ATTR_KEY, message.getAckId()) - .put(MESSAGE_EXACTLY_ONCE_ATTR_KEY, exactlyOnceDeliveryEnabled); - if (!message.getOrderingKey().isEmpty()) { - attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); - } - if (message.getDeliveryAttempt() > 0) { - attributesBuilder.put(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, message.getDeliveryAttempt()); - } - Attributes attributes = attributesBuilder.build(); - Context publisherSpanContext = message.extractSpanContext(attributes); - message.setPublisherSpan(Span.fromContextOrNull(publisherSpanContext)); - message.setSubscriberSpan( - tracer - .spanBuilder(message.getSubscriptionName() + " subscribe") - .setSpanKind(SpanKind.CONSUMER) - .setParent(publisherSpanContext) - .setAllAttributes(attributes) - .startSpan()); - } - - void endSubscriberSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endSubscriberSpan(); - } - - void setSubscriberSpanExpirationResult(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.setSubscriberSpanExpirationResult(); - } - - void setSubscriberSpanException(PubsubMessageWrapper message, Throwable t, String exception) { - if (!enabled) { - return; - } - message.setSubscriberSpanException(t, exception); - } - - /** Creates a span for subscribe concurrency control as a child of the parent subscriber span. */ - void startSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span subscriberSpan = message.getSubscriberSpan(); - if (subscriberSpan != null) { - message.setSubscribeConcurrencyControlSpan( - startChildSpan(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME, subscriberSpan)); - } - } - - void endSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endSubscribeConcurrencyControlSpan(); - } - - void setSubscribeConcurrencyControlSpanException(PubsubMessageWrapper message, Throwable t) { - if (!enabled) { - return; - } - message.setSubscribeConcurrencyControlSpanException(t); - } - - /** - * Creates a span for subscribe ordering key scheduling as a child of the parent subscriber span. - */ - void startSubscribeSchedulerSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span subscriberSpan = message.getSubscriberSpan(); - if (subscriberSpan != null) { - message.setSubscribeSchedulerSpan( - startChildSpan(SUBSCRIBE_SCHEDULER_SPAN_NAME, subscriberSpan)); - } - } - - void endSubscribeSchedulerSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endSubscribeSchedulerSpan(); - } - - /** Creates a span for subscribe message processing as a child of the parent subscriber span. */ - void startSubscribeProcessSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span subscriberSpan = message.getSubscriberSpan(); - if (subscriberSpan != null) { - Span subscribeProcessSpan = - startChildSpan(message.getSubscriptionName() + " process", subscriberSpan); - subscribeProcessSpan.setAttribute( - SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE); - Span publisherSpan = message.getPublisherSpan(); - if (publisherSpan != null) { - subscribeProcessSpan.addLink(publisherSpan.getSpanContext()); - } - message.setSubscribeProcessSpan(subscribeProcessSpan); - } - } - - void endSubscribeProcessSpan(PubsubMessageWrapper message, String action) { - if (!enabled) { - return; - } - message.endSubscribeProcessSpan(action); - } - - /** - * Creates, starts, and returns spans for ModAck, Nack, and Ack RPC requests. Bi-directional links - * to parent subscribe span for sampled messages are added. - */ - Span startSubscribeRpcSpan( - String subscription, - String rpcOperation, - List messages, - int ackDeadline, - boolean isReceiptModack) { - if (!enabled) { - return null; - } - String codeFunction = rpcOperation == "ack" ? "sendAckOperations" : "sendModAckOperations"; - SubscriptionName subscriptionName = SubscriptionName.parse(subscription); - AttributesBuilder attributesBuilder = - createCommonSpanAttributesBuilder( - subscriptionName.getSubscription(), - subscriptionName.getProject(), - codeFunction, - rpcOperation) - .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()); - - // Ack deadline and receipt modack are specific to the modack operation - if (rpcOperation == "modack") { - attributesBuilder - .put(ACK_DEADLINE_ATTR_KEY, ackDeadline) - .put(RECEIPT_MODACK_ATTR_KEY, isReceiptModack); - } - - SpanBuilder rpcSpanBuilder = - tracer - .spanBuilder(subscriptionName.getSubscription() + " " + rpcOperation) - .setSpanKind(SpanKind.CLIENT) - .setAllAttributes(attributesBuilder.build()); - Attributes linkAttributes = - Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, rpcOperation).build(); - for (PubsubMessageWrapper message : messages) { - if (message.getSubscriberSpan().getSpanContext().isSampled()) { - rpcSpanBuilder.addLink(message.getSubscriberSpan().getSpanContext(), linkAttributes); - } - } - Span rpcSpan = rpcSpanBuilder.startSpan(); - - for (PubsubMessageWrapper message : messages) { - if (rpcSpan.getSpanContext().isSampled()) { - message.getSubscriberSpan().addLink(rpcSpan.getSpanContext(), linkAttributes); - switch (rpcOperation) { - case "ack": - message.addAckStartEvent(); - break; - case "modack": - message.addModAckStartEvent(); - break; - case "nack": - message.addNackStartEvent(); - break; - } - } - } - return rpcSpan; - } - - /** Ends the given subscribe RPC span if it exists. */ - void endSubscribeRpcSpan(Span rpcSpan) { - if (!enabled) { - return; - } - if (rpcSpan != null) { - rpcSpan.end(); - } - } - - /** - * Sets an error status and records an exception when an exception is thrown when handling a - * subscribe-side RPC. - */ - void setSubscribeRpcSpanException(Span rpcSpan, boolean isModack, int ackDeadline, Throwable t) { - if (!enabled) { - return; - } - if (rpcSpan != null) { - String operation = !isModack ? "ack" : (ackDeadline == 0 ? "nack" : "modack"); - rpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on " + operation + " RPC."); - rpcSpan.recordException(t); - rpcSpan.end(); - } - } - - /** Adds the appropriate subscribe-side RPC end event. */ - void addEndRpcEvent( - PubsubMessageWrapper message, boolean rpcSampled, boolean isModack, int ackDeadline) { - if (!enabled || !rpcSampled) { - return; - } - if (!isModack) { - message.addAckEndEvent(); - } else if (ackDeadline == 0) { - message.addNackEndEvent(); - } else { - message.addModAckEndEvent(); - } - } -} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java deleted file mode 100644 index 94fd13085..000000000 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java +++ /dev/null @@ -1,430 +0,0 @@ -/* - * 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.pubsub.v1; - -import com.google.pubsub.v1.PubsubMessage; -import com.google.pubsub.v1.SubscriptionName; -import com.google.pubsub.v1.TopicName; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.propagation.TextMapGetter; -import io.opentelemetry.context.propagation.TextMapSetter; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; - -/** - * A wrapper class for a {@link PubsubMessage} object that handles creation and tracking of - * OpenTelemetry {@link Span} objects for different operations that occur during publishing. - */ -public class PubsubMessageWrapper { - private PubsubMessage message; - - private final TopicName topicName; - private final SubscriptionName subscriptionName; - - // Attributes set only for messages received from a streaming pull response. - private final String ackId; - private final int deliveryAttempt; - - private static final String PUBLISH_START_EVENT = "publish start"; - private static final String PUBLISH_END_EVENT = "publish end"; - - private static final String MODACK_START_EVENT = "modack start"; - private static final String MODACK_END_EVENT = "modack end"; - private static final String NACK_START_EVENT = "nack start"; - private static final String NACK_END_EVENT = "nack end"; - private static final String ACK_START_EVENT = "ack start"; - private static final String ACK_END_EVENT = "ack end"; - - private static final String GOOGCLIENT_PREFIX = "googclient_"; - - private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; - - private Span publisherSpan; - private Span publishFlowControlSpan; - private Span publishBatchingSpan; - - private Span subscriberSpan; - private Span subscribeConcurrencyControlSpan; - private Span subscribeSchedulerSpan; - private Span subscribeProcessSpan; - - private PubsubMessageWrapper(Builder builder) { - this.message = builder.message; - this.topicName = builder.topicName; - this.subscriptionName = builder.subscriptionName; - this.ackId = builder.ackId; - this.deliveryAttempt = builder.deliveryAttempt; - } - - static Builder newBuilder(PubsubMessage message, String topicName) { - return new Builder(message, topicName); - } - - static Builder newBuilder( - PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { - return new Builder(message, subscriptionName, ackId, deliveryAttempt); - } - - /** Returns the PubsubMessage associated with this wrapper. */ - PubsubMessage getPubsubMessage() { - return message; - } - - void setPubsubMessage(PubsubMessage message) { - this.message = message; - } - - /** Returns the TopicName for this wrapper as a string. */ - String getTopicName() { - if (topicName != null) { - return topicName.getTopic(); - } - return ""; - } - - String getTopicProject() { - if (topicName != null) { - return topicName.getProject(); - } - return ""; - } - - /** Returns the SubscriptionName for this wrapper as a string. */ - String getSubscriptionName() { - if (subscriptionName != null) { - return subscriptionName.getSubscription(); - } - return ""; - } - - String getSubscriptionProject() { - if (subscriptionName != null) { - return subscriptionName.getProject(); - } - return ""; - } - - String getMessageId() { - return message.getMessageId(); - } - - String getAckId() { - return ackId; - } - - int getDataSize() { - return message.getData().size(); - } - - String getOrderingKey() { - return message.getOrderingKey(); - } - - int getDeliveryAttempt() { - return deliveryAttempt; - } - - Span getPublisherSpan() { - return publisherSpan; - } - - void setPublisherSpan(Span span) { - this.publisherSpan = span; - } - - void setPublishFlowControlSpan(Span span) { - this.publishFlowControlSpan = span; - } - - void setPublishBatchingSpan(Span span) { - this.publishBatchingSpan = span; - } - - Span getSubscriberSpan() { - return subscriberSpan; - } - - void setSubscriberSpan(Span span) { - this.subscriberSpan = span; - } - - void setSubscribeConcurrencyControlSpan(Span span) { - this.subscribeConcurrencyControlSpan = span; - } - - void setSubscribeSchedulerSpan(Span span) { - this.subscribeSchedulerSpan = span; - } - - void setSubscribeProcessSpan(Span span) { - this.subscribeProcessSpan = span; - } - - /** Creates a publish start event that is tied to the publish RPC span time. */ - void addPublishStartEvent() { - if (publisherSpan != null) { - publisherSpan.addEvent(PUBLISH_START_EVENT); - } - } - - /** - * Sets the message ID attribute in the publisher parent span. This is called after the publish - * RPC returns with a message ID. - */ - void setPublisherMessageIdSpanAttribute(String messageId) { - if (publisherSpan != null) { - publisherSpan.setAttribute(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId); - } - } - - /** Ends the publisher parent span if it exists. */ - void endPublisherSpan() { - if (publisherSpan != null) { - publisherSpan.addEvent(PUBLISH_END_EVENT); - publisherSpan.end(); - } - } - - /** Ends the publish flow control span if it exists. */ - void endPublishFlowControlSpan() { - if (publishFlowControlSpan != null) { - publishFlowControlSpan.end(); - } - } - - /** Ends the publish batching span if it exists. */ - void endPublishBatchingSpan() { - if (publishBatchingSpan != null) { - publishBatchingSpan.end(); - } - } - - /** - * Sets an error status and records an exception when an exception is thrown during flow control. - */ - void setPublishFlowControlSpanException(Throwable t) { - if (publishFlowControlSpan != null) { - publishFlowControlSpan.setStatus( - StatusCode.ERROR, "Exception thrown during publish flow control."); - publishFlowControlSpan.recordException(t); - endAllPublishSpans(); - } - } - - /** - * Creates start and end events for ModAcks, Nacks, and Acks that are tied to the corresponding - * RPC span start and end times. - */ - void addModAckStartEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(MODACK_START_EVENT); - } - } - - void addModAckEndEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(MODACK_END_EVENT); - } - } - - void addNackStartEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(NACK_START_EVENT); - } - } - - void addNackEndEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(NACK_END_EVENT); - } - } - - void addAckStartEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(ACK_START_EVENT); - } - } - - void addAckEndEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(ACK_END_EVENT); - } - } - - /** Ends the subscriber parent span if exists. */ - void endSubscriberSpan() { - if (subscriberSpan != null) { - subscriberSpan.end(); - } - } - - /** Ends the subscribe concurreny control span if exists. */ - void endSubscribeConcurrencyControlSpan() { - if (subscribeConcurrencyControlSpan != null) { - subscribeConcurrencyControlSpan.end(); - } - } - - /** Ends the subscribe scheduler span if exists. */ - void endSubscribeSchedulerSpan() { - if (subscribeSchedulerSpan != null) { - subscribeSchedulerSpan.end(); - } - } - - /** - * Ends the subscribe process span if it exists, creates an event with the appropriate result, and - * sets the result on the parent subscriber span. - */ - void endSubscribeProcessSpan(String action) { - if (subscribeProcessSpan != null) { - subscribeProcessSpan.addEvent(action + " called"); - subscribeProcessSpan.end(); - subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, action); - } - } - - /** Sets an exception on the subscriber span during Ack/ModAck/Nack failures */ - void setSubscriberSpanException(Throwable t, String exception) { - if (subscriberSpan != null) { - subscriberSpan.setStatus(StatusCode.ERROR, exception); - subscriberSpan.recordException(t); - endAllSubscribeSpans(); - } - } - - /** Sets result of the parent subscriber span to expired and ends its. */ - void setSubscriberSpanExpirationResult() { - if (subscriberSpan != null) { - subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, "expired"); - endSubscriberSpan(); - } - } - - /** - * Sets an error status and records an exception when an exception is thrown subscriber - * concurrency control. - */ - void setSubscribeConcurrencyControlSpanException(Throwable t) { - if (subscribeConcurrencyControlSpan != null) { - subscribeConcurrencyControlSpan.setStatus( - StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); - subscribeConcurrencyControlSpan.recordException(t); - endAllSubscribeSpans(); - } - } - - /** Ends all publisher-side spans associated with this message wrapper. */ - private void endAllPublishSpans() { - endPublishFlowControlSpan(); - endPublishBatchingSpan(); - endPublisherSpan(); - } - - /** Ends all subscriber-side spans associated with this message wrapper. */ - private void endAllSubscribeSpans() { - endSubscribeConcurrencyControlSpan(); - endSubscribeSchedulerSpan(); - endSubscriberSpan(); - } - - /** - * Injects the span context into the attributes of a Pub/Sub message for propagation to the - * subscriber client. - */ - void injectSpanContext() { - TextMapSetter injectMessageAttributes = - new TextMapSetter() { - @Override - public void set(PubsubMessageWrapper carrier, String key, String value) { - PubsubMessage newMessage = - PubsubMessage.newBuilder(carrier.message) - .putAttributes(GOOGCLIENT_PREFIX + key, value) - .build(); - carrier.message = newMessage; - } - }; - W3CTraceContextPropagator.getInstance() - .inject(Context.current().with(publisherSpan), this, injectMessageAttributes); - } - - /** - * Extracts the span context from the attributes of a Pub/Sub message and creates the parent - * subscriber span using that context. - */ - Context extractSpanContext(Attributes attributes) { - TextMapGetter extractMessageAttributes = - new TextMapGetter() { - @Override - public String get(PubsubMessageWrapper carrier, String key) { - return carrier.message.getAttributesOrDefault(GOOGCLIENT_PREFIX + key, ""); - } - - public Iterable keys(PubsubMessageWrapper carrier) { - return carrier.message.getAttributesMap().keySet(); - } - }; - Context context = - W3CTraceContextPropagator.getInstance() - .extract(Context.current(), this, extractMessageAttributes); - return context; - } - - /** Builder of {@link PubsubMessageWrapper PubsubMessageWrapper}. */ - static final class Builder { - private PubsubMessage message = null; - private TopicName topicName = null; - private SubscriptionName subscriptionName = null; - private String ackId = null; - private int deliveryAttempt = 0; - - public Builder(PubsubMessage message, String topicName) { - this.message = message; - if (topicName != null) { - this.topicName = TopicName.parse(topicName); - } - } - - public Builder( - PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { - this.message = message; - if (subscriptionName != null) { - this.subscriptionName = SubscriptionName.parse(subscriptionName); - } - this.ackId = ackId; - this.deliveryAttempt = deliveryAttempt; - } - - public Builder( - PubsubMessage message, - SubscriptionName subscriptionName, - String ackId, - int deliveryAttempt) { - this.message = message; - this.subscriptionName = subscriptionName; - this.ackId = ackId; - this.deliveryAttempt = deliveryAttempt; - } - - public PubsubMessageWrapper build() { - return new PubsubMessageWrapper(this); - } - } -} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java deleted file mode 100644 index b4433f41e..000000000 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ /dev/null @@ -1,669 +0,0 @@ -/* - * 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.pubsub.v1; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import com.google.protobuf.ByteString; -import com.google.pubsub.v1.PubsubMessage; -import com.google.pubsub.v1.SubscriptionName; -import com.google.pubsub.v1.TopicName; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.sdk.testing.assertj.AttributesAssert; -import io.opentelemetry.sdk.testing.assertj.EventDataAssert; -import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; -import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; -import io.opentelemetry.sdk.testing.junit4.OpenTelemetryRule; -import io.opentelemetry.sdk.trace.data.LinkData; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.data.StatusData; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.util.Arrays; -import java.util.List; -import org.junit.Test; - -public class OpenTelemetryTest { - private static final TopicName FULL_TOPIC_NAME = - TopicName.parse("projects/test-project/topics/test-topic"); - private static final SubscriptionName FULL_SUBSCRIPTION_NAME = - SubscriptionName.parse("projects/test-project/subscriptions/test-sub"); - private static final String PROJECT_NAME = "test-project"; - private static final String ORDERING_KEY = "abc"; - private static final String MESSAGE_ID = "m0"; - private static final String ACK_ID = "def"; - private static final int DELIVERY_ATTEMPT = 1; - private static final int ACK_DEADLINE = 10; - private static final boolean EXACTLY_ONCE_ENABLED = true; - - private static final String PUBLISHER_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " create"; - private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; - private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; - private static final String PUBLISH_RPC_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " publish"; - private static final String PUBLISH_START_EVENT = "publish start"; - private static final String PUBLISH_END_EVENT = "publish end"; - - private static final String SUBSCRIBER_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " subscribe"; - private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = - "subscriber concurrency control"; - private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; - private static final String SUBSCRIBE_PROCESS_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " process"; - private static final String SUBSCRIBE_MODACK_RPC_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " modack"; - private static final String SUBSCRIBE_ACK_RPC_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " ack"; - private static final String SUBSCRIBE_NACK_RPC_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " nack"; - - private static final String PROCESS_ACTION = "ack"; - private static final String MODACK_START_EVENT = "modack start"; - private static final String MODACK_END_EVENT = "modack end"; - private static final String NACK_START_EVENT = "nack start"; - private static final String NACK_END_EVENT = "nack end"; - private static final String ACK_START_EVENT = "ack start"; - private static final String ACK_END_EVENT = "ack end"; - - private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; - private static final String PROJECT_ATTR_KEY = "gcp.project_id"; - private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; - private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; - private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; - private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; - private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; - private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = - "messaging.gcp_pubsub.message.exactly_once_delivery"; - private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; - private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = - "messaging.gcp_pubsub.message.delivery_attempt"; - - private static final String TRACEPARENT_ATTRIBUTE = "googclient_traceparent"; - - private static final OpenTelemetryRule openTelemetryTesting = OpenTelemetryRule.create(); - - @Test - public void testPublishSpansSuccess() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); - List messageWrappers = Arrays.asList(messageWrapper); - - long messageSize = messageWrapper.getPubsubMessage().getData().size(); - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - // Call all span start/end methods in the expected order - tracer.startPublisherSpan(messageWrapper); - tracer.startPublishFlowControlSpan(messageWrapper); - tracer.endPublishFlowControlSpan(messageWrapper); - tracer.startPublishBatchingSpan(messageWrapper); - tracer.endPublishBatchingSpan(messageWrapper); - Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); - tracer.endPublishRpcSpan(publishRpcSpan); - tracer.setPublisherMessageIdSpanAttribute(messageWrapper, MESSAGE_ID); - tracer.endPublisherSpan(messageWrapper); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(4, allSpans.size()); - SpanData flowControlSpanData = allSpans.get(0); - SpanData batchingSpanData = allSpans.get(1); - SpanData publishRpcSpanData = allSpans.get(2); - SpanData publisherSpanData = allSpans.get(3); - - SpanDataAssert flowControlSpanDataAssert = - OpenTelemetryAssertions.assertThat(flowControlSpanData); - flowControlSpanDataAssert - .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) - .hasParent(publisherSpanData) - .hasEnded(); - - SpanDataAssert batchingSpanDataAssert = OpenTelemetryAssertions.assertThat(batchingSpanData); - batchingSpanDataAssert - .hasName(PUBLISH_BATCHING_SPAN_NAME) - .hasParent(publisherSpanData) - .hasEnded(); - - // Check span data, links, and attributes for the publish RPC span - SpanDataAssert publishRpcSpanDataAssert = - OpenTelemetryAssertions.assertThat(publishRpcSpanData); - publishRpcSpanDataAssert - .hasName(PUBLISH_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasEnded(); - - List publishRpcLinks = publishRpcSpanData.getLinks(); - assertEquals(messageWrappers.size(), publishRpcLinks.size()); - assertEquals(publisherSpanData.getSpanContext(), publishRpcLinks.get(0).getSpanContext()); - - assertEquals(6, publishRpcSpanData.getAttributes().size()); - AttributesAssert publishRpcSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(publishRpcSpanData.getAttributes()); - publishRpcSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) - .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "publishCall") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "publish") - .containsEntry(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messageWrappers.size()); - - // Check span data, events, links, and attributes for the publisher create span - SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); - publisherSpanDataAssert - .hasName(PUBLISHER_SPAN_NAME) - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasEnded(); - - assertEquals(2, publisherSpanData.getEvents().size()); - EventDataAssert startEventAssert = - OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(0)); - startEventAssert.hasName(PUBLISH_START_EVENT); - EventDataAssert endEventAssert = - OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(1)); - endEventAssert.hasName(PUBLISH_END_EVENT); - - List publisherLinks = publisherSpanData.getLinks(); - assertEquals(1, publisherLinks.size()); - assertEquals(publishRpcSpanData.getSpanContext(), publisherLinks.get(0).getSpanContext()); - - assertEquals(8, publisherSpanData.getAttributes().size()); - AttributesAssert publisherSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(publisherSpanData.getAttributes()); - publisherSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) - .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "publish") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "create") - .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) - .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) - .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); - - // Check that the message has the attribute containing the trace context. - PubsubMessage message = messageWrapper.getPubsubMessage(); - assertEquals(1, message.getAttributesMap().size()); - assertTrue(message.containsAttributes(TRACEPARENT_ATTRIBUTE)); - assertTrue( - message - .getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "") - .contains(publisherSpanData.getTraceId())); - assertTrue( - message - .getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "") - .contains(publisherSpanData.getSpanId())); - } - - @Test - public void testPublishFlowControlSpanFailure() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startPublisherSpan(messageWrapper); - tracer.startPublishFlowControlSpan(messageWrapper); - - Exception e = new Exception("test-exception"); - tracer.setPublishFlowControlSpanException(messageWrapper, e); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(2, allSpans.size()); - SpanData flowControlSpanData = allSpans.get(0); - SpanData publisherSpanData = allSpans.get(1); - - SpanDataAssert flowControlSpanDataAssert = - OpenTelemetryAssertions.assertThat(flowControlSpanData); - StatusData expectedStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown during publish flow control."); - flowControlSpanDataAssert - .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) - .hasParent(publisherSpanData) - .hasStatus(expectedStatus) - .hasException(e) - .hasEnded(); - - SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); - publisherSpanDataAssert - .hasName(PUBLISHER_SPAN_NAME) - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasEnded(); - } - - @Test - public void testPublishRpcSpanFailure() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); - - List messageWrappers = Arrays.asList(messageWrapper); - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startPublisherSpan(messageWrapper); - Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); - - Exception e = new Exception("test-exception"); - tracer.setPublishRpcSpanException(publishRpcSpan, e); - tracer.endPublisherSpan(messageWrapper); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(2, allSpans.size()); - SpanData rpcSpanData = allSpans.get(0); - SpanData publisherSpanData = allSpans.get(1); - - SpanDataAssert rpcSpanDataAssert = OpenTelemetryAssertions.assertThat(rpcSpanData); - StatusData expectedStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown on publish RPC."); - rpcSpanDataAssert - .hasName(PUBLISH_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasStatus(expectedStatus) - .hasException(e) - .hasEnded(); - - SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); - publisherSpanDataAssert - .hasName(PUBLISHER_SPAN_NAME) - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasEnded(); - } - - @Test - public void testSubscribeSpansSuccess() { - openTelemetryTesting.clearSpans(); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - PubsubMessageWrapper publishMessageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); - // Initialize the Publisher span to inject the context in the message - tracer.startPublisherSpan(publishMessageWrapper); - tracer.endPublisherSpan(publishMessageWrapper); - - PubsubMessage publishedMessage = - publishMessageWrapper.getPubsubMessage().toBuilder().setMessageId(MESSAGE_ID).build(); - PubsubMessageWrapper subscribeMessageWrapper = - PubsubMessageWrapper.newBuilder( - publishedMessage, FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, 1) - .build(); - List subscribeMessageWrappers = Arrays.asList(subscribeMessageWrapper); - - long messageSize = subscribeMessageWrapper.getPubsubMessage().getData().size(); - - // Call all span start/end methods in the expected order - tracer.startSubscriberSpan(subscribeMessageWrapper, EXACTLY_ONCE_ENABLED); - tracer.startSubscribeConcurrencyControlSpan(subscribeMessageWrapper); - tracer.endSubscribeConcurrencyControlSpan(subscribeMessageWrapper); - tracer.startSubscribeSchedulerSpan(subscribeMessageWrapper); - tracer.endSubscribeSchedulerSpan(subscribeMessageWrapper); - tracer.startSubscribeProcessSpan(subscribeMessageWrapper); - tracer.endSubscribeProcessSpan(subscribeMessageWrapper, PROCESS_ACTION); - Span subscribeModackRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), - "modack", - subscribeMessageWrappers, - ACK_DEADLINE, - true); - tracer.endSubscribeRpcSpan(subscribeModackRpcSpan); - tracer.addEndRpcEvent(subscribeMessageWrapper, true, true, ACK_DEADLINE); - Span subscribeAckRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "ack", subscribeMessageWrappers, 0, false); - tracer.endSubscribeRpcSpan(subscribeAckRpcSpan); - tracer.addEndRpcEvent(subscribeMessageWrapper, true, false, 0); - Span subscribeNackRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "nack", subscribeMessageWrappers, 0, false); - tracer.endSubscribeRpcSpan(subscribeNackRpcSpan); - tracer.addEndRpcEvent(subscribeMessageWrapper, true, true, 0); - tracer.endSubscriberSpan(subscribeMessageWrapper); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(8, allSpans.size()); - - SpanData publisherSpanData = allSpans.get(0); - SpanData concurrencyControlSpanData = allSpans.get(1); - SpanData schedulerSpanData = allSpans.get(2); - SpanData processSpanData = allSpans.get(3); - SpanData modackRpcSpanData = allSpans.get(4); - SpanData ackRpcSpanData = allSpans.get(5); - SpanData nackRpcSpanData = allSpans.get(6); - SpanData subscriberSpanData = allSpans.get(7); - - SpanDataAssert concurrencyControlSpanDataAssert = - OpenTelemetryAssertions.assertThat(concurrencyControlSpanData); - concurrencyControlSpanDataAssert - .hasName(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME) - .hasParent(subscriberSpanData) - .hasEnded(); - - SpanDataAssert schedulerSpanDataAssert = OpenTelemetryAssertions.assertThat(schedulerSpanData); - schedulerSpanDataAssert - .hasName(SUBSCRIBE_SCHEDULER_SPAN_NAME) - .hasParent(subscriberSpanData) - .hasEnded(); - - SpanDataAssert processSpanDataAssert = OpenTelemetryAssertions.assertThat(processSpanData); - processSpanDataAssert - .hasName(SUBSCRIBE_PROCESS_SPAN_NAME) - .hasParent(subscriberSpanData) - .hasEnded(); - - assertEquals(1, processSpanData.getEvents().size()); - EventDataAssert actionCalledEventAssert = - OpenTelemetryAssertions.assertThat(processSpanData.getEvents().get(0)); - actionCalledEventAssert.hasName(PROCESS_ACTION + " called"); - - // Check span data, links, and attributes for the modack RPC span - SpanDataAssert modackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(modackRpcSpanData); - modackRpcSpanDataAssert - .hasName(SUBSCRIBE_MODACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasEnded(); - - List modackRpcLinks = modackRpcSpanData.getLinks(); - assertEquals(subscribeMessageWrappers.size(), modackRpcLinks.size()); - assertEquals(subscriberSpanData.getSpanContext(), modackRpcLinks.get(0).getSpanContext()); - - assertEquals(8, modackRpcSpanData.getAttributes().size()); - AttributesAssert modackRpcSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(modackRpcSpanData.getAttributes()); - modackRpcSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry( - SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) - .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "modack") - .containsEntry( - SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()) - .containsEntry(ACK_DEADLINE_ATTR_KEY, 10) - .containsEntry(RECEIPT_MODACK_ATTR_KEY, true); - - // Check span data, links, and attributes for the ack RPC span - SpanDataAssert ackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(ackRpcSpanData); - ackRpcSpanDataAssert - .hasName(SUBSCRIBE_ACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasEnded(); - - List ackRpcLinks = ackRpcSpanData.getLinks(); - assertEquals(subscribeMessageWrappers.size(), ackRpcLinks.size()); - assertEquals(subscriberSpanData.getSpanContext(), ackRpcLinks.get(0).getSpanContext()); - - assertEquals(6, ackRpcSpanData.getAttributes().size()); - AttributesAssert ackRpcSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(ackRpcSpanData.getAttributes()); - ackRpcSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry( - SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) - .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendAckOperations") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "ack") - .containsEntry( - SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); - - // Check span data, links, and attributes for the nack RPC span - SpanDataAssert nackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(nackRpcSpanData); - nackRpcSpanDataAssert - .hasName(SUBSCRIBE_NACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasEnded(); - - List nackRpcLinks = nackRpcSpanData.getLinks(); - assertEquals(subscribeMessageWrappers.size(), nackRpcLinks.size()); - assertEquals(subscriberSpanData.getSpanContext(), nackRpcLinks.get(0).getSpanContext()); - - assertEquals(6, nackRpcSpanData.getAttributes().size()); - AttributesAssert nackRpcSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(nackRpcSpanData.getAttributes()); - nackRpcSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry( - SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) - .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "nack") - .containsEntry( - SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); - - // Check span data, events, links, and attributes for the publisher create span - SpanDataAssert subscriberSpanDataAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData); - subscriberSpanDataAssert - .hasName(SUBSCRIBER_SPAN_NAME) - .hasKind(SpanKind.CONSUMER) - .hasParent(publisherSpanData) - .hasEnded(); - - assertEquals(6, subscriberSpanData.getEvents().size()); - EventDataAssert startModackEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(0)); - startModackEventAssert.hasName(MODACK_START_EVENT); - EventDataAssert endModackEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(1)); - endModackEventAssert.hasName(MODACK_END_EVENT); - EventDataAssert startAckEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(2)); - startAckEventAssert.hasName(ACK_START_EVENT); - EventDataAssert endAckEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(3)); - endAckEventAssert.hasName(ACK_END_EVENT); - EventDataAssert startNackEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(4)); - startNackEventAssert.hasName(NACK_START_EVENT); - EventDataAssert endNackEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(5)); - endNackEventAssert.hasName(NACK_END_EVENT); - - List subscriberLinks = subscriberSpanData.getLinks(); - assertEquals(3, subscriberLinks.size()); - assertEquals(modackRpcSpanData.getSpanContext(), subscriberLinks.get(0).getSpanContext()); - assertEquals(ackRpcSpanData.getSpanContext(), subscriberLinks.get(1).getSpanContext()); - assertEquals(nackRpcSpanData.getSpanContext(), subscriberLinks.get(2).getSpanContext()); - - assertEquals(11, subscriberSpanData.getAttributes().size()); - AttributesAssert subscriberSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getAttributes()); - subscriberSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry( - SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) - .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "onResponse") - .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) - .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) - .containsEntry(MESSAGE_ACK_ID_ATTR_KEY, ACK_ID) - .containsEntry(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, DELIVERY_ATTEMPT) - .containsEntry(MESSAGE_EXACTLY_ONCE_ATTR_KEY, EXACTLY_ONCE_ENABLED) - .containsEntry(MESSAGE_RESULT_ATTR_KEY, PROCESS_ACTION) - .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); - } - - @Test - public void testSubscribeConcurrencyControlSpanFailure() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder( - getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) - .build(); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); - tracer.startSubscribeConcurrencyControlSpan(messageWrapper); - - Exception e = new Exception("test-exception"); - tracer.setSubscribeConcurrencyControlSpanException(messageWrapper, e); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(2, allSpans.size()); - SpanData concurrencyControlSpanData = allSpans.get(0); - SpanData subscriberSpanData = allSpans.get(1); - - SpanDataAssert concurrencyControlSpanDataAssert = - OpenTelemetryAssertions.assertThat(concurrencyControlSpanData); - StatusData expectedStatus = - StatusData.create( - StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); - concurrencyControlSpanDataAssert - .hasName(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME) - .hasParent(subscriberSpanData) - .hasStatus(expectedStatus) - .hasException(e) - .hasEnded(); - - SpanDataAssert subscriberSpanDataAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData); - subscriberSpanDataAssert - .hasName(SUBSCRIBER_SPAN_NAME) - .hasKind(SpanKind.CONSUMER) - .hasNoParent() - .hasEnded(); - } - - @Test - public void testSubscriberSpanFailure() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder( - getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) - .build(); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); - - Exception e = new Exception("test-exception"); - tracer.setSubscriberSpanException(messageWrapper, e, "Test exception"); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(1, allSpans.size()); - SpanData subscriberSpanData = allSpans.get(0); - - StatusData expectedStatus = StatusData.create(StatusCode.ERROR, "Test exception"); - SpanDataAssert subscriberSpanDataAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData); - subscriberSpanDataAssert - .hasName(SUBSCRIBER_SPAN_NAME) - .hasKind(SpanKind.CONSUMER) - .hasNoParent() - .hasStatus(expectedStatus) - .hasException(e) - .hasEnded(); - } - - @Test - public void testSubscribeRpcSpanFailures() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder( - getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) - .build(); - List messageWrappers = Arrays.asList(messageWrapper); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); - Span subscribeModackRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "modack", messageWrappers, ACK_DEADLINE, true); - Span subscribeAckRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "ack", messageWrappers, 0, false); - Span subscribeNackRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "nack", messageWrappers, 0, false); - - Exception e = new Exception("test-exception"); - tracer.setSubscribeRpcSpanException(subscribeModackRpcSpan, true, ACK_DEADLINE, e); - tracer.setSubscribeRpcSpanException(subscribeAckRpcSpan, false, 0, e); - tracer.setSubscribeRpcSpanException(subscribeNackRpcSpan, true, 0, e); - tracer.endSubscriberSpan(messageWrapper); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(4, allSpans.size()); - SpanData modackSpanData = allSpans.get(0); - SpanData ackSpanData = allSpans.get(1); - SpanData nackSpanData = allSpans.get(2); - SpanData subscriberSpanData = allSpans.get(3); - - StatusData expectedModackStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown on modack RPC."); - SpanDataAssert modackSpanDataAssert = OpenTelemetryAssertions.assertThat(modackSpanData); - modackSpanDataAssert - .hasName(SUBSCRIBE_MODACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasStatus(expectedModackStatus) - .hasException(e) - .hasEnded(); - - StatusData expectedAckStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown on ack RPC."); - SpanDataAssert ackSpanDataAssert = OpenTelemetryAssertions.assertThat(ackSpanData); - ackSpanDataAssert - .hasName(SUBSCRIBE_ACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasStatus(expectedAckStatus) - .hasException(e) - .hasEnded(); - - StatusData expectedNackStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown on nack RPC."); - SpanDataAssert nackSpanDataAssert = OpenTelemetryAssertions.assertThat(nackSpanData); - nackSpanDataAssert - .hasName(SUBSCRIBE_NACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasStatus(expectedNackStatus) - .hasException(e) - .hasEnded(); - } - - private PubsubMessage getPubsubMessage() { - return PubsubMessage.newBuilder() - .setData(ByteString.copyFromUtf8("test-data")) - .setOrderingKey(ORDERING_KEY) - .build(); - } -} From 30d1c20855a5a40deb72561816602f70b20d6cbe Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 30 Sep 2024 21:01:12 +0000 Subject: [PATCH 39/46] Revert "chore: generate libraries at Mon Sep 30 20:49:40 UTC 2024" This reverts commit 5ebbbf933b79f7f3c56bba5da2b3c334f544dd4d. --- .../pubsub/v1/OpenTelemetryPubsubTracer.java | 460 ++++++++++++ .../cloud/pubsub/v1/PubsubMessageWrapper.java | 430 +++++++++++ .../cloud/pubsub/v1/OpenTelemetryTest.java | 669 ++++++++++++++++++ 3 files changed, 1559 insertions(+) create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java new file mode 100644 index 000000000..b946f44bf --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java @@ -0,0 +1,460 @@ +/* + * 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.pubsub.v1; + +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.SubscriptionName; +import com.google.pubsub.v1.TopicName; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.List; + +public class OpenTelemetryPubsubTracer { + private final Tracer tracer; + private boolean enabled = false; + + private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; + private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; + private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = + "subscriber concurrency control"; + private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; + + private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; + private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; + private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; + private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = + "messaging.gcp_pubsub.message.exactly_once_delivery"; + private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = + "messaging.gcp_pubsub.message.delivery_attempt"; + private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; + private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; + private static final String PROJECT_ATTR_KEY = "gcp.project_id"; + private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; + + private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; + + OpenTelemetryPubsubTracer(Tracer tracer, boolean enableOpenTelemetry) { + this.tracer = tracer; + if (this.tracer != null && enableOpenTelemetry) { + this.enabled = true; + } + } + + /** Populates attributes that are common the publisher parent span and publish RPC span. */ + private static final AttributesBuilder createCommonSpanAttributesBuilder( + String destinationName, String projectName, String codeFunction, String operation) { + AttributesBuilder attributesBuilder = + Attributes.builder() + .put(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .put(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName) + .put(PROJECT_ATTR_KEY, projectName) + .put(SemanticAttributes.CODE_FUNCTION, codeFunction); + if (operation != null) { + attributesBuilder.put(SemanticAttributes.MESSAGING_OPERATION, operation); + } + + return attributesBuilder; + } + + private Span startChildSpan(String name, Span parent) { + return tracer.spanBuilder(name).setParent(Context.current().with(parent)).startSpan(); + } + + /** + * Creates and starts the parent span with the appropriate span attributes and injects the span + * context into the {@link PubsubMessage} attributes. + */ + void startPublisherSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + AttributesBuilder attributesBuilder = + createCommonSpanAttributesBuilder( + message.getTopicName(), message.getTopicProject(), "publish", "create"); + + attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()); + if (!message.getOrderingKey().isEmpty()) { + attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); + } + + Span publisherSpan = + tracer + .spanBuilder(message.getTopicName() + " create") + .setSpanKind(SpanKind.PRODUCER) + .setAllAttributes(attributesBuilder.build()) + .startSpan(); + + message.setPublisherSpan(publisherSpan); + if (publisherSpan.getSpanContext().isValid()) { + message.injectSpanContext(); + } + } + + void endPublisherSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endPublisherSpan(); + } + + void setPublisherMessageIdSpanAttribute(PubsubMessageWrapper message, String messageId) { + if (!enabled) { + return; + } + message.setPublisherMessageIdSpanAttribute(messageId); + } + + /** Creates a span for publish-side flow control as a child of the parent publisher span. */ + void startPublishFlowControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span publisherSpan = message.getPublisherSpan(); + if (publisherSpan != null) + message.setPublishFlowControlSpan( + startChildSpan(PUBLISH_FLOW_CONTROL_SPAN_NAME, publisherSpan)); + } + + void endPublishFlowControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endPublishFlowControlSpan(); + } + + void setPublishFlowControlSpanException(PubsubMessageWrapper message, Throwable t) { + if (!enabled) { + return; + } + message.setPublishFlowControlSpanException(t); + } + + /** Creates a span for publish message batching as a child of the parent publisher span. */ + void startPublishBatchingSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span publisherSpan = message.getPublisherSpan(); + if (publisherSpan != null) { + message.setPublishBatchingSpan(startChildSpan(PUBLISH_BATCHING_SPAN_NAME, publisherSpan)); + } + } + + void endPublishBatchingSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endPublishBatchingSpan(); + } + + /** + * Creates, starts, and returns a publish RPC span for the given message batch. Bi-directional + * links with the publisher parent span are created for sampled messages in the batch. + */ + Span startPublishRpcSpan(String topic, List messages) { + if (!enabled) { + return null; + } + TopicName topicName = TopicName.parse(topic); + Attributes attributes = + createCommonSpanAttributesBuilder( + topicName.getTopic(), topicName.getProject(), "publishCall", "publish") + .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()) + .build(); + SpanBuilder publishRpcSpanBuilder = + tracer + .spanBuilder(topicName.getTopic() + PUBLISH_RPC_SPAN_SUFFIX) + .setSpanKind(SpanKind.CLIENT) + .setAllAttributes(attributes); + Attributes linkAttributes = + Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, "publish").build(); + for (PubsubMessageWrapper message : messages) { + if (message.getPublisherSpan().getSpanContext().isSampled()) + publishRpcSpanBuilder.addLink(message.getPublisherSpan().getSpanContext(), linkAttributes); + } + Span publishRpcSpan = publishRpcSpanBuilder.startSpan(); + + for (PubsubMessageWrapper message : messages) { + if (publishRpcSpan.getSpanContext().isSampled()) { + message.getPublisherSpan().addLink(publishRpcSpan.getSpanContext(), linkAttributes); + message.addPublishStartEvent(); + } + } + return publishRpcSpan; + } + + /** Ends the given publish RPC span if it exists. */ + void endPublishRpcSpan(Span publishRpcSpan) { + if (!enabled) { + return; + } + if (publishRpcSpan != null) { + publishRpcSpan.end(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown when publishing the + * message batch. + */ + void setPublishRpcSpanException(Span publishRpcSpan, Throwable t) { + if (!enabled) { + return; + } + if (publishRpcSpan != null) { + publishRpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on publish RPC."); + publishRpcSpan.recordException(t); + publishRpcSpan.end(); + } + } + + void startSubscriberSpan(PubsubMessageWrapper message, boolean exactlyOnceDeliveryEnabled) { + if (!enabled) { + return; + } + AttributesBuilder attributesBuilder = + createCommonSpanAttributesBuilder( + message.getSubscriptionName(), message.getSubscriptionProject(), "onResponse", null); + + attributesBuilder + .put(SemanticAttributes.MESSAGING_MESSAGE_ID, message.getMessageId()) + .put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()) + .put(MESSAGE_ACK_ID_ATTR_KEY, message.getAckId()) + .put(MESSAGE_EXACTLY_ONCE_ATTR_KEY, exactlyOnceDeliveryEnabled); + if (!message.getOrderingKey().isEmpty()) { + attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); + } + if (message.getDeliveryAttempt() > 0) { + attributesBuilder.put(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, message.getDeliveryAttempt()); + } + Attributes attributes = attributesBuilder.build(); + Context publisherSpanContext = message.extractSpanContext(attributes); + message.setPublisherSpan(Span.fromContextOrNull(publisherSpanContext)); + message.setSubscriberSpan( + tracer + .spanBuilder(message.getSubscriptionName() + " subscribe") + .setSpanKind(SpanKind.CONSUMER) + .setParent(publisherSpanContext) + .setAllAttributes(attributes) + .startSpan()); + } + + void endSubscriberSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endSubscriberSpan(); + } + + void setSubscriberSpanExpirationResult(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.setSubscriberSpanExpirationResult(); + } + + void setSubscriberSpanException(PubsubMessageWrapper message, Throwable t, String exception) { + if (!enabled) { + return; + } + message.setSubscriberSpanException(t, exception); + } + + /** Creates a span for subscribe concurrency control as a child of the parent subscriber span. */ + void startSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span subscriberSpan = message.getSubscriberSpan(); + if (subscriberSpan != null) { + message.setSubscribeConcurrencyControlSpan( + startChildSpan(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME, subscriberSpan)); + } + } + + void endSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endSubscribeConcurrencyControlSpan(); + } + + void setSubscribeConcurrencyControlSpanException(PubsubMessageWrapper message, Throwable t) { + if (!enabled) { + return; + } + message.setSubscribeConcurrencyControlSpanException(t); + } + + /** + * Creates a span for subscribe ordering key scheduling as a child of the parent subscriber span. + */ + void startSubscribeSchedulerSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span subscriberSpan = message.getSubscriberSpan(); + if (subscriberSpan != null) { + message.setSubscribeSchedulerSpan( + startChildSpan(SUBSCRIBE_SCHEDULER_SPAN_NAME, subscriberSpan)); + } + } + + void endSubscribeSchedulerSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endSubscribeSchedulerSpan(); + } + + /** Creates a span for subscribe message processing as a child of the parent subscriber span. */ + void startSubscribeProcessSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span subscriberSpan = message.getSubscriberSpan(); + if (subscriberSpan != null) { + Span subscribeProcessSpan = + startChildSpan(message.getSubscriptionName() + " process", subscriberSpan); + subscribeProcessSpan.setAttribute( + SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE); + Span publisherSpan = message.getPublisherSpan(); + if (publisherSpan != null) { + subscribeProcessSpan.addLink(publisherSpan.getSpanContext()); + } + message.setSubscribeProcessSpan(subscribeProcessSpan); + } + } + + void endSubscribeProcessSpan(PubsubMessageWrapper message, String action) { + if (!enabled) { + return; + } + message.endSubscribeProcessSpan(action); + } + + /** + * Creates, starts, and returns spans for ModAck, Nack, and Ack RPC requests. Bi-directional links + * to parent subscribe span for sampled messages are added. + */ + Span startSubscribeRpcSpan( + String subscription, + String rpcOperation, + List messages, + int ackDeadline, + boolean isReceiptModack) { + if (!enabled) { + return null; + } + String codeFunction = rpcOperation == "ack" ? "sendAckOperations" : "sendModAckOperations"; + SubscriptionName subscriptionName = SubscriptionName.parse(subscription); + AttributesBuilder attributesBuilder = + createCommonSpanAttributesBuilder( + subscriptionName.getSubscription(), + subscriptionName.getProject(), + codeFunction, + rpcOperation) + .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()); + + // Ack deadline and receipt modack are specific to the modack operation + if (rpcOperation == "modack") { + attributesBuilder + .put(ACK_DEADLINE_ATTR_KEY, ackDeadline) + .put(RECEIPT_MODACK_ATTR_KEY, isReceiptModack); + } + + SpanBuilder rpcSpanBuilder = + tracer + .spanBuilder(subscriptionName.getSubscription() + " " + rpcOperation) + .setSpanKind(SpanKind.CLIENT) + .setAllAttributes(attributesBuilder.build()); + Attributes linkAttributes = + Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, rpcOperation).build(); + for (PubsubMessageWrapper message : messages) { + if (message.getSubscriberSpan().getSpanContext().isSampled()) { + rpcSpanBuilder.addLink(message.getSubscriberSpan().getSpanContext(), linkAttributes); + } + } + Span rpcSpan = rpcSpanBuilder.startSpan(); + + for (PubsubMessageWrapper message : messages) { + if (rpcSpan.getSpanContext().isSampled()) { + message.getSubscriberSpan().addLink(rpcSpan.getSpanContext(), linkAttributes); + switch (rpcOperation) { + case "ack": + message.addAckStartEvent(); + break; + case "modack": + message.addModAckStartEvent(); + break; + case "nack": + message.addNackStartEvent(); + break; + } + } + } + return rpcSpan; + } + + /** Ends the given subscribe RPC span if it exists. */ + void endSubscribeRpcSpan(Span rpcSpan) { + if (!enabled) { + return; + } + if (rpcSpan != null) { + rpcSpan.end(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown when handling a + * subscribe-side RPC. + */ + void setSubscribeRpcSpanException(Span rpcSpan, boolean isModack, int ackDeadline, Throwable t) { + if (!enabled) { + return; + } + if (rpcSpan != null) { + String operation = !isModack ? "ack" : (ackDeadline == 0 ? "nack" : "modack"); + rpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on " + operation + " RPC."); + rpcSpan.recordException(t); + rpcSpan.end(); + } + } + + /** Adds the appropriate subscribe-side RPC end event. */ + void addEndRpcEvent( + PubsubMessageWrapper message, boolean rpcSampled, boolean isModack, int ackDeadline) { + if (!enabled || !rpcSampled) { + return; + } + if (!isModack) { + message.addAckEndEvent(); + } else if (ackDeadline == 0) { + message.addNackEndEvent(); + } else { + message.addModAckEndEvent(); + } + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java new file mode 100644 index 000000000..94fd13085 --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java @@ -0,0 +1,430 @@ +/* + * 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.pubsub.v1; + +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.SubscriptionName; +import com.google.pubsub.v1.TopicName; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.context.propagation.TextMapSetter; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; + +/** + * A wrapper class for a {@link PubsubMessage} object that handles creation and tracking of + * OpenTelemetry {@link Span} objects for different operations that occur during publishing. + */ +public class PubsubMessageWrapper { + private PubsubMessage message; + + private final TopicName topicName; + private final SubscriptionName subscriptionName; + + // Attributes set only for messages received from a streaming pull response. + private final String ackId; + private final int deliveryAttempt; + + private static final String PUBLISH_START_EVENT = "publish start"; + private static final String PUBLISH_END_EVENT = "publish end"; + + private static final String MODACK_START_EVENT = "modack start"; + private static final String MODACK_END_EVENT = "modack end"; + private static final String NACK_START_EVENT = "nack start"; + private static final String NACK_END_EVENT = "nack end"; + private static final String ACK_START_EVENT = "ack start"; + private static final String ACK_END_EVENT = "ack end"; + + private static final String GOOGCLIENT_PREFIX = "googclient_"; + + private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; + + private Span publisherSpan; + private Span publishFlowControlSpan; + private Span publishBatchingSpan; + + private Span subscriberSpan; + private Span subscribeConcurrencyControlSpan; + private Span subscribeSchedulerSpan; + private Span subscribeProcessSpan; + + private PubsubMessageWrapper(Builder builder) { + this.message = builder.message; + this.topicName = builder.topicName; + this.subscriptionName = builder.subscriptionName; + this.ackId = builder.ackId; + this.deliveryAttempt = builder.deliveryAttempt; + } + + static Builder newBuilder(PubsubMessage message, String topicName) { + return new Builder(message, topicName); + } + + static Builder newBuilder( + PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { + return new Builder(message, subscriptionName, ackId, deliveryAttempt); + } + + /** Returns the PubsubMessage associated with this wrapper. */ + PubsubMessage getPubsubMessage() { + return message; + } + + void setPubsubMessage(PubsubMessage message) { + this.message = message; + } + + /** Returns the TopicName for this wrapper as a string. */ + String getTopicName() { + if (topicName != null) { + return topicName.getTopic(); + } + return ""; + } + + String getTopicProject() { + if (topicName != null) { + return topicName.getProject(); + } + return ""; + } + + /** Returns the SubscriptionName for this wrapper as a string. */ + String getSubscriptionName() { + if (subscriptionName != null) { + return subscriptionName.getSubscription(); + } + return ""; + } + + String getSubscriptionProject() { + if (subscriptionName != null) { + return subscriptionName.getProject(); + } + return ""; + } + + String getMessageId() { + return message.getMessageId(); + } + + String getAckId() { + return ackId; + } + + int getDataSize() { + return message.getData().size(); + } + + String getOrderingKey() { + return message.getOrderingKey(); + } + + int getDeliveryAttempt() { + return deliveryAttempt; + } + + Span getPublisherSpan() { + return publisherSpan; + } + + void setPublisherSpan(Span span) { + this.publisherSpan = span; + } + + void setPublishFlowControlSpan(Span span) { + this.publishFlowControlSpan = span; + } + + void setPublishBatchingSpan(Span span) { + this.publishBatchingSpan = span; + } + + Span getSubscriberSpan() { + return subscriberSpan; + } + + void setSubscriberSpan(Span span) { + this.subscriberSpan = span; + } + + void setSubscribeConcurrencyControlSpan(Span span) { + this.subscribeConcurrencyControlSpan = span; + } + + void setSubscribeSchedulerSpan(Span span) { + this.subscribeSchedulerSpan = span; + } + + void setSubscribeProcessSpan(Span span) { + this.subscribeProcessSpan = span; + } + + /** Creates a publish start event that is tied to the publish RPC span time. */ + void addPublishStartEvent() { + if (publisherSpan != null) { + publisherSpan.addEvent(PUBLISH_START_EVENT); + } + } + + /** + * Sets the message ID attribute in the publisher parent span. This is called after the publish + * RPC returns with a message ID. + */ + void setPublisherMessageIdSpanAttribute(String messageId) { + if (publisherSpan != null) { + publisherSpan.setAttribute(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId); + } + } + + /** Ends the publisher parent span if it exists. */ + void endPublisherSpan() { + if (publisherSpan != null) { + publisherSpan.addEvent(PUBLISH_END_EVENT); + publisherSpan.end(); + } + } + + /** Ends the publish flow control span if it exists. */ + void endPublishFlowControlSpan() { + if (publishFlowControlSpan != null) { + publishFlowControlSpan.end(); + } + } + + /** Ends the publish batching span if it exists. */ + void endPublishBatchingSpan() { + if (publishBatchingSpan != null) { + publishBatchingSpan.end(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown during flow control. + */ + void setPublishFlowControlSpanException(Throwable t) { + if (publishFlowControlSpan != null) { + publishFlowControlSpan.setStatus( + StatusCode.ERROR, "Exception thrown during publish flow control."); + publishFlowControlSpan.recordException(t); + endAllPublishSpans(); + } + } + + /** + * Creates start and end events for ModAcks, Nacks, and Acks that are tied to the corresponding + * RPC span start and end times. + */ + void addModAckStartEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(MODACK_START_EVENT); + } + } + + void addModAckEndEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(MODACK_END_EVENT); + } + } + + void addNackStartEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(NACK_START_EVENT); + } + } + + void addNackEndEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(NACK_END_EVENT); + } + } + + void addAckStartEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(ACK_START_EVENT); + } + } + + void addAckEndEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(ACK_END_EVENT); + } + } + + /** Ends the subscriber parent span if exists. */ + void endSubscriberSpan() { + if (subscriberSpan != null) { + subscriberSpan.end(); + } + } + + /** Ends the subscribe concurreny control span if exists. */ + void endSubscribeConcurrencyControlSpan() { + if (subscribeConcurrencyControlSpan != null) { + subscribeConcurrencyControlSpan.end(); + } + } + + /** Ends the subscribe scheduler span if exists. */ + void endSubscribeSchedulerSpan() { + if (subscribeSchedulerSpan != null) { + subscribeSchedulerSpan.end(); + } + } + + /** + * Ends the subscribe process span if it exists, creates an event with the appropriate result, and + * sets the result on the parent subscriber span. + */ + void endSubscribeProcessSpan(String action) { + if (subscribeProcessSpan != null) { + subscribeProcessSpan.addEvent(action + " called"); + subscribeProcessSpan.end(); + subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, action); + } + } + + /** Sets an exception on the subscriber span during Ack/ModAck/Nack failures */ + void setSubscriberSpanException(Throwable t, String exception) { + if (subscriberSpan != null) { + subscriberSpan.setStatus(StatusCode.ERROR, exception); + subscriberSpan.recordException(t); + endAllSubscribeSpans(); + } + } + + /** Sets result of the parent subscriber span to expired and ends its. */ + void setSubscriberSpanExpirationResult() { + if (subscriberSpan != null) { + subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, "expired"); + endSubscriberSpan(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown subscriber + * concurrency control. + */ + void setSubscribeConcurrencyControlSpanException(Throwable t) { + if (subscribeConcurrencyControlSpan != null) { + subscribeConcurrencyControlSpan.setStatus( + StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); + subscribeConcurrencyControlSpan.recordException(t); + endAllSubscribeSpans(); + } + } + + /** Ends all publisher-side spans associated with this message wrapper. */ + private void endAllPublishSpans() { + endPublishFlowControlSpan(); + endPublishBatchingSpan(); + endPublisherSpan(); + } + + /** Ends all subscriber-side spans associated with this message wrapper. */ + private void endAllSubscribeSpans() { + endSubscribeConcurrencyControlSpan(); + endSubscribeSchedulerSpan(); + endSubscriberSpan(); + } + + /** + * Injects the span context into the attributes of a Pub/Sub message for propagation to the + * subscriber client. + */ + void injectSpanContext() { + TextMapSetter injectMessageAttributes = + new TextMapSetter() { + @Override + public void set(PubsubMessageWrapper carrier, String key, String value) { + PubsubMessage newMessage = + PubsubMessage.newBuilder(carrier.message) + .putAttributes(GOOGCLIENT_PREFIX + key, value) + .build(); + carrier.message = newMessage; + } + }; + W3CTraceContextPropagator.getInstance() + .inject(Context.current().with(publisherSpan), this, injectMessageAttributes); + } + + /** + * Extracts the span context from the attributes of a Pub/Sub message and creates the parent + * subscriber span using that context. + */ + Context extractSpanContext(Attributes attributes) { + TextMapGetter extractMessageAttributes = + new TextMapGetter() { + @Override + public String get(PubsubMessageWrapper carrier, String key) { + return carrier.message.getAttributesOrDefault(GOOGCLIENT_PREFIX + key, ""); + } + + public Iterable keys(PubsubMessageWrapper carrier) { + return carrier.message.getAttributesMap().keySet(); + } + }; + Context context = + W3CTraceContextPropagator.getInstance() + .extract(Context.current(), this, extractMessageAttributes); + return context; + } + + /** Builder of {@link PubsubMessageWrapper PubsubMessageWrapper}. */ + static final class Builder { + private PubsubMessage message = null; + private TopicName topicName = null; + private SubscriptionName subscriptionName = null; + private String ackId = null; + private int deliveryAttempt = 0; + + public Builder(PubsubMessage message, String topicName) { + this.message = message; + if (topicName != null) { + this.topicName = TopicName.parse(topicName); + } + } + + public Builder( + PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { + this.message = message; + if (subscriptionName != null) { + this.subscriptionName = SubscriptionName.parse(subscriptionName); + } + this.ackId = ackId; + this.deliveryAttempt = deliveryAttempt; + } + + public Builder( + PubsubMessage message, + SubscriptionName subscriptionName, + String ackId, + int deliveryAttempt) { + this.message = message; + this.subscriptionName = subscriptionName; + this.ackId = ackId; + this.deliveryAttempt = deliveryAttempt; + } + + public PubsubMessageWrapper build() { + return new PubsubMessageWrapper(this); + } + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java new file mode 100644 index 000000000..b4433f41e --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -0,0 +1,669 @@ +/* + * 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.pubsub.v1; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.google.protobuf.ByteString; +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.SubscriptionName; +import com.google.pubsub.v1.TopicName; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.sdk.testing.assertj.AttributesAssert; +import io.opentelemetry.sdk.testing.assertj.EventDataAssert; +import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.testing.junit4.OpenTelemetryRule; +import io.opentelemetry.sdk.trace.data.LinkData; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.data.StatusData; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.Arrays; +import java.util.List; +import org.junit.Test; + +public class OpenTelemetryTest { + private static final TopicName FULL_TOPIC_NAME = + TopicName.parse("projects/test-project/topics/test-topic"); + private static final SubscriptionName FULL_SUBSCRIPTION_NAME = + SubscriptionName.parse("projects/test-project/subscriptions/test-sub"); + private static final String PROJECT_NAME = "test-project"; + private static final String ORDERING_KEY = "abc"; + private static final String MESSAGE_ID = "m0"; + private static final String ACK_ID = "def"; + private static final int DELIVERY_ATTEMPT = 1; + private static final int ACK_DEADLINE = 10; + private static final boolean EXACTLY_ONCE_ENABLED = true; + + private static final String PUBLISHER_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " create"; + private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; + private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; + private static final String PUBLISH_RPC_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " publish"; + private static final String PUBLISH_START_EVENT = "publish start"; + private static final String PUBLISH_END_EVENT = "publish end"; + + private static final String SUBSCRIBER_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " subscribe"; + private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = + "subscriber concurrency control"; + private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; + private static final String SUBSCRIBE_PROCESS_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " process"; + private static final String SUBSCRIBE_MODACK_RPC_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " modack"; + private static final String SUBSCRIBE_ACK_RPC_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " ack"; + private static final String SUBSCRIBE_NACK_RPC_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " nack"; + + private static final String PROCESS_ACTION = "ack"; + private static final String MODACK_START_EVENT = "modack start"; + private static final String MODACK_END_EVENT = "modack end"; + private static final String NACK_START_EVENT = "nack start"; + private static final String NACK_END_EVENT = "nack end"; + private static final String ACK_START_EVENT = "ack start"; + private static final String ACK_END_EVENT = "ack end"; + + private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; + private static final String PROJECT_ATTR_KEY = "gcp.project_id"; + private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; + private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; + private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; + private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; + private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; + private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = + "messaging.gcp_pubsub.message.exactly_once_delivery"; + private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; + private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = + "messaging.gcp_pubsub.message.delivery_attempt"; + + private static final String TRACEPARENT_ATTRIBUTE = "googclient_traceparent"; + + private static final OpenTelemetryRule openTelemetryTesting = OpenTelemetryRule.create(); + + @Test + public void testPublishSpansSuccess() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); + List messageWrappers = Arrays.asList(messageWrapper); + + long messageSize = messageWrapper.getPubsubMessage().getData().size(); + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + // Call all span start/end methods in the expected order + tracer.startPublisherSpan(messageWrapper); + tracer.startPublishFlowControlSpan(messageWrapper); + tracer.endPublishFlowControlSpan(messageWrapper); + tracer.startPublishBatchingSpan(messageWrapper); + tracer.endPublishBatchingSpan(messageWrapper); + Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); + tracer.endPublishRpcSpan(publishRpcSpan); + tracer.setPublisherMessageIdSpanAttribute(messageWrapper, MESSAGE_ID); + tracer.endPublisherSpan(messageWrapper); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(4, allSpans.size()); + SpanData flowControlSpanData = allSpans.get(0); + SpanData batchingSpanData = allSpans.get(1); + SpanData publishRpcSpanData = allSpans.get(2); + SpanData publisherSpanData = allSpans.get(3); + + SpanDataAssert flowControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(flowControlSpanData); + flowControlSpanDataAssert + .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) + .hasParent(publisherSpanData) + .hasEnded(); + + SpanDataAssert batchingSpanDataAssert = OpenTelemetryAssertions.assertThat(batchingSpanData); + batchingSpanDataAssert + .hasName(PUBLISH_BATCHING_SPAN_NAME) + .hasParent(publisherSpanData) + .hasEnded(); + + // Check span data, links, and attributes for the publish RPC span + SpanDataAssert publishRpcSpanDataAssert = + OpenTelemetryAssertions.assertThat(publishRpcSpanData); + publishRpcSpanDataAssert + .hasName(PUBLISH_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List publishRpcLinks = publishRpcSpanData.getLinks(); + assertEquals(messageWrappers.size(), publishRpcLinks.size()); + assertEquals(publisherSpanData.getSpanContext(), publishRpcLinks.get(0).getSpanContext()); + + assertEquals(6, publishRpcSpanData.getAttributes().size()); + AttributesAssert publishRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(publishRpcSpanData.getAttributes()); + publishRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "publishCall") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "publish") + .containsEntry(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messageWrappers.size()); + + // Check span data, events, links, and attributes for the publisher create span + SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publisherSpanDataAssert + .hasName(PUBLISHER_SPAN_NAME) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasEnded(); + + assertEquals(2, publisherSpanData.getEvents().size()); + EventDataAssert startEventAssert = + OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(0)); + startEventAssert.hasName(PUBLISH_START_EVENT); + EventDataAssert endEventAssert = + OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(1)); + endEventAssert.hasName(PUBLISH_END_EVENT); + + List publisherLinks = publisherSpanData.getLinks(); + assertEquals(1, publisherLinks.size()); + assertEquals(publishRpcSpanData.getSpanContext(), publisherLinks.get(0).getSpanContext()); + + assertEquals(8, publisherSpanData.getAttributes().size()); + AttributesAssert publisherSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(publisherSpanData.getAttributes()); + publisherSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) + .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "publish") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "create") + .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) + .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) + .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); + + // Check that the message has the attribute containing the trace context. + PubsubMessage message = messageWrapper.getPubsubMessage(); + assertEquals(1, message.getAttributesMap().size()); + assertTrue(message.containsAttributes(TRACEPARENT_ATTRIBUTE)); + assertTrue( + message + .getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "") + .contains(publisherSpanData.getTraceId())); + assertTrue( + message + .getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "") + .contains(publisherSpanData.getSpanId())); + } + + @Test + public void testPublishFlowControlSpanFailure() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startPublisherSpan(messageWrapper); + tracer.startPublishFlowControlSpan(messageWrapper); + + Exception e = new Exception("test-exception"); + tracer.setPublishFlowControlSpanException(messageWrapper, e); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(2, allSpans.size()); + SpanData flowControlSpanData = allSpans.get(0); + SpanData publisherSpanData = allSpans.get(1); + + SpanDataAssert flowControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(flowControlSpanData); + StatusData expectedStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown during publish flow control."); + flowControlSpanDataAssert + .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) + .hasParent(publisherSpanData) + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + + SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publisherSpanDataAssert + .hasName(PUBLISHER_SPAN_NAME) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasEnded(); + } + + @Test + public void testPublishRpcSpanFailure() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); + + List messageWrappers = Arrays.asList(messageWrapper); + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startPublisherSpan(messageWrapper); + Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); + + Exception e = new Exception("test-exception"); + tracer.setPublishRpcSpanException(publishRpcSpan, e); + tracer.endPublisherSpan(messageWrapper); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(2, allSpans.size()); + SpanData rpcSpanData = allSpans.get(0); + SpanData publisherSpanData = allSpans.get(1); + + SpanDataAssert rpcSpanDataAssert = OpenTelemetryAssertions.assertThat(rpcSpanData); + StatusData expectedStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on publish RPC."); + rpcSpanDataAssert + .hasName(PUBLISH_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + + SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publisherSpanDataAssert + .hasName(PUBLISHER_SPAN_NAME) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasEnded(); + } + + @Test + public void testSubscribeSpansSuccess() { + openTelemetryTesting.clearSpans(); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + PubsubMessageWrapper publishMessageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); + // Initialize the Publisher span to inject the context in the message + tracer.startPublisherSpan(publishMessageWrapper); + tracer.endPublisherSpan(publishMessageWrapper); + + PubsubMessage publishedMessage = + publishMessageWrapper.getPubsubMessage().toBuilder().setMessageId(MESSAGE_ID).build(); + PubsubMessageWrapper subscribeMessageWrapper = + PubsubMessageWrapper.newBuilder( + publishedMessage, FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, 1) + .build(); + List subscribeMessageWrappers = Arrays.asList(subscribeMessageWrapper); + + long messageSize = subscribeMessageWrapper.getPubsubMessage().getData().size(); + + // Call all span start/end methods in the expected order + tracer.startSubscriberSpan(subscribeMessageWrapper, EXACTLY_ONCE_ENABLED); + tracer.startSubscribeConcurrencyControlSpan(subscribeMessageWrapper); + tracer.endSubscribeConcurrencyControlSpan(subscribeMessageWrapper); + tracer.startSubscribeSchedulerSpan(subscribeMessageWrapper); + tracer.endSubscribeSchedulerSpan(subscribeMessageWrapper); + tracer.startSubscribeProcessSpan(subscribeMessageWrapper); + tracer.endSubscribeProcessSpan(subscribeMessageWrapper, PROCESS_ACTION); + Span subscribeModackRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), + "modack", + subscribeMessageWrappers, + ACK_DEADLINE, + true); + tracer.endSubscribeRpcSpan(subscribeModackRpcSpan); + tracer.addEndRpcEvent(subscribeMessageWrapper, true, true, ACK_DEADLINE); + Span subscribeAckRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "ack", subscribeMessageWrappers, 0, false); + tracer.endSubscribeRpcSpan(subscribeAckRpcSpan); + tracer.addEndRpcEvent(subscribeMessageWrapper, true, false, 0); + Span subscribeNackRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "nack", subscribeMessageWrappers, 0, false); + tracer.endSubscribeRpcSpan(subscribeNackRpcSpan); + tracer.addEndRpcEvent(subscribeMessageWrapper, true, true, 0); + tracer.endSubscriberSpan(subscribeMessageWrapper); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(8, allSpans.size()); + + SpanData publisherSpanData = allSpans.get(0); + SpanData concurrencyControlSpanData = allSpans.get(1); + SpanData schedulerSpanData = allSpans.get(2); + SpanData processSpanData = allSpans.get(3); + SpanData modackRpcSpanData = allSpans.get(4); + SpanData ackRpcSpanData = allSpans.get(5); + SpanData nackRpcSpanData = allSpans.get(6); + SpanData subscriberSpanData = allSpans.get(7); + + SpanDataAssert concurrencyControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(concurrencyControlSpanData); + concurrencyControlSpanDataAssert + .hasName(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasEnded(); + + SpanDataAssert schedulerSpanDataAssert = OpenTelemetryAssertions.assertThat(schedulerSpanData); + schedulerSpanDataAssert + .hasName(SUBSCRIBE_SCHEDULER_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasEnded(); + + SpanDataAssert processSpanDataAssert = OpenTelemetryAssertions.assertThat(processSpanData); + processSpanDataAssert + .hasName(SUBSCRIBE_PROCESS_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasEnded(); + + assertEquals(1, processSpanData.getEvents().size()); + EventDataAssert actionCalledEventAssert = + OpenTelemetryAssertions.assertThat(processSpanData.getEvents().get(0)); + actionCalledEventAssert.hasName(PROCESS_ACTION + " called"); + + // Check span data, links, and attributes for the modack RPC span + SpanDataAssert modackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(modackRpcSpanData); + modackRpcSpanDataAssert + .hasName(SUBSCRIBE_MODACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List modackRpcLinks = modackRpcSpanData.getLinks(); + assertEquals(subscribeMessageWrappers.size(), modackRpcLinks.size()); + assertEquals(subscriberSpanData.getSpanContext(), modackRpcLinks.get(0).getSpanContext()); + + assertEquals(8, modackRpcSpanData.getAttributes().size()); + AttributesAssert modackRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(modackRpcSpanData.getAttributes()); + modackRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "modack") + .containsEntry( + SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()) + .containsEntry(ACK_DEADLINE_ATTR_KEY, 10) + .containsEntry(RECEIPT_MODACK_ATTR_KEY, true); + + // Check span data, links, and attributes for the ack RPC span + SpanDataAssert ackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(ackRpcSpanData); + ackRpcSpanDataAssert + .hasName(SUBSCRIBE_ACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List ackRpcLinks = ackRpcSpanData.getLinks(); + assertEquals(subscribeMessageWrappers.size(), ackRpcLinks.size()); + assertEquals(subscriberSpanData.getSpanContext(), ackRpcLinks.get(0).getSpanContext()); + + assertEquals(6, ackRpcSpanData.getAttributes().size()); + AttributesAssert ackRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(ackRpcSpanData.getAttributes()); + ackRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendAckOperations") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "ack") + .containsEntry( + SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); + + // Check span data, links, and attributes for the nack RPC span + SpanDataAssert nackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(nackRpcSpanData); + nackRpcSpanDataAssert + .hasName(SUBSCRIBE_NACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List nackRpcLinks = nackRpcSpanData.getLinks(); + assertEquals(subscribeMessageWrappers.size(), nackRpcLinks.size()); + assertEquals(subscriberSpanData.getSpanContext(), nackRpcLinks.get(0).getSpanContext()); + + assertEquals(6, nackRpcSpanData.getAttributes().size()); + AttributesAssert nackRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(nackRpcSpanData.getAttributes()); + nackRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "nack") + .containsEntry( + SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); + + // Check span data, events, links, and attributes for the publisher create span + SpanDataAssert subscriberSpanDataAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData); + subscriberSpanDataAssert + .hasName(SUBSCRIBER_SPAN_NAME) + .hasKind(SpanKind.CONSUMER) + .hasParent(publisherSpanData) + .hasEnded(); + + assertEquals(6, subscriberSpanData.getEvents().size()); + EventDataAssert startModackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(0)); + startModackEventAssert.hasName(MODACK_START_EVENT); + EventDataAssert endModackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(1)); + endModackEventAssert.hasName(MODACK_END_EVENT); + EventDataAssert startAckEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(2)); + startAckEventAssert.hasName(ACK_START_EVENT); + EventDataAssert endAckEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(3)); + endAckEventAssert.hasName(ACK_END_EVENT); + EventDataAssert startNackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(4)); + startNackEventAssert.hasName(NACK_START_EVENT); + EventDataAssert endNackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(5)); + endNackEventAssert.hasName(NACK_END_EVENT); + + List subscriberLinks = subscriberSpanData.getLinks(); + assertEquals(3, subscriberLinks.size()); + assertEquals(modackRpcSpanData.getSpanContext(), subscriberLinks.get(0).getSpanContext()); + assertEquals(ackRpcSpanData.getSpanContext(), subscriberLinks.get(1).getSpanContext()); + assertEquals(nackRpcSpanData.getSpanContext(), subscriberLinks.get(2).getSpanContext()); + + assertEquals(11, subscriberSpanData.getAttributes().size()); + AttributesAssert subscriberSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getAttributes()); + subscriberSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "onResponse") + .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) + .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) + .containsEntry(MESSAGE_ACK_ID_ATTR_KEY, ACK_ID) + .containsEntry(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, DELIVERY_ATTEMPT) + .containsEntry(MESSAGE_EXACTLY_ONCE_ATTR_KEY, EXACTLY_ONCE_ENABLED) + .containsEntry(MESSAGE_RESULT_ATTR_KEY, PROCESS_ACTION) + .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); + } + + @Test + public void testSubscribeConcurrencyControlSpanFailure() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder( + getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) + .build(); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); + tracer.startSubscribeConcurrencyControlSpan(messageWrapper); + + Exception e = new Exception("test-exception"); + tracer.setSubscribeConcurrencyControlSpanException(messageWrapper, e); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(2, allSpans.size()); + SpanData concurrencyControlSpanData = allSpans.get(0); + SpanData subscriberSpanData = allSpans.get(1); + + SpanDataAssert concurrencyControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(concurrencyControlSpanData); + StatusData expectedStatus = + StatusData.create( + StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); + concurrencyControlSpanDataAssert + .hasName(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + + SpanDataAssert subscriberSpanDataAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData); + subscriberSpanDataAssert + .hasName(SUBSCRIBER_SPAN_NAME) + .hasKind(SpanKind.CONSUMER) + .hasNoParent() + .hasEnded(); + } + + @Test + public void testSubscriberSpanFailure() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder( + getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) + .build(); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); + + Exception e = new Exception("test-exception"); + tracer.setSubscriberSpanException(messageWrapper, e, "Test exception"); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(1, allSpans.size()); + SpanData subscriberSpanData = allSpans.get(0); + + StatusData expectedStatus = StatusData.create(StatusCode.ERROR, "Test exception"); + SpanDataAssert subscriberSpanDataAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData); + subscriberSpanDataAssert + .hasName(SUBSCRIBER_SPAN_NAME) + .hasKind(SpanKind.CONSUMER) + .hasNoParent() + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + } + + @Test + public void testSubscribeRpcSpanFailures() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder( + getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) + .build(); + List messageWrappers = Arrays.asList(messageWrapper); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); + Span subscribeModackRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "modack", messageWrappers, ACK_DEADLINE, true); + Span subscribeAckRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "ack", messageWrappers, 0, false); + Span subscribeNackRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "nack", messageWrappers, 0, false); + + Exception e = new Exception("test-exception"); + tracer.setSubscribeRpcSpanException(subscribeModackRpcSpan, true, ACK_DEADLINE, e); + tracer.setSubscribeRpcSpanException(subscribeAckRpcSpan, false, 0, e); + tracer.setSubscribeRpcSpanException(subscribeNackRpcSpan, true, 0, e); + tracer.endSubscriberSpan(messageWrapper); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(4, allSpans.size()); + SpanData modackSpanData = allSpans.get(0); + SpanData ackSpanData = allSpans.get(1); + SpanData nackSpanData = allSpans.get(2); + SpanData subscriberSpanData = allSpans.get(3); + + StatusData expectedModackStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on modack RPC."); + SpanDataAssert modackSpanDataAssert = OpenTelemetryAssertions.assertThat(modackSpanData); + modackSpanDataAssert + .hasName(SUBSCRIBE_MODACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasStatus(expectedModackStatus) + .hasException(e) + .hasEnded(); + + StatusData expectedAckStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on ack RPC."); + SpanDataAssert ackSpanDataAssert = OpenTelemetryAssertions.assertThat(ackSpanData); + ackSpanDataAssert + .hasName(SUBSCRIBE_ACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasStatus(expectedAckStatus) + .hasException(e) + .hasEnded(); + + StatusData expectedNackStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on nack RPC."); + SpanDataAssert nackSpanDataAssert = OpenTelemetryAssertions.assertThat(nackSpanData); + nackSpanDataAssert + .hasName(SUBSCRIBE_NACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasStatus(expectedNackStatus) + .hasException(e) + .hasEnded(); + } + + private PubsubMessage getPubsubMessage() { + return PubsubMessage.newBuilder() + .setData(ByteString.copyFromUtf8("test-data")) + .setOrderingKey(ORDERING_KEY) + .build(); + } +} From 23f3a70d64f0f72cf18dd3a7640125ff9027dec7 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Mon, 30 Sep 2024 21:06:05 +0000 Subject: [PATCH 40/46] chore: generate libraries at Mon Sep 30 21:03:31 UTC 2024 --- .../pubsub/v1/OpenTelemetryPubsubTracer.java | 460 ------------ .../cloud/pubsub/v1/PubsubMessageWrapper.java | 430 ----------- .../cloud/pubsub/v1/OpenTelemetryTest.java | 669 ------------------ 3 files changed, 1559 deletions(-) delete mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java delete mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java delete mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java deleted file mode 100644 index b946f44bf..000000000 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java +++ /dev/null @@ -1,460 +0,0 @@ -/* - * 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.pubsub.v1; - -import com.google.pubsub.v1.PubsubMessage; -import com.google.pubsub.v1.SubscriptionName; -import com.google.pubsub.v1.TopicName; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanBuilder; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.context.Context; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.util.List; - -public class OpenTelemetryPubsubTracer { - private final Tracer tracer; - private boolean enabled = false; - - private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; - private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; - private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = - "subscriber concurrency control"; - private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; - - private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; - private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; - private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; - private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = - "messaging.gcp_pubsub.message.exactly_once_delivery"; - private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = - "messaging.gcp_pubsub.message.delivery_attempt"; - private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; - private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; - private static final String PROJECT_ATTR_KEY = "gcp.project_id"; - private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; - - private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; - - OpenTelemetryPubsubTracer(Tracer tracer, boolean enableOpenTelemetry) { - this.tracer = tracer; - if (this.tracer != null && enableOpenTelemetry) { - this.enabled = true; - } - } - - /** Populates attributes that are common the publisher parent span and publish RPC span. */ - private static final AttributesBuilder createCommonSpanAttributesBuilder( - String destinationName, String projectName, String codeFunction, String operation) { - AttributesBuilder attributesBuilder = - Attributes.builder() - .put(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .put(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName) - .put(PROJECT_ATTR_KEY, projectName) - .put(SemanticAttributes.CODE_FUNCTION, codeFunction); - if (operation != null) { - attributesBuilder.put(SemanticAttributes.MESSAGING_OPERATION, operation); - } - - return attributesBuilder; - } - - private Span startChildSpan(String name, Span parent) { - return tracer.spanBuilder(name).setParent(Context.current().with(parent)).startSpan(); - } - - /** - * Creates and starts the parent span with the appropriate span attributes and injects the span - * context into the {@link PubsubMessage} attributes. - */ - void startPublisherSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - AttributesBuilder attributesBuilder = - createCommonSpanAttributesBuilder( - message.getTopicName(), message.getTopicProject(), "publish", "create"); - - attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()); - if (!message.getOrderingKey().isEmpty()) { - attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); - } - - Span publisherSpan = - tracer - .spanBuilder(message.getTopicName() + " create") - .setSpanKind(SpanKind.PRODUCER) - .setAllAttributes(attributesBuilder.build()) - .startSpan(); - - message.setPublisherSpan(publisherSpan); - if (publisherSpan.getSpanContext().isValid()) { - message.injectSpanContext(); - } - } - - void endPublisherSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endPublisherSpan(); - } - - void setPublisherMessageIdSpanAttribute(PubsubMessageWrapper message, String messageId) { - if (!enabled) { - return; - } - message.setPublisherMessageIdSpanAttribute(messageId); - } - - /** Creates a span for publish-side flow control as a child of the parent publisher span. */ - void startPublishFlowControlSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span publisherSpan = message.getPublisherSpan(); - if (publisherSpan != null) - message.setPublishFlowControlSpan( - startChildSpan(PUBLISH_FLOW_CONTROL_SPAN_NAME, publisherSpan)); - } - - void endPublishFlowControlSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endPublishFlowControlSpan(); - } - - void setPublishFlowControlSpanException(PubsubMessageWrapper message, Throwable t) { - if (!enabled) { - return; - } - message.setPublishFlowControlSpanException(t); - } - - /** Creates a span for publish message batching as a child of the parent publisher span. */ - void startPublishBatchingSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span publisherSpan = message.getPublisherSpan(); - if (publisherSpan != null) { - message.setPublishBatchingSpan(startChildSpan(PUBLISH_BATCHING_SPAN_NAME, publisherSpan)); - } - } - - void endPublishBatchingSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endPublishBatchingSpan(); - } - - /** - * Creates, starts, and returns a publish RPC span for the given message batch. Bi-directional - * links with the publisher parent span are created for sampled messages in the batch. - */ - Span startPublishRpcSpan(String topic, List messages) { - if (!enabled) { - return null; - } - TopicName topicName = TopicName.parse(topic); - Attributes attributes = - createCommonSpanAttributesBuilder( - topicName.getTopic(), topicName.getProject(), "publishCall", "publish") - .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()) - .build(); - SpanBuilder publishRpcSpanBuilder = - tracer - .spanBuilder(topicName.getTopic() + PUBLISH_RPC_SPAN_SUFFIX) - .setSpanKind(SpanKind.CLIENT) - .setAllAttributes(attributes); - Attributes linkAttributes = - Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, "publish").build(); - for (PubsubMessageWrapper message : messages) { - if (message.getPublisherSpan().getSpanContext().isSampled()) - publishRpcSpanBuilder.addLink(message.getPublisherSpan().getSpanContext(), linkAttributes); - } - Span publishRpcSpan = publishRpcSpanBuilder.startSpan(); - - for (PubsubMessageWrapper message : messages) { - if (publishRpcSpan.getSpanContext().isSampled()) { - message.getPublisherSpan().addLink(publishRpcSpan.getSpanContext(), linkAttributes); - message.addPublishStartEvent(); - } - } - return publishRpcSpan; - } - - /** Ends the given publish RPC span if it exists. */ - void endPublishRpcSpan(Span publishRpcSpan) { - if (!enabled) { - return; - } - if (publishRpcSpan != null) { - publishRpcSpan.end(); - } - } - - /** - * Sets an error status and records an exception when an exception is thrown when publishing the - * message batch. - */ - void setPublishRpcSpanException(Span publishRpcSpan, Throwable t) { - if (!enabled) { - return; - } - if (publishRpcSpan != null) { - publishRpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on publish RPC."); - publishRpcSpan.recordException(t); - publishRpcSpan.end(); - } - } - - void startSubscriberSpan(PubsubMessageWrapper message, boolean exactlyOnceDeliveryEnabled) { - if (!enabled) { - return; - } - AttributesBuilder attributesBuilder = - createCommonSpanAttributesBuilder( - message.getSubscriptionName(), message.getSubscriptionProject(), "onResponse", null); - - attributesBuilder - .put(SemanticAttributes.MESSAGING_MESSAGE_ID, message.getMessageId()) - .put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()) - .put(MESSAGE_ACK_ID_ATTR_KEY, message.getAckId()) - .put(MESSAGE_EXACTLY_ONCE_ATTR_KEY, exactlyOnceDeliveryEnabled); - if (!message.getOrderingKey().isEmpty()) { - attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); - } - if (message.getDeliveryAttempt() > 0) { - attributesBuilder.put(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, message.getDeliveryAttempt()); - } - Attributes attributes = attributesBuilder.build(); - Context publisherSpanContext = message.extractSpanContext(attributes); - message.setPublisherSpan(Span.fromContextOrNull(publisherSpanContext)); - message.setSubscriberSpan( - tracer - .spanBuilder(message.getSubscriptionName() + " subscribe") - .setSpanKind(SpanKind.CONSUMER) - .setParent(publisherSpanContext) - .setAllAttributes(attributes) - .startSpan()); - } - - void endSubscriberSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endSubscriberSpan(); - } - - void setSubscriberSpanExpirationResult(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.setSubscriberSpanExpirationResult(); - } - - void setSubscriberSpanException(PubsubMessageWrapper message, Throwable t, String exception) { - if (!enabled) { - return; - } - message.setSubscriberSpanException(t, exception); - } - - /** Creates a span for subscribe concurrency control as a child of the parent subscriber span. */ - void startSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span subscriberSpan = message.getSubscriberSpan(); - if (subscriberSpan != null) { - message.setSubscribeConcurrencyControlSpan( - startChildSpan(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME, subscriberSpan)); - } - } - - void endSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endSubscribeConcurrencyControlSpan(); - } - - void setSubscribeConcurrencyControlSpanException(PubsubMessageWrapper message, Throwable t) { - if (!enabled) { - return; - } - message.setSubscribeConcurrencyControlSpanException(t); - } - - /** - * Creates a span for subscribe ordering key scheduling as a child of the parent subscriber span. - */ - void startSubscribeSchedulerSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span subscriberSpan = message.getSubscriberSpan(); - if (subscriberSpan != null) { - message.setSubscribeSchedulerSpan( - startChildSpan(SUBSCRIBE_SCHEDULER_SPAN_NAME, subscriberSpan)); - } - } - - void endSubscribeSchedulerSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endSubscribeSchedulerSpan(); - } - - /** Creates a span for subscribe message processing as a child of the parent subscriber span. */ - void startSubscribeProcessSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span subscriberSpan = message.getSubscriberSpan(); - if (subscriberSpan != null) { - Span subscribeProcessSpan = - startChildSpan(message.getSubscriptionName() + " process", subscriberSpan); - subscribeProcessSpan.setAttribute( - SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE); - Span publisherSpan = message.getPublisherSpan(); - if (publisherSpan != null) { - subscribeProcessSpan.addLink(publisherSpan.getSpanContext()); - } - message.setSubscribeProcessSpan(subscribeProcessSpan); - } - } - - void endSubscribeProcessSpan(PubsubMessageWrapper message, String action) { - if (!enabled) { - return; - } - message.endSubscribeProcessSpan(action); - } - - /** - * Creates, starts, and returns spans for ModAck, Nack, and Ack RPC requests. Bi-directional links - * to parent subscribe span for sampled messages are added. - */ - Span startSubscribeRpcSpan( - String subscription, - String rpcOperation, - List messages, - int ackDeadline, - boolean isReceiptModack) { - if (!enabled) { - return null; - } - String codeFunction = rpcOperation == "ack" ? "sendAckOperations" : "sendModAckOperations"; - SubscriptionName subscriptionName = SubscriptionName.parse(subscription); - AttributesBuilder attributesBuilder = - createCommonSpanAttributesBuilder( - subscriptionName.getSubscription(), - subscriptionName.getProject(), - codeFunction, - rpcOperation) - .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()); - - // Ack deadline and receipt modack are specific to the modack operation - if (rpcOperation == "modack") { - attributesBuilder - .put(ACK_DEADLINE_ATTR_KEY, ackDeadline) - .put(RECEIPT_MODACK_ATTR_KEY, isReceiptModack); - } - - SpanBuilder rpcSpanBuilder = - tracer - .spanBuilder(subscriptionName.getSubscription() + " " + rpcOperation) - .setSpanKind(SpanKind.CLIENT) - .setAllAttributes(attributesBuilder.build()); - Attributes linkAttributes = - Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, rpcOperation).build(); - for (PubsubMessageWrapper message : messages) { - if (message.getSubscriberSpan().getSpanContext().isSampled()) { - rpcSpanBuilder.addLink(message.getSubscriberSpan().getSpanContext(), linkAttributes); - } - } - Span rpcSpan = rpcSpanBuilder.startSpan(); - - for (PubsubMessageWrapper message : messages) { - if (rpcSpan.getSpanContext().isSampled()) { - message.getSubscriberSpan().addLink(rpcSpan.getSpanContext(), linkAttributes); - switch (rpcOperation) { - case "ack": - message.addAckStartEvent(); - break; - case "modack": - message.addModAckStartEvent(); - break; - case "nack": - message.addNackStartEvent(); - break; - } - } - } - return rpcSpan; - } - - /** Ends the given subscribe RPC span if it exists. */ - void endSubscribeRpcSpan(Span rpcSpan) { - if (!enabled) { - return; - } - if (rpcSpan != null) { - rpcSpan.end(); - } - } - - /** - * Sets an error status and records an exception when an exception is thrown when handling a - * subscribe-side RPC. - */ - void setSubscribeRpcSpanException(Span rpcSpan, boolean isModack, int ackDeadline, Throwable t) { - if (!enabled) { - return; - } - if (rpcSpan != null) { - String operation = !isModack ? "ack" : (ackDeadline == 0 ? "nack" : "modack"); - rpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on " + operation + " RPC."); - rpcSpan.recordException(t); - rpcSpan.end(); - } - } - - /** Adds the appropriate subscribe-side RPC end event. */ - void addEndRpcEvent( - PubsubMessageWrapper message, boolean rpcSampled, boolean isModack, int ackDeadline) { - if (!enabled || !rpcSampled) { - return; - } - if (!isModack) { - message.addAckEndEvent(); - } else if (ackDeadline == 0) { - message.addNackEndEvent(); - } else { - message.addModAckEndEvent(); - } - } -} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java deleted file mode 100644 index 94fd13085..000000000 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java +++ /dev/null @@ -1,430 +0,0 @@ -/* - * 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.pubsub.v1; - -import com.google.pubsub.v1.PubsubMessage; -import com.google.pubsub.v1.SubscriptionName; -import com.google.pubsub.v1.TopicName; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.propagation.TextMapGetter; -import io.opentelemetry.context.propagation.TextMapSetter; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; - -/** - * A wrapper class for a {@link PubsubMessage} object that handles creation and tracking of - * OpenTelemetry {@link Span} objects for different operations that occur during publishing. - */ -public class PubsubMessageWrapper { - private PubsubMessage message; - - private final TopicName topicName; - private final SubscriptionName subscriptionName; - - // Attributes set only for messages received from a streaming pull response. - private final String ackId; - private final int deliveryAttempt; - - private static final String PUBLISH_START_EVENT = "publish start"; - private static final String PUBLISH_END_EVENT = "publish end"; - - private static final String MODACK_START_EVENT = "modack start"; - private static final String MODACK_END_EVENT = "modack end"; - private static final String NACK_START_EVENT = "nack start"; - private static final String NACK_END_EVENT = "nack end"; - private static final String ACK_START_EVENT = "ack start"; - private static final String ACK_END_EVENT = "ack end"; - - private static final String GOOGCLIENT_PREFIX = "googclient_"; - - private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; - - private Span publisherSpan; - private Span publishFlowControlSpan; - private Span publishBatchingSpan; - - private Span subscriberSpan; - private Span subscribeConcurrencyControlSpan; - private Span subscribeSchedulerSpan; - private Span subscribeProcessSpan; - - private PubsubMessageWrapper(Builder builder) { - this.message = builder.message; - this.topicName = builder.topicName; - this.subscriptionName = builder.subscriptionName; - this.ackId = builder.ackId; - this.deliveryAttempt = builder.deliveryAttempt; - } - - static Builder newBuilder(PubsubMessage message, String topicName) { - return new Builder(message, topicName); - } - - static Builder newBuilder( - PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { - return new Builder(message, subscriptionName, ackId, deliveryAttempt); - } - - /** Returns the PubsubMessage associated with this wrapper. */ - PubsubMessage getPubsubMessage() { - return message; - } - - void setPubsubMessage(PubsubMessage message) { - this.message = message; - } - - /** Returns the TopicName for this wrapper as a string. */ - String getTopicName() { - if (topicName != null) { - return topicName.getTopic(); - } - return ""; - } - - String getTopicProject() { - if (topicName != null) { - return topicName.getProject(); - } - return ""; - } - - /** Returns the SubscriptionName for this wrapper as a string. */ - String getSubscriptionName() { - if (subscriptionName != null) { - return subscriptionName.getSubscription(); - } - return ""; - } - - String getSubscriptionProject() { - if (subscriptionName != null) { - return subscriptionName.getProject(); - } - return ""; - } - - String getMessageId() { - return message.getMessageId(); - } - - String getAckId() { - return ackId; - } - - int getDataSize() { - return message.getData().size(); - } - - String getOrderingKey() { - return message.getOrderingKey(); - } - - int getDeliveryAttempt() { - return deliveryAttempt; - } - - Span getPublisherSpan() { - return publisherSpan; - } - - void setPublisherSpan(Span span) { - this.publisherSpan = span; - } - - void setPublishFlowControlSpan(Span span) { - this.publishFlowControlSpan = span; - } - - void setPublishBatchingSpan(Span span) { - this.publishBatchingSpan = span; - } - - Span getSubscriberSpan() { - return subscriberSpan; - } - - void setSubscriberSpan(Span span) { - this.subscriberSpan = span; - } - - void setSubscribeConcurrencyControlSpan(Span span) { - this.subscribeConcurrencyControlSpan = span; - } - - void setSubscribeSchedulerSpan(Span span) { - this.subscribeSchedulerSpan = span; - } - - void setSubscribeProcessSpan(Span span) { - this.subscribeProcessSpan = span; - } - - /** Creates a publish start event that is tied to the publish RPC span time. */ - void addPublishStartEvent() { - if (publisherSpan != null) { - publisherSpan.addEvent(PUBLISH_START_EVENT); - } - } - - /** - * Sets the message ID attribute in the publisher parent span. This is called after the publish - * RPC returns with a message ID. - */ - void setPublisherMessageIdSpanAttribute(String messageId) { - if (publisherSpan != null) { - publisherSpan.setAttribute(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId); - } - } - - /** Ends the publisher parent span if it exists. */ - void endPublisherSpan() { - if (publisherSpan != null) { - publisherSpan.addEvent(PUBLISH_END_EVENT); - publisherSpan.end(); - } - } - - /** Ends the publish flow control span if it exists. */ - void endPublishFlowControlSpan() { - if (publishFlowControlSpan != null) { - publishFlowControlSpan.end(); - } - } - - /** Ends the publish batching span if it exists. */ - void endPublishBatchingSpan() { - if (publishBatchingSpan != null) { - publishBatchingSpan.end(); - } - } - - /** - * Sets an error status and records an exception when an exception is thrown during flow control. - */ - void setPublishFlowControlSpanException(Throwable t) { - if (publishFlowControlSpan != null) { - publishFlowControlSpan.setStatus( - StatusCode.ERROR, "Exception thrown during publish flow control."); - publishFlowControlSpan.recordException(t); - endAllPublishSpans(); - } - } - - /** - * Creates start and end events for ModAcks, Nacks, and Acks that are tied to the corresponding - * RPC span start and end times. - */ - void addModAckStartEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(MODACK_START_EVENT); - } - } - - void addModAckEndEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(MODACK_END_EVENT); - } - } - - void addNackStartEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(NACK_START_EVENT); - } - } - - void addNackEndEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(NACK_END_EVENT); - } - } - - void addAckStartEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(ACK_START_EVENT); - } - } - - void addAckEndEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(ACK_END_EVENT); - } - } - - /** Ends the subscriber parent span if exists. */ - void endSubscriberSpan() { - if (subscriberSpan != null) { - subscriberSpan.end(); - } - } - - /** Ends the subscribe concurreny control span if exists. */ - void endSubscribeConcurrencyControlSpan() { - if (subscribeConcurrencyControlSpan != null) { - subscribeConcurrencyControlSpan.end(); - } - } - - /** Ends the subscribe scheduler span if exists. */ - void endSubscribeSchedulerSpan() { - if (subscribeSchedulerSpan != null) { - subscribeSchedulerSpan.end(); - } - } - - /** - * Ends the subscribe process span if it exists, creates an event with the appropriate result, and - * sets the result on the parent subscriber span. - */ - void endSubscribeProcessSpan(String action) { - if (subscribeProcessSpan != null) { - subscribeProcessSpan.addEvent(action + " called"); - subscribeProcessSpan.end(); - subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, action); - } - } - - /** Sets an exception on the subscriber span during Ack/ModAck/Nack failures */ - void setSubscriberSpanException(Throwable t, String exception) { - if (subscriberSpan != null) { - subscriberSpan.setStatus(StatusCode.ERROR, exception); - subscriberSpan.recordException(t); - endAllSubscribeSpans(); - } - } - - /** Sets result of the parent subscriber span to expired and ends its. */ - void setSubscriberSpanExpirationResult() { - if (subscriberSpan != null) { - subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, "expired"); - endSubscriberSpan(); - } - } - - /** - * Sets an error status and records an exception when an exception is thrown subscriber - * concurrency control. - */ - void setSubscribeConcurrencyControlSpanException(Throwable t) { - if (subscribeConcurrencyControlSpan != null) { - subscribeConcurrencyControlSpan.setStatus( - StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); - subscribeConcurrencyControlSpan.recordException(t); - endAllSubscribeSpans(); - } - } - - /** Ends all publisher-side spans associated with this message wrapper. */ - private void endAllPublishSpans() { - endPublishFlowControlSpan(); - endPublishBatchingSpan(); - endPublisherSpan(); - } - - /** Ends all subscriber-side spans associated with this message wrapper. */ - private void endAllSubscribeSpans() { - endSubscribeConcurrencyControlSpan(); - endSubscribeSchedulerSpan(); - endSubscriberSpan(); - } - - /** - * Injects the span context into the attributes of a Pub/Sub message for propagation to the - * subscriber client. - */ - void injectSpanContext() { - TextMapSetter injectMessageAttributes = - new TextMapSetter() { - @Override - public void set(PubsubMessageWrapper carrier, String key, String value) { - PubsubMessage newMessage = - PubsubMessage.newBuilder(carrier.message) - .putAttributes(GOOGCLIENT_PREFIX + key, value) - .build(); - carrier.message = newMessage; - } - }; - W3CTraceContextPropagator.getInstance() - .inject(Context.current().with(publisherSpan), this, injectMessageAttributes); - } - - /** - * Extracts the span context from the attributes of a Pub/Sub message and creates the parent - * subscriber span using that context. - */ - Context extractSpanContext(Attributes attributes) { - TextMapGetter extractMessageAttributes = - new TextMapGetter() { - @Override - public String get(PubsubMessageWrapper carrier, String key) { - return carrier.message.getAttributesOrDefault(GOOGCLIENT_PREFIX + key, ""); - } - - public Iterable keys(PubsubMessageWrapper carrier) { - return carrier.message.getAttributesMap().keySet(); - } - }; - Context context = - W3CTraceContextPropagator.getInstance() - .extract(Context.current(), this, extractMessageAttributes); - return context; - } - - /** Builder of {@link PubsubMessageWrapper PubsubMessageWrapper}. */ - static final class Builder { - private PubsubMessage message = null; - private TopicName topicName = null; - private SubscriptionName subscriptionName = null; - private String ackId = null; - private int deliveryAttempt = 0; - - public Builder(PubsubMessage message, String topicName) { - this.message = message; - if (topicName != null) { - this.topicName = TopicName.parse(topicName); - } - } - - public Builder( - PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { - this.message = message; - if (subscriptionName != null) { - this.subscriptionName = SubscriptionName.parse(subscriptionName); - } - this.ackId = ackId; - this.deliveryAttempt = deliveryAttempt; - } - - public Builder( - PubsubMessage message, - SubscriptionName subscriptionName, - String ackId, - int deliveryAttempt) { - this.message = message; - this.subscriptionName = subscriptionName; - this.ackId = ackId; - this.deliveryAttempt = deliveryAttempt; - } - - public PubsubMessageWrapper build() { - return new PubsubMessageWrapper(this); - } - } -} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java deleted file mode 100644 index b4433f41e..000000000 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ /dev/null @@ -1,669 +0,0 @@ -/* - * 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.pubsub.v1; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import com.google.protobuf.ByteString; -import com.google.pubsub.v1.PubsubMessage; -import com.google.pubsub.v1.SubscriptionName; -import com.google.pubsub.v1.TopicName; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.sdk.testing.assertj.AttributesAssert; -import io.opentelemetry.sdk.testing.assertj.EventDataAssert; -import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; -import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; -import io.opentelemetry.sdk.testing.junit4.OpenTelemetryRule; -import io.opentelemetry.sdk.trace.data.LinkData; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.data.StatusData; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.util.Arrays; -import java.util.List; -import org.junit.Test; - -public class OpenTelemetryTest { - private static final TopicName FULL_TOPIC_NAME = - TopicName.parse("projects/test-project/topics/test-topic"); - private static final SubscriptionName FULL_SUBSCRIPTION_NAME = - SubscriptionName.parse("projects/test-project/subscriptions/test-sub"); - private static final String PROJECT_NAME = "test-project"; - private static final String ORDERING_KEY = "abc"; - private static final String MESSAGE_ID = "m0"; - private static final String ACK_ID = "def"; - private static final int DELIVERY_ATTEMPT = 1; - private static final int ACK_DEADLINE = 10; - private static final boolean EXACTLY_ONCE_ENABLED = true; - - private static final String PUBLISHER_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " create"; - private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; - private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; - private static final String PUBLISH_RPC_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " publish"; - private static final String PUBLISH_START_EVENT = "publish start"; - private static final String PUBLISH_END_EVENT = "publish end"; - - private static final String SUBSCRIBER_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " subscribe"; - private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = - "subscriber concurrency control"; - private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; - private static final String SUBSCRIBE_PROCESS_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " process"; - private static final String SUBSCRIBE_MODACK_RPC_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " modack"; - private static final String SUBSCRIBE_ACK_RPC_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " ack"; - private static final String SUBSCRIBE_NACK_RPC_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " nack"; - - private static final String PROCESS_ACTION = "ack"; - private static final String MODACK_START_EVENT = "modack start"; - private static final String MODACK_END_EVENT = "modack end"; - private static final String NACK_START_EVENT = "nack start"; - private static final String NACK_END_EVENT = "nack end"; - private static final String ACK_START_EVENT = "ack start"; - private static final String ACK_END_EVENT = "ack end"; - - private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; - private static final String PROJECT_ATTR_KEY = "gcp.project_id"; - private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; - private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; - private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; - private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; - private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; - private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = - "messaging.gcp_pubsub.message.exactly_once_delivery"; - private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; - private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = - "messaging.gcp_pubsub.message.delivery_attempt"; - - private static final String TRACEPARENT_ATTRIBUTE = "googclient_traceparent"; - - private static final OpenTelemetryRule openTelemetryTesting = OpenTelemetryRule.create(); - - @Test - public void testPublishSpansSuccess() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); - List messageWrappers = Arrays.asList(messageWrapper); - - long messageSize = messageWrapper.getPubsubMessage().getData().size(); - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - // Call all span start/end methods in the expected order - tracer.startPublisherSpan(messageWrapper); - tracer.startPublishFlowControlSpan(messageWrapper); - tracer.endPublishFlowControlSpan(messageWrapper); - tracer.startPublishBatchingSpan(messageWrapper); - tracer.endPublishBatchingSpan(messageWrapper); - Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); - tracer.endPublishRpcSpan(publishRpcSpan); - tracer.setPublisherMessageIdSpanAttribute(messageWrapper, MESSAGE_ID); - tracer.endPublisherSpan(messageWrapper); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(4, allSpans.size()); - SpanData flowControlSpanData = allSpans.get(0); - SpanData batchingSpanData = allSpans.get(1); - SpanData publishRpcSpanData = allSpans.get(2); - SpanData publisherSpanData = allSpans.get(3); - - SpanDataAssert flowControlSpanDataAssert = - OpenTelemetryAssertions.assertThat(flowControlSpanData); - flowControlSpanDataAssert - .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) - .hasParent(publisherSpanData) - .hasEnded(); - - SpanDataAssert batchingSpanDataAssert = OpenTelemetryAssertions.assertThat(batchingSpanData); - batchingSpanDataAssert - .hasName(PUBLISH_BATCHING_SPAN_NAME) - .hasParent(publisherSpanData) - .hasEnded(); - - // Check span data, links, and attributes for the publish RPC span - SpanDataAssert publishRpcSpanDataAssert = - OpenTelemetryAssertions.assertThat(publishRpcSpanData); - publishRpcSpanDataAssert - .hasName(PUBLISH_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasEnded(); - - List publishRpcLinks = publishRpcSpanData.getLinks(); - assertEquals(messageWrappers.size(), publishRpcLinks.size()); - assertEquals(publisherSpanData.getSpanContext(), publishRpcLinks.get(0).getSpanContext()); - - assertEquals(6, publishRpcSpanData.getAttributes().size()); - AttributesAssert publishRpcSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(publishRpcSpanData.getAttributes()); - publishRpcSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) - .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "publishCall") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "publish") - .containsEntry(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messageWrappers.size()); - - // Check span data, events, links, and attributes for the publisher create span - SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); - publisherSpanDataAssert - .hasName(PUBLISHER_SPAN_NAME) - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasEnded(); - - assertEquals(2, publisherSpanData.getEvents().size()); - EventDataAssert startEventAssert = - OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(0)); - startEventAssert.hasName(PUBLISH_START_EVENT); - EventDataAssert endEventAssert = - OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(1)); - endEventAssert.hasName(PUBLISH_END_EVENT); - - List publisherLinks = publisherSpanData.getLinks(); - assertEquals(1, publisherLinks.size()); - assertEquals(publishRpcSpanData.getSpanContext(), publisherLinks.get(0).getSpanContext()); - - assertEquals(8, publisherSpanData.getAttributes().size()); - AttributesAssert publisherSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(publisherSpanData.getAttributes()); - publisherSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) - .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "publish") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "create") - .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) - .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) - .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); - - // Check that the message has the attribute containing the trace context. - PubsubMessage message = messageWrapper.getPubsubMessage(); - assertEquals(1, message.getAttributesMap().size()); - assertTrue(message.containsAttributes(TRACEPARENT_ATTRIBUTE)); - assertTrue( - message - .getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "") - .contains(publisherSpanData.getTraceId())); - assertTrue( - message - .getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "") - .contains(publisherSpanData.getSpanId())); - } - - @Test - public void testPublishFlowControlSpanFailure() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startPublisherSpan(messageWrapper); - tracer.startPublishFlowControlSpan(messageWrapper); - - Exception e = new Exception("test-exception"); - tracer.setPublishFlowControlSpanException(messageWrapper, e); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(2, allSpans.size()); - SpanData flowControlSpanData = allSpans.get(0); - SpanData publisherSpanData = allSpans.get(1); - - SpanDataAssert flowControlSpanDataAssert = - OpenTelemetryAssertions.assertThat(flowControlSpanData); - StatusData expectedStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown during publish flow control."); - flowControlSpanDataAssert - .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) - .hasParent(publisherSpanData) - .hasStatus(expectedStatus) - .hasException(e) - .hasEnded(); - - SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); - publisherSpanDataAssert - .hasName(PUBLISHER_SPAN_NAME) - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasEnded(); - } - - @Test - public void testPublishRpcSpanFailure() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); - - List messageWrappers = Arrays.asList(messageWrapper); - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startPublisherSpan(messageWrapper); - Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); - - Exception e = new Exception("test-exception"); - tracer.setPublishRpcSpanException(publishRpcSpan, e); - tracer.endPublisherSpan(messageWrapper); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(2, allSpans.size()); - SpanData rpcSpanData = allSpans.get(0); - SpanData publisherSpanData = allSpans.get(1); - - SpanDataAssert rpcSpanDataAssert = OpenTelemetryAssertions.assertThat(rpcSpanData); - StatusData expectedStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown on publish RPC."); - rpcSpanDataAssert - .hasName(PUBLISH_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasStatus(expectedStatus) - .hasException(e) - .hasEnded(); - - SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); - publisherSpanDataAssert - .hasName(PUBLISHER_SPAN_NAME) - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasEnded(); - } - - @Test - public void testSubscribeSpansSuccess() { - openTelemetryTesting.clearSpans(); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - PubsubMessageWrapper publishMessageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); - // Initialize the Publisher span to inject the context in the message - tracer.startPublisherSpan(publishMessageWrapper); - tracer.endPublisherSpan(publishMessageWrapper); - - PubsubMessage publishedMessage = - publishMessageWrapper.getPubsubMessage().toBuilder().setMessageId(MESSAGE_ID).build(); - PubsubMessageWrapper subscribeMessageWrapper = - PubsubMessageWrapper.newBuilder( - publishedMessage, FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, 1) - .build(); - List subscribeMessageWrappers = Arrays.asList(subscribeMessageWrapper); - - long messageSize = subscribeMessageWrapper.getPubsubMessage().getData().size(); - - // Call all span start/end methods in the expected order - tracer.startSubscriberSpan(subscribeMessageWrapper, EXACTLY_ONCE_ENABLED); - tracer.startSubscribeConcurrencyControlSpan(subscribeMessageWrapper); - tracer.endSubscribeConcurrencyControlSpan(subscribeMessageWrapper); - tracer.startSubscribeSchedulerSpan(subscribeMessageWrapper); - tracer.endSubscribeSchedulerSpan(subscribeMessageWrapper); - tracer.startSubscribeProcessSpan(subscribeMessageWrapper); - tracer.endSubscribeProcessSpan(subscribeMessageWrapper, PROCESS_ACTION); - Span subscribeModackRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), - "modack", - subscribeMessageWrappers, - ACK_DEADLINE, - true); - tracer.endSubscribeRpcSpan(subscribeModackRpcSpan); - tracer.addEndRpcEvent(subscribeMessageWrapper, true, true, ACK_DEADLINE); - Span subscribeAckRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "ack", subscribeMessageWrappers, 0, false); - tracer.endSubscribeRpcSpan(subscribeAckRpcSpan); - tracer.addEndRpcEvent(subscribeMessageWrapper, true, false, 0); - Span subscribeNackRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "nack", subscribeMessageWrappers, 0, false); - tracer.endSubscribeRpcSpan(subscribeNackRpcSpan); - tracer.addEndRpcEvent(subscribeMessageWrapper, true, true, 0); - tracer.endSubscriberSpan(subscribeMessageWrapper); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(8, allSpans.size()); - - SpanData publisherSpanData = allSpans.get(0); - SpanData concurrencyControlSpanData = allSpans.get(1); - SpanData schedulerSpanData = allSpans.get(2); - SpanData processSpanData = allSpans.get(3); - SpanData modackRpcSpanData = allSpans.get(4); - SpanData ackRpcSpanData = allSpans.get(5); - SpanData nackRpcSpanData = allSpans.get(6); - SpanData subscriberSpanData = allSpans.get(7); - - SpanDataAssert concurrencyControlSpanDataAssert = - OpenTelemetryAssertions.assertThat(concurrencyControlSpanData); - concurrencyControlSpanDataAssert - .hasName(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME) - .hasParent(subscriberSpanData) - .hasEnded(); - - SpanDataAssert schedulerSpanDataAssert = OpenTelemetryAssertions.assertThat(schedulerSpanData); - schedulerSpanDataAssert - .hasName(SUBSCRIBE_SCHEDULER_SPAN_NAME) - .hasParent(subscriberSpanData) - .hasEnded(); - - SpanDataAssert processSpanDataAssert = OpenTelemetryAssertions.assertThat(processSpanData); - processSpanDataAssert - .hasName(SUBSCRIBE_PROCESS_SPAN_NAME) - .hasParent(subscriberSpanData) - .hasEnded(); - - assertEquals(1, processSpanData.getEvents().size()); - EventDataAssert actionCalledEventAssert = - OpenTelemetryAssertions.assertThat(processSpanData.getEvents().get(0)); - actionCalledEventAssert.hasName(PROCESS_ACTION + " called"); - - // Check span data, links, and attributes for the modack RPC span - SpanDataAssert modackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(modackRpcSpanData); - modackRpcSpanDataAssert - .hasName(SUBSCRIBE_MODACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasEnded(); - - List modackRpcLinks = modackRpcSpanData.getLinks(); - assertEquals(subscribeMessageWrappers.size(), modackRpcLinks.size()); - assertEquals(subscriberSpanData.getSpanContext(), modackRpcLinks.get(0).getSpanContext()); - - assertEquals(8, modackRpcSpanData.getAttributes().size()); - AttributesAssert modackRpcSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(modackRpcSpanData.getAttributes()); - modackRpcSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry( - SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) - .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "modack") - .containsEntry( - SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()) - .containsEntry(ACK_DEADLINE_ATTR_KEY, 10) - .containsEntry(RECEIPT_MODACK_ATTR_KEY, true); - - // Check span data, links, and attributes for the ack RPC span - SpanDataAssert ackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(ackRpcSpanData); - ackRpcSpanDataAssert - .hasName(SUBSCRIBE_ACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasEnded(); - - List ackRpcLinks = ackRpcSpanData.getLinks(); - assertEquals(subscribeMessageWrappers.size(), ackRpcLinks.size()); - assertEquals(subscriberSpanData.getSpanContext(), ackRpcLinks.get(0).getSpanContext()); - - assertEquals(6, ackRpcSpanData.getAttributes().size()); - AttributesAssert ackRpcSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(ackRpcSpanData.getAttributes()); - ackRpcSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry( - SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) - .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendAckOperations") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "ack") - .containsEntry( - SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); - - // Check span data, links, and attributes for the nack RPC span - SpanDataAssert nackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(nackRpcSpanData); - nackRpcSpanDataAssert - .hasName(SUBSCRIBE_NACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasEnded(); - - List nackRpcLinks = nackRpcSpanData.getLinks(); - assertEquals(subscribeMessageWrappers.size(), nackRpcLinks.size()); - assertEquals(subscriberSpanData.getSpanContext(), nackRpcLinks.get(0).getSpanContext()); - - assertEquals(6, nackRpcSpanData.getAttributes().size()); - AttributesAssert nackRpcSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(nackRpcSpanData.getAttributes()); - nackRpcSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry( - SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) - .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "nack") - .containsEntry( - SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); - - // Check span data, events, links, and attributes for the publisher create span - SpanDataAssert subscriberSpanDataAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData); - subscriberSpanDataAssert - .hasName(SUBSCRIBER_SPAN_NAME) - .hasKind(SpanKind.CONSUMER) - .hasParent(publisherSpanData) - .hasEnded(); - - assertEquals(6, subscriberSpanData.getEvents().size()); - EventDataAssert startModackEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(0)); - startModackEventAssert.hasName(MODACK_START_EVENT); - EventDataAssert endModackEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(1)); - endModackEventAssert.hasName(MODACK_END_EVENT); - EventDataAssert startAckEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(2)); - startAckEventAssert.hasName(ACK_START_EVENT); - EventDataAssert endAckEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(3)); - endAckEventAssert.hasName(ACK_END_EVENT); - EventDataAssert startNackEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(4)); - startNackEventAssert.hasName(NACK_START_EVENT); - EventDataAssert endNackEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(5)); - endNackEventAssert.hasName(NACK_END_EVENT); - - List subscriberLinks = subscriberSpanData.getLinks(); - assertEquals(3, subscriberLinks.size()); - assertEquals(modackRpcSpanData.getSpanContext(), subscriberLinks.get(0).getSpanContext()); - assertEquals(ackRpcSpanData.getSpanContext(), subscriberLinks.get(1).getSpanContext()); - assertEquals(nackRpcSpanData.getSpanContext(), subscriberLinks.get(2).getSpanContext()); - - assertEquals(11, subscriberSpanData.getAttributes().size()); - AttributesAssert subscriberSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getAttributes()); - subscriberSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry( - SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) - .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "onResponse") - .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) - .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) - .containsEntry(MESSAGE_ACK_ID_ATTR_KEY, ACK_ID) - .containsEntry(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, DELIVERY_ATTEMPT) - .containsEntry(MESSAGE_EXACTLY_ONCE_ATTR_KEY, EXACTLY_ONCE_ENABLED) - .containsEntry(MESSAGE_RESULT_ATTR_KEY, PROCESS_ACTION) - .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); - } - - @Test - public void testSubscribeConcurrencyControlSpanFailure() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder( - getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) - .build(); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); - tracer.startSubscribeConcurrencyControlSpan(messageWrapper); - - Exception e = new Exception("test-exception"); - tracer.setSubscribeConcurrencyControlSpanException(messageWrapper, e); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(2, allSpans.size()); - SpanData concurrencyControlSpanData = allSpans.get(0); - SpanData subscriberSpanData = allSpans.get(1); - - SpanDataAssert concurrencyControlSpanDataAssert = - OpenTelemetryAssertions.assertThat(concurrencyControlSpanData); - StatusData expectedStatus = - StatusData.create( - StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); - concurrencyControlSpanDataAssert - .hasName(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME) - .hasParent(subscriberSpanData) - .hasStatus(expectedStatus) - .hasException(e) - .hasEnded(); - - SpanDataAssert subscriberSpanDataAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData); - subscriberSpanDataAssert - .hasName(SUBSCRIBER_SPAN_NAME) - .hasKind(SpanKind.CONSUMER) - .hasNoParent() - .hasEnded(); - } - - @Test - public void testSubscriberSpanFailure() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder( - getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) - .build(); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); - - Exception e = new Exception("test-exception"); - tracer.setSubscriberSpanException(messageWrapper, e, "Test exception"); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(1, allSpans.size()); - SpanData subscriberSpanData = allSpans.get(0); - - StatusData expectedStatus = StatusData.create(StatusCode.ERROR, "Test exception"); - SpanDataAssert subscriberSpanDataAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData); - subscriberSpanDataAssert - .hasName(SUBSCRIBER_SPAN_NAME) - .hasKind(SpanKind.CONSUMER) - .hasNoParent() - .hasStatus(expectedStatus) - .hasException(e) - .hasEnded(); - } - - @Test - public void testSubscribeRpcSpanFailures() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder( - getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) - .build(); - List messageWrappers = Arrays.asList(messageWrapper); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); - Span subscribeModackRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "modack", messageWrappers, ACK_DEADLINE, true); - Span subscribeAckRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "ack", messageWrappers, 0, false); - Span subscribeNackRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "nack", messageWrappers, 0, false); - - Exception e = new Exception("test-exception"); - tracer.setSubscribeRpcSpanException(subscribeModackRpcSpan, true, ACK_DEADLINE, e); - tracer.setSubscribeRpcSpanException(subscribeAckRpcSpan, false, 0, e); - tracer.setSubscribeRpcSpanException(subscribeNackRpcSpan, true, 0, e); - tracer.endSubscriberSpan(messageWrapper); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(4, allSpans.size()); - SpanData modackSpanData = allSpans.get(0); - SpanData ackSpanData = allSpans.get(1); - SpanData nackSpanData = allSpans.get(2); - SpanData subscriberSpanData = allSpans.get(3); - - StatusData expectedModackStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown on modack RPC."); - SpanDataAssert modackSpanDataAssert = OpenTelemetryAssertions.assertThat(modackSpanData); - modackSpanDataAssert - .hasName(SUBSCRIBE_MODACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasStatus(expectedModackStatus) - .hasException(e) - .hasEnded(); - - StatusData expectedAckStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown on ack RPC."); - SpanDataAssert ackSpanDataAssert = OpenTelemetryAssertions.assertThat(ackSpanData); - ackSpanDataAssert - .hasName(SUBSCRIBE_ACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasStatus(expectedAckStatus) - .hasException(e) - .hasEnded(); - - StatusData expectedNackStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown on nack RPC."); - SpanDataAssert nackSpanDataAssert = OpenTelemetryAssertions.assertThat(nackSpanData); - nackSpanDataAssert - .hasName(SUBSCRIBE_NACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasStatus(expectedNackStatus) - .hasException(e) - .hasEnded(); - } - - private PubsubMessage getPubsubMessage() { - return PubsubMessage.newBuilder() - .setData(ByteString.copyFromUtf8("test-data")) - .setOrderingKey(ORDERING_KEY) - .build(); - } -} From ea884128e2745b041e1d368ae16999e18db19913 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 30 Sep 2024 21:11:54 +0000 Subject: [PATCH 41/46] Revert "chore: generate libraries at Mon Sep 30 21:03:31 UTC 2024" This reverts commit 23f3a70d64f0f72cf18dd3a7640125ff9027dec7. --- .../pubsub/v1/OpenTelemetryPubsubTracer.java | 460 ++++++++++++ .../cloud/pubsub/v1/PubsubMessageWrapper.java | 430 +++++++++++ .../cloud/pubsub/v1/OpenTelemetryTest.java | 669 ++++++++++++++++++ 3 files changed, 1559 insertions(+) create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java new file mode 100644 index 000000000..b946f44bf --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java @@ -0,0 +1,460 @@ +/* + * 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.pubsub.v1; + +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.SubscriptionName; +import com.google.pubsub.v1.TopicName; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.List; + +public class OpenTelemetryPubsubTracer { + private final Tracer tracer; + private boolean enabled = false; + + private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; + private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; + private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = + "subscriber concurrency control"; + private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; + + private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; + private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; + private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; + private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = + "messaging.gcp_pubsub.message.exactly_once_delivery"; + private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = + "messaging.gcp_pubsub.message.delivery_attempt"; + private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; + private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; + private static final String PROJECT_ATTR_KEY = "gcp.project_id"; + private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; + + private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; + + OpenTelemetryPubsubTracer(Tracer tracer, boolean enableOpenTelemetry) { + this.tracer = tracer; + if (this.tracer != null && enableOpenTelemetry) { + this.enabled = true; + } + } + + /** Populates attributes that are common the publisher parent span and publish RPC span. */ + private static final AttributesBuilder createCommonSpanAttributesBuilder( + String destinationName, String projectName, String codeFunction, String operation) { + AttributesBuilder attributesBuilder = + Attributes.builder() + .put(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .put(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName) + .put(PROJECT_ATTR_KEY, projectName) + .put(SemanticAttributes.CODE_FUNCTION, codeFunction); + if (operation != null) { + attributesBuilder.put(SemanticAttributes.MESSAGING_OPERATION, operation); + } + + return attributesBuilder; + } + + private Span startChildSpan(String name, Span parent) { + return tracer.spanBuilder(name).setParent(Context.current().with(parent)).startSpan(); + } + + /** + * Creates and starts the parent span with the appropriate span attributes and injects the span + * context into the {@link PubsubMessage} attributes. + */ + void startPublisherSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + AttributesBuilder attributesBuilder = + createCommonSpanAttributesBuilder( + message.getTopicName(), message.getTopicProject(), "publish", "create"); + + attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()); + if (!message.getOrderingKey().isEmpty()) { + attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); + } + + Span publisherSpan = + tracer + .spanBuilder(message.getTopicName() + " create") + .setSpanKind(SpanKind.PRODUCER) + .setAllAttributes(attributesBuilder.build()) + .startSpan(); + + message.setPublisherSpan(publisherSpan); + if (publisherSpan.getSpanContext().isValid()) { + message.injectSpanContext(); + } + } + + void endPublisherSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endPublisherSpan(); + } + + void setPublisherMessageIdSpanAttribute(PubsubMessageWrapper message, String messageId) { + if (!enabled) { + return; + } + message.setPublisherMessageIdSpanAttribute(messageId); + } + + /** Creates a span for publish-side flow control as a child of the parent publisher span. */ + void startPublishFlowControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span publisherSpan = message.getPublisherSpan(); + if (publisherSpan != null) + message.setPublishFlowControlSpan( + startChildSpan(PUBLISH_FLOW_CONTROL_SPAN_NAME, publisherSpan)); + } + + void endPublishFlowControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endPublishFlowControlSpan(); + } + + void setPublishFlowControlSpanException(PubsubMessageWrapper message, Throwable t) { + if (!enabled) { + return; + } + message.setPublishFlowControlSpanException(t); + } + + /** Creates a span for publish message batching as a child of the parent publisher span. */ + void startPublishBatchingSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span publisherSpan = message.getPublisherSpan(); + if (publisherSpan != null) { + message.setPublishBatchingSpan(startChildSpan(PUBLISH_BATCHING_SPAN_NAME, publisherSpan)); + } + } + + void endPublishBatchingSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endPublishBatchingSpan(); + } + + /** + * Creates, starts, and returns a publish RPC span for the given message batch. Bi-directional + * links with the publisher parent span are created for sampled messages in the batch. + */ + Span startPublishRpcSpan(String topic, List messages) { + if (!enabled) { + return null; + } + TopicName topicName = TopicName.parse(topic); + Attributes attributes = + createCommonSpanAttributesBuilder( + topicName.getTopic(), topicName.getProject(), "publishCall", "publish") + .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()) + .build(); + SpanBuilder publishRpcSpanBuilder = + tracer + .spanBuilder(topicName.getTopic() + PUBLISH_RPC_SPAN_SUFFIX) + .setSpanKind(SpanKind.CLIENT) + .setAllAttributes(attributes); + Attributes linkAttributes = + Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, "publish").build(); + for (PubsubMessageWrapper message : messages) { + if (message.getPublisherSpan().getSpanContext().isSampled()) + publishRpcSpanBuilder.addLink(message.getPublisherSpan().getSpanContext(), linkAttributes); + } + Span publishRpcSpan = publishRpcSpanBuilder.startSpan(); + + for (PubsubMessageWrapper message : messages) { + if (publishRpcSpan.getSpanContext().isSampled()) { + message.getPublisherSpan().addLink(publishRpcSpan.getSpanContext(), linkAttributes); + message.addPublishStartEvent(); + } + } + return publishRpcSpan; + } + + /** Ends the given publish RPC span if it exists. */ + void endPublishRpcSpan(Span publishRpcSpan) { + if (!enabled) { + return; + } + if (publishRpcSpan != null) { + publishRpcSpan.end(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown when publishing the + * message batch. + */ + void setPublishRpcSpanException(Span publishRpcSpan, Throwable t) { + if (!enabled) { + return; + } + if (publishRpcSpan != null) { + publishRpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on publish RPC."); + publishRpcSpan.recordException(t); + publishRpcSpan.end(); + } + } + + void startSubscriberSpan(PubsubMessageWrapper message, boolean exactlyOnceDeliveryEnabled) { + if (!enabled) { + return; + } + AttributesBuilder attributesBuilder = + createCommonSpanAttributesBuilder( + message.getSubscriptionName(), message.getSubscriptionProject(), "onResponse", null); + + attributesBuilder + .put(SemanticAttributes.MESSAGING_MESSAGE_ID, message.getMessageId()) + .put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()) + .put(MESSAGE_ACK_ID_ATTR_KEY, message.getAckId()) + .put(MESSAGE_EXACTLY_ONCE_ATTR_KEY, exactlyOnceDeliveryEnabled); + if (!message.getOrderingKey().isEmpty()) { + attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); + } + if (message.getDeliveryAttempt() > 0) { + attributesBuilder.put(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, message.getDeliveryAttempt()); + } + Attributes attributes = attributesBuilder.build(); + Context publisherSpanContext = message.extractSpanContext(attributes); + message.setPublisherSpan(Span.fromContextOrNull(publisherSpanContext)); + message.setSubscriberSpan( + tracer + .spanBuilder(message.getSubscriptionName() + " subscribe") + .setSpanKind(SpanKind.CONSUMER) + .setParent(publisherSpanContext) + .setAllAttributes(attributes) + .startSpan()); + } + + void endSubscriberSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endSubscriberSpan(); + } + + void setSubscriberSpanExpirationResult(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.setSubscriberSpanExpirationResult(); + } + + void setSubscriberSpanException(PubsubMessageWrapper message, Throwable t, String exception) { + if (!enabled) { + return; + } + message.setSubscriberSpanException(t, exception); + } + + /** Creates a span for subscribe concurrency control as a child of the parent subscriber span. */ + void startSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span subscriberSpan = message.getSubscriberSpan(); + if (subscriberSpan != null) { + message.setSubscribeConcurrencyControlSpan( + startChildSpan(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME, subscriberSpan)); + } + } + + void endSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endSubscribeConcurrencyControlSpan(); + } + + void setSubscribeConcurrencyControlSpanException(PubsubMessageWrapper message, Throwable t) { + if (!enabled) { + return; + } + message.setSubscribeConcurrencyControlSpanException(t); + } + + /** + * Creates a span for subscribe ordering key scheduling as a child of the parent subscriber span. + */ + void startSubscribeSchedulerSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span subscriberSpan = message.getSubscriberSpan(); + if (subscriberSpan != null) { + message.setSubscribeSchedulerSpan( + startChildSpan(SUBSCRIBE_SCHEDULER_SPAN_NAME, subscriberSpan)); + } + } + + void endSubscribeSchedulerSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endSubscribeSchedulerSpan(); + } + + /** Creates a span for subscribe message processing as a child of the parent subscriber span. */ + void startSubscribeProcessSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span subscriberSpan = message.getSubscriberSpan(); + if (subscriberSpan != null) { + Span subscribeProcessSpan = + startChildSpan(message.getSubscriptionName() + " process", subscriberSpan); + subscribeProcessSpan.setAttribute( + SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE); + Span publisherSpan = message.getPublisherSpan(); + if (publisherSpan != null) { + subscribeProcessSpan.addLink(publisherSpan.getSpanContext()); + } + message.setSubscribeProcessSpan(subscribeProcessSpan); + } + } + + void endSubscribeProcessSpan(PubsubMessageWrapper message, String action) { + if (!enabled) { + return; + } + message.endSubscribeProcessSpan(action); + } + + /** + * Creates, starts, and returns spans for ModAck, Nack, and Ack RPC requests. Bi-directional links + * to parent subscribe span for sampled messages are added. + */ + Span startSubscribeRpcSpan( + String subscription, + String rpcOperation, + List messages, + int ackDeadline, + boolean isReceiptModack) { + if (!enabled) { + return null; + } + String codeFunction = rpcOperation == "ack" ? "sendAckOperations" : "sendModAckOperations"; + SubscriptionName subscriptionName = SubscriptionName.parse(subscription); + AttributesBuilder attributesBuilder = + createCommonSpanAttributesBuilder( + subscriptionName.getSubscription(), + subscriptionName.getProject(), + codeFunction, + rpcOperation) + .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()); + + // Ack deadline and receipt modack are specific to the modack operation + if (rpcOperation == "modack") { + attributesBuilder + .put(ACK_DEADLINE_ATTR_KEY, ackDeadline) + .put(RECEIPT_MODACK_ATTR_KEY, isReceiptModack); + } + + SpanBuilder rpcSpanBuilder = + tracer + .spanBuilder(subscriptionName.getSubscription() + " " + rpcOperation) + .setSpanKind(SpanKind.CLIENT) + .setAllAttributes(attributesBuilder.build()); + Attributes linkAttributes = + Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, rpcOperation).build(); + for (PubsubMessageWrapper message : messages) { + if (message.getSubscriberSpan().getSpanContext().isSampled()) { + rpcSpanBuilder.addLink(message.getSubscriberSpan().getSpanContext(), linkAttributes); + } + } + Span rpcSpan = rpcSpanBuilder.startSpan(); + + for (PubsubMessageWrapper message : messages) { + if (rpcSpan.getSpanContext().isSampled()) { + message.getSubscriberSpan().addLink(rpcSpan.getSpanContext(), linkAttributes); + switch (rpcOperation) { + case "ack": + message.addAckStartEvent(); + break; + case "modack": + message.addModAckStartEvent(); + break; + case "nack": + message.addNackStartEvent(); + break; + } + } + } + return rpcSpan; + } + + /** Ends the given subscribe RPC span if it exists. */ + void endSubscribeRpcSpan(Span rpcSpan) { + if (!enabled) { + return; + } + if (rpcSpan != null) { + rpcSpan.end(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown when handling a + * subscribe-side RPC. + */ + void setSubscribeRpcSpanException(Span rpcSpan, boolean isModack, int ackDeadline, Throwable t) { + if (!enabled) { + return; + } + if (rpcSpan != null) { + String operation = !isModack ? "ack" : (ackDeadline == 0 ? "nack" : "modack"); + rpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on " + operation + " RPC."); + rpcSpan.recordException(t); + rpcSpan.end(); + } + } + + /** Adds the appropriate subscribe-side RPC end event. */ + void addEndRpcEvent( + PubsubMessageWrapper message, boolean rpcSampled, boolean isModack, int ackDeadline) { + if (!enabled || !rpcSampled) { + return; + } + if (!isModack) { + message.addAckEndEvent(); + } else if (ackDeadline == 0) { + message.addNackEndEvent(); + } else { + message.addModAckEndEvent(); + } + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java new file mode 100644 index 000000000..94fd13085 --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java @@ -0,0 +1,430 @@ +/* + * 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.pubsub.v1; + +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.SubscriptionName; +import com.google.pubsub.v1.TopicName; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.context.propagation.TextMapSetter; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; + +/** + * A wrapper class for a {@link PubsubMessage} object that handles creation and tracking of + * OpenTelemetry {@link Span} objects for different operations that occur during publishing. + */ +public class PubsubMessageWrapper { + private PubsubMessage message; + + private final TopicName topicName; + private final SubscriptionName subscriptionName; + + // Attributes set only for messages received from a streaming pull response. + private final String ackId; + private final int deliveryAttempt; + + private static final String PUBLISH_START_EVENT = "publish start"; + private static final String PUBLISH_END_EVENT = "publish end"; + + private static final String MODACK_START_EVENT = "modack start"; + private static final String MODACK_END_EVENT = "modack end"; + private static final String NACK_START_EVENT = "nack start"; + private static final String NACK_END_EVENT = "nack end"; + private static final String ACK_START_EVENT = "ack start"; + private static final String ACK_END_EVENT = "ack end"; + + private static final String GOOGCLIENT_PREFIX = "googclient_"; + + private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; + + private Span publisherSpan; + private Span publishFlowControlSpan; + private Span publishBatchingSpan; + + private Span subscriberSpan; + private Span subscribeConcurrencyControlSpan; + private Span subscribeSchedulerSpan; + private Span subscribeProcessSpan; + + private PubsubMessageWrapper(Builder builder) { + this.message = builder.message; + this.topicName = builder.topicName; + this.subscriptionName = builder.subscriptionName; + this.ackId = builder.ackId; + this.deliveryAttempt = builder.deliveryAttempt; + } + + static Builder newBuilder(PubsubMessage message, String topicName) { + return new Builder(message, topicName); + } + + static Builder newBuilder( + PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { + return new Builder(message, subscriptionName, ackId, deliveryAttempt); + } + + /** Returns the PubsubMessage associated with this wrapper. */ + PubsubMessage getPubsubMessage() { + return message; + } + + void setPubsubMessage(PubsubMessage message) { + this.message = message; + } + + /** Returns the TopicName for this wrapper as a string. */ + String getTopicName() { + if (topicName != null) { + return topicName.getTopic(); + } + return ""; + } + + String getTopicProject() { + if (topicName != null) { + return topicName.getProject(); + } + return ""; + } + + /** Returns the SubscriptionName for this wrapper as a string. */ + String getSubscriptionName() { + if (subscriptionName != null) { + return subscriptionName.getSubscription(); + } + return ""; + } + + String getSubscriptionProject() { + if (subscriptionName != null) { + return subscriptionName.getProject(); + } + return ""; + } + + String getMessageId() { + return message.getMessageId(); + } + + String getAckId() { + return ackId; + } + + int getDataSize() { + return message.getData().size(); + } + + String getOrderingKey() { + return message.getOrderingKey(); + } + + int getDeliveryAttempt() { + return deliveryAttempt; + } + + Span getPublisherSpan() { + return publisherSpan; + } + + void setPublisherSpan(Span span) { + this.publisherSpan = span; + } + + void setPublishFlowControlSpan(Span span) { + this.publishFlowControlSpan = span; + } + + void setPublishBatchingSpan(Span span) { + this.publishBatchingSpan = span; + } + + Span getSubscriberSpan() { + return subscriberSpan; + } + + void setSubscriberSpan(Span span) { + this.subscriberSpan = span; + } + + void setSubscribeConcurrencyControlSpan(Span span) { + this.subscribeConcurrencyControlSpan = span; + } + + void setSubscribeSchedulerSpan(Span span) { + this.subscribeSchedulerSpan = span; + } + + void setSubscribeProcessSpan(Span span) { + this.subscribeProcessSpan = span; + } + + /** Creates a publish start event that is tied to the publish RPC span time. */ + void addPublishStartEvent() { + if (publisherSpan != null) { + publisherSpan.addEvent(PUBLISH_START_EVENT); + } + } + + /** + * Sets the message ID attribute in the publisher parent span. This is called after the publish + * RPC returns with a message ID. + */ + void setPublisherMessageIdSpanAttribute(String messageId) { + if (publisherSpan != null) { + publisherSpan.setAttribute(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId); + } + } + + /** Ends the publisher parent span if it exists. */ + void endPublisherSpan() { + if (publisherSpan != null) { + publisherSpan.addEvent(PUBLISH_END_EVENT); + publisherSpan.end(); + } + } + + /** Ends the publish flow control span if it exists. */ + void endPublishFlowControlSpan() { + if (publishFlowControlSpan != null) { + publishFlowControlSpan.end(); + } + } + + /** Ends the publish batching span if it exists. */ + void endPublishBatchingSpan() { + if (publishBatchingSpan != null) { + publishBatchingSpan.end(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown during flow control. + */ + void setPublishFlowControlSpanException(Throwable t) { + if (publishFlowControlSpan != null) { + publishFlowControlSpan.setStatus( + StatusCode.ERROR, "Exception thrown during publish flow control."); + publishFlowControlSpan.recordException(t); + endAllPublishSpans(); + } + } + + /** + * Creates start and end events for ModAcks, Nacks, and Acks that are tied to the corresponding + * RPC span start and end times. + */ + void addModAckStartEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(MODACK_START_EVENT); + } + } + + void addModAckEndEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(MODACK_END_EVENT); + } + } + + void addNackStartEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(NACK_START_EVENT); + } + } + + void addNackEndEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(NACK_END_EVENT); + } + } + + void addAckStartEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(ACK_START_EVENT); + } + } + + void addAckEndEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(ACK_END_EVENT); + } + } + + /** Ends the subscriber parent span if exists. */ + void endSubscriberSpan() { + if (subscriberSpan != null) { + subscriberSpan.end(); + } + } + + /** Ends the subscribe concurreny control span if exists. */ + void endSubscribeConcurrencyControlSpan() { + if (subscribeConcurrencyControlSpan != null) { + subscribeConcurrencyControlSpan.end(); + } + } + + /** Ends the subscribe scheduler span if exists. */ + void endSubscribeSchedulerSpan() { + if (subscribeSchedulerSpan != null) { + subscribeSchedulerSpan.end(); + } + } + + /** + * Ends the subscribe process span if it exists, creates an event with the appropriate result, and + * sets the result on the parent subscriber span. + */ + void endSubscribeProcessSpan(String action) { + if (subscribeProcessSpan != null) { + subscribeProcessSpan.addEvent(action + " called"); + subscribeProcessSpan.end(); + subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, action); + } + } + + /** Sets an exception on the subscriber span during Ack/ModAck/Nack failures */ + void setSubscriberSpanException(Throwable t, String exception) { + if (subscriberSpan != null) { + subscriberSpan.setStatus(StatusCode.ERROR, exception); + subscriberSpan.recordException(t); + endAllSubscribeSpans(); + } + } + + /** Sets result of the parent subscriber span to expired and ends its. */ + void setSubscriberSpanExpirationResult() { + if (subscriberSpan != null) { + subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, "expired"); + endSubscriberSpan(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown subscriber + * concurrency control. + */ + void setSubscribeConcurrencyControlSpanException(Throwable t) { + if (subscribeConcurrencyControlSpan != null) { + subscribeConcurrencyControlSpan.setStatus( + StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); + subscribeConcurrencyControlSpan.recordException(t); + endAllSubscribeSpans(); + } + } + + /** Ends all publisher-side spans associated with this message wrapper. */ + private void endAllPublishSpans() { + endPublishFlowControlSpan(); + endPublishBatchingSpan(); + endPublisherSpan(); + } + + /** Ends all subscriber-side spans associated with this message wrapper. */ + private void endAllSubscribeSpans() { + endSubscribeConcurrencyControlSpan(); + endSubscribeSchedulerSpan(); + endSubscriberSpan(); + } + + /** + * Injects the span context into the attributes of a Pub/Sub message for propagation to the + * subscriber client. + */ + void injectSpanContext() { + TextMapSetter injectMessageAttributes = + new TextMapSetter() { + @Override + public void set(PubsubMessageWrapper carrier, String key, String value) { + PubsubMessage newMessage = + PubsubMessage.newBuilder(carrier.message) + .putAttributes(GOOGCLIENT_PREFIX + key, value) + .build(); + carrier.message = newMessage; + } + }; + W3CTraceContextPropagator.getInstance() + .inject(Context.current().with(publisherSpan), this, injectMessageAttributes); + } + + /** + * Extracts the span context from the attributes of a Pub/Sub message and creates the parent + * subscriber span using that context. + */ + Context extractSpanContext(Attributes attributes) { + TextMapGetter extractMessageAttributes = + new TextMapGetter() { + @Override + public String get(PubsubMessageWrapper carrier, String key) { + return carrier.message.getAttributesOrDefault(GOOGCLIENT_PREFIX + key, ""); + } + + public Iterable keys(PubsubMessageWrapper carrier) { + return carrier.message.getAttributesMap().keySet(); + } + }; + Context context = + W3CTraceContextPropagator.getInstance() + .extract(Context.current(), this, extractMessageAttributes); + return context; + } + + /** Builder of {@link PubsubMessageWrapper PubsubMessageWrapper}. */ + static final class Builder { + private PubsubMessage message = null; + private TopicName topicName = null; + private SubscriptionName subscriptionName = null; + private String ackId = null; + private int deliveryAttempt = 0; + + public Builder(PubsubMessage message, String topicName) { + this.message = message; + if (topicName != null) { + this.topicName = TopicName.parse(topicName); + } + } + + public Builder( + PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { + this.message = message; + if (subscriptionName != null) { + this.subscriptionName = SubscriptionName.parse(subscriptionName); + } + this.ackId = ackId; + this.deliveryAttempt = deliveryAttempt; + } + + public Builder( + PubsubMessage message, + SubscriptionName subscriptionName, + String ackId, + int deliveryAttempt) { + this.message = message; + this.subscriptionName = subscriptionName; + this.ackId = ackId; + this.deliveryAttempt = deliveryAttempt; + } + + public PubsubMessageWrapper build() { + return new PubsubMessageWrapper(this); + } + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java new file mode 100644 index 000000000..b4433f41e --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -0,0 +1,669 @@ +/* + * 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.pubsub.v1; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.google.protobuf.ByteString; +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.SubscriptionName; +import com.google.pubsub.v1.TopicName; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.sdk.testing.assertj.AttributesAssert; +import io.opentelemetry.sdk.testing.assertj.EventDataAssert; +import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.testing.junit4.OpenTelemetryRule; +import io.opentelemetry.sdk.trace.data.LinkData; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.data.StatusData; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.Arrays; +import java.util.List; +import org.junit.Test; + +public class OpenTelemetryTest { + private static final TopicName FULL_TOPIC_NAME = + TopicName.parse("projects/test-project/topics/test-topic"); + private static final SubscriptionName FULL_SUBSCRIPTION_NAME = + SubscriptionName.parse("projects/test-project/subscriptions/test-sub"); + private static final String PROJECT_NAME = "test-project"; + private static final String ORDERING_KEY = "abc"; + private static final String MESSAGE_ID = "m0"; + private static final String ACK_ID = "def"; + private static final int DELIVERY_ATTEMPT = 1; + private static final int ACK_DEADLINE = 10; + private static final boolean EXACTLY_ONCE_ENABLED = true; + + private static final String PUBLISHER_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " create"; + private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; + private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; + private static final String PUBLISH_RPC_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " publish"; + private static final String PUBLISH_START_EVENT = "publish start"; + private static final String PUBLISH_END_EVENT = "publish end"; + + private static final String SUBSCRIBER_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " subscribe"; + private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = + "subscriber concurrency control"; + private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; + private static final String SUBSCRIBE_PROCESS_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " process"; + private static final String SUBSCRIBE_MODACK_RPC_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " modack"; + private static final String SUBSCRIBE_ACK_RPC_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " ack"; + private static final String SUBSCRIBE_NACK_RPC_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " nack"; + + private static final String PROCESS_ACTION = "ack"; + private static final String MODACK_START_EVENT = "modack start"; + private static final String MODACK_END_EVENT = "modack end"; + private static final String NACK_START_EVENT = "nack start"; + private static final String NACK_END_EVENT = "nack end"; + private static final String ACK_START_EVENT = "ack start"; + private static final String ACK_END_EVENT = "ack end"; + + private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; + private static final String PROJECT_ATTR_KEY = "gcp.project_id"; + private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; + private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; + private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; + private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; + private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; + private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = + "messaging.gcp_pubsub.message.exactly_once_delivery"; + private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; + private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = + "messaging.gcp_pubsub.message.delivery_attempt"; + + private static final String TRACEPARENT_ATTRIBUTE = "googclient_traceparent"; + + private static final OpenTelemetryRule openTelemetryTesting = OpenTelemetryRule.create(); + + @Test + public void testPublishSpansSuccess() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); + List messageWrappers = Arrays.asList(messageWrapper); + + long messageSize = messageWrapper.getPubsubMessage().getData().size(); + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + // Call all span start/end methods in the expected order + tracer.startPublisherSpan(messageWrapper); + tracer.startPublishFlowControlSpan(messageWrapper); + tracer.endPublishFlowControlSpan(messageWrapper); + tracer.startPublishBatchingSpan(messageWrapper); + tracer.endPublishBatchingSpan(messageWrapper); + Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); + tracer.endPublishRpcSpan(publishRpcSpan); + tracer.setPublisherMessageIdSpanAttribute(messageWrapper, MESSAGE_ID); + tracer.endPublisherSpan(messageWrapper); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(4, allSpans.size()); + SpanData flowControlSpanData = allSpans.get(0); + SpanData batchingSpanData = allSpans.get(1); + SpanData publishRpcSpanData = allSpans.get(2); + SpanData publisherSpanData = allSpans.get(3); + + SpanDataAssert flowControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(flowControlSpanData); + flowControlSpanDataAssert + .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) + .hasParent(publisherSpanData) + .hasEnded(); + + SpanDataAssert batchingSpanDataAssert = OpenTelemetryAssertions.assertThat(batchingSpanData); + batchingSpanDataAssert + .hasName(PUBLISH_BATCHING_SPAN_NAME) + .hasParent(publisherSpanData) + .hasEnded(); + + // Check span data, links, and attributes for the publish RPC span + SpanDataAssert publishRpcSpanDataAssert = + OpenTelemetryAssertions.assertThat(publishRpcSpanData); + publishRpcSpanDataAssert + .hasName(PUBLISH_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List publishRpcLinks = publishRpcSpanData.getLinks(); + assertEquals(messageWrappers.size(), publishRpcLinks.size()); + assertEquals(publisherSpanData.getSpanContext(), publishRpcLinks.get(0).getSpanContext()); + + assertEquals(6, publishRpcSpanData.getAttributes().size()); + AttributesAssert publishRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(publishRpcSpanData.getAttributes()); + publishRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "publishCall") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "publish") + .containsEntry(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messageWrappers.size()); + + // Check span data, events, links, and attributes for the publisher create span + SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publisherSpanDataAssert + .hasName(PUBLISHER_SPAN_NAME) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasEnded(); + + assertEquals(2, publisherSpanData.getEvents().size()); + EventDataAssert startEventAssert = + OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(0)); + startEventAssert.hasName(PUBLISH_START_EVENT); + EventDataAssert endEventAssert = + OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(1)); + endEventAssert.hasName(PUBLISH_END_EVENT); + + List publisherLinks = publisherSpanData.getLinks(); + assertEquals(1, publisherLinks.size()); + assertEquals(publishRpcSpanData.getSpanContext(), publisherLinks.get(0).getSpanContext()); + + assertEquals(8, publisherSpanData.getAttributes().size()); + AttributesAssert publisherSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(publisherSpanData.getAttributes()); + publisherSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) + .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "publish") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "create") + .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) + .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) + .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); + + // Check that the message has the attribute containing the trace context. + PubsubMessage message = messageWrapper.getPubsubMessage(); + assertEquals(1, message.getAttributesMap().size()); + assertTrue(message.containsAttributes(TRACEPARENT_ATTRIBUTE)); + assertTrue( + message + .getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "") + .contains(publisherSpanData.getTraceId())); + assertTrue( + message + .getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "") + .contains(publisherSpanData.getSpanId())); + } + + @Test + public void testPublishFlowControlSpanFailure() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startPublisherSpan(messageWrapper); + tracer.startPublishFlowControlSpan(messageWrapper); + + Exception e = new Exception("test-exception"); + tracer.setPublishFlowControlSpanException(messageWrapper, e); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(2, allSpans.size()); + SpanData flowControlSpanData = allSpans.get(0); + SpanData publisherSpanData = allSpans.get(1); + + SpanDataAssert flowControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(flowControlSpanData); + StatusData expectedStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown during publish flow control."); + flowControlSpanDataAssert + .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) + .hasParent(publisherSpanData) + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + + SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publisherSpanDataAssert + .hasName(PUBLISHER_SPAN_NAME) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasEnded(); + } + + @Test + public void testPublishRpcSpanFailure() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); + + List messageWrappers = Arrays.asList(messageWrapper); + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startPublisherSpan(messageWrapper); + Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); + + Exception e = new Exception("test-exception"); + tracer.setPublishRpcSpanException(publishRpcSpan, e); + tracer.endPublisherSpan(messageWrapper); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(2, allSpans.size()); + SpanData rpcSpanData = allSpans.get(0); + SpanData publisherSpanData = allSpans.get(1); + + SpanDataAssert rpcSpanDataAssert = OpenTelemetryAssertions.assertThat(rpcSpanData); + StatusData expectedStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on publish RPC."); + rpcSpanDataAssert + .hasName(PUBLISH_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + + SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publisherSpanDataAssert + .hasName(PUBLISHER_SPAN_NAME) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasEnded(); + } + + @Test + public void testSubscribeSpansSuccess() { + openTelemetryTesting.clearSpans(); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + PubsubMessageWrapper publishMessageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); + // Initialize the Publisher span to inject the context in the message + tracer.startPublisherSpan(publishMessageWrapper); + tracer.endPublisherSpan(publishMessageWrapper); + + PubsubMessage publishedMessage = + publishMessageWrapper.getPubsubMessage().toBuilder().setMessageId(MESSAGE_ID).build(); + PubsubMessageWrapper subscribeMessageWrapper = + PubsubMessageWrapper.newBuilder( + publishedMessage, FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, 1) + .build(); + List subscribeMessageWrappers = Arrays.asList(subscribeMessageWrapper); + + long messageSize = subscribeMessageWrapper.getPubsubMessage().getData().size(); + + // Call all span start/end methods in the expected order + tracer.startSubscriberSpan(subscribeMessageWrapper, EXACTLY_ONCE_ENABLED); + tracer.startSubscribeConcurrencyControlSpan(subscribeMessageWrapper); + tracer.endSubscribeConcurrencyControlSpan(subscribeMessageWrapper); + tracer.startSubscribeSchedulerSpan(subscribeMessageWrapper); + tracer.endSubscribeSchedulerSpan(subscribeMessageWrapper); + tracer.startSubscribeProcessSpan(subscribeMessageWrapper); + tracer.endSubscribeProcessSpan(subscribeMessageWrapper, PROCESS_ACTION); + Span subscribeModackRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), + "modack", + subscribeMessageWrappers, + ACK_DEADLINE, + true); + tracer.endSubscribeRpcSpan(subscribeModackRpcSpan); + tracer.addEndRpcEvent(subscribeMessageWrapper, true, true, ACK_DEADLINE); + Span subscribeAckRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "ack", subscribeMessageWrappers, 0, false); + tracer.endSubscribeRpcSpan(subscribeAckRpcSpan); + tracer.addEndRpcEvent(subscribeMessageWrapper, true, false, 0); + Span subscribeNackRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "nack", subscribeMessageWrappers, 0, false); + tracer.endSubscribeRpcSpan(subscribeNackRpcSpan); + tracer.addEndRpcEvent(subscribeMessageWrapper, true, true, 0); + tracer.endSubscriberSpan(subscribeMessageWrapper); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(8, allSpans.size()); + + SpanData publisherSpanData = allSpans.get(0); + SpanData concurrencyControlSpanData = allSpans.get(1); + SpanData schedulerSpanData = allSpans.get(2); + SpanData processSpanData = allSpans.get(3); + SpanData modackRpcSpanData = allSpans.get(4); + SpanData ackRpcSpanData = allSpans.get(5); + SpanData nackRpcSpanData = allSpans.get(6); + SpanData subscriberSpanData = allSpans.get(7); + + SpanDataAssert concurrencyControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(concurrencyControlSpanData); + concurrencyControlSpanDataAssert + .hasName(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasEnded(); + + SpanDataAssert schedulerSpanDataAssert = OpenTelemetryAssertions.assertThat(schedulerSpanData); + schedulerSpanDataAssert + .hasName(SUBSCRIBE_SCHEDULER_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasEnded(); + + SpanDataAssert processSpanDataAssert = OpenTelemetryAssertions.assertThat(processSpanData); + processSpanDataAssert + .hasName(SUBSCRIBE_PROCESS_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasEnded(); + + assertEquals(1, processSpanData.getEvents().size()); + EventDataAssert actionCalledEventAssert = + OpenTelemetryAssertions.assertThat(processSpanData.getEvents().get(0)); + actionCalledEventAssert.hasName(PROCESS_ACTION + " called"); + + // Check span data, links, and attributes for the modack RPC span + SpanDataAssert modackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(modackRpcSpanData); + modackRpcSpanDataAssert + .hasName(SUBSCRIBE_MODACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List modackRpcLinks = modackRpcSpanData.getLinks(); + assertEquals(subscribeMessageWrappers.size(), modackRpcLinks.size()); + assertEquals(subscriberSpanData.getSpanContext(), modackRpcLinks.get(0).getSpanContext()); + + assertEquals(8, modackRpcSpanData.getAttributes().size()); + AttributesAssert modackRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(modackRpcSpanData.getAttributes()); + modackRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "modack") + .containsEntry( + SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()) + .containsEntry(ACK_DEADLINE_ATTR_KEY, 10) + .containsEntry(RECEIPT_MODACK_ATTR_KEY, true); + + // Check span data, links, and attributes for the ack RPC span + SpanDataAssert ackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(ackRpcSpanData); + ackRpcSpanDataAssert + .hasName(SUBSCRIBE_ACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List ackRpcLinks = ackRpcSpanData.getLinks(); + assertEquals(subscribeMessageWrappers.size(), ackRpcLinks.size()); + assertEquals(subscriberSpanData.getSpanContext(), ackRpcLinks.get(0).getSpanContext()); + + assertEquals(6, ackRpcSpanData.getAttributes().size()); + AttributesAssert ackRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(ackRpcSpanData.getAttributes()); + ackRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendAckOperations") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "ack") + .containsEntry( + SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); + + // Check span data, links, and attributes for the nack RPC span + SpanDataAssert nackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(nackRpcSpanData); + nackRpcSpanDataAssert + .hasName(SUBSCRIBE_NACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List nackRpcLinks = nackRpcSpanData.getLinks(); + assertEquals(subscribeMessageWrappers.size(), nackRpcLinks.size()); + assertEquals(subscriberSpanData.getSpanContext(), nackRpcLinks.get(0).getSpanContext()); + + assertEquals(6, nackRpcSpanData.getAttributes().size()); + AttributesAssert nackRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(nackRpcSpanData.getAttributes()); + nackRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "nack") + .containsEntry( + SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); + + // Check span data, events, links, and attributes for the publisher create span + SpanDataAssert subscriberSpanDataAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData); + subscriberSpanDataAssert + .hasName(SUBSCRIBER_SPAN_NAME) + .hasKind(SpanKind.CONSUMER) + .hasParent(publisherSpanData) + .hasEnded(); + + assertEquals(6, subscriberSpanData.getEvents().size()); + EventDataAssert startModackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(0)); + startModackEventAssert.hasName(MODACK_START_EVENT); + EventDataAssert endModackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(1)); + endModackEventAssert.hasName(MODACK_END_EVENT); + EventDataAssert startAckEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(2)); + startAckEventAssert.hasName(ACK_START_EVENT); + EventDataAssert endAckEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(3)); + endAckEventAssert.hasName(ACK_END_EVENT); + EventDataAssert startNackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(4)); + startNackEventAssert.hasName(NACK_START_EVENT); + EventDataAssert endNackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(5)); + endNackEventAssert.hasName(NACK_END_EVENT); + + List subscriberLinks = subscriberSpanData.getLinks(); + assertEquals(3, subscriberLinks.size()); + assertEquals(modackRpcSpanData.getSpanContext(), subscriberLinks.get(0).getSpanContext()); + assertEquals(ackRpcSpanData.getSpanContext(), subscriberLinks.get(1).getSpanContext()); + assertEquals(nackRpcSpanData.getSpanContext(), subscriberLinks.get(2).getSpanContext()); + + assertEquals(11, subscriberSpanData.getAttributes().size()); + AttributesAssert subscriberSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getAttributes()); + subscriberSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "onResponse") + .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) + .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) + .containsEntry(MESSAGE_ACK_ID_ATTR_KEY, ACK_ID) + .containsEntry(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, DELIVERY_ATTEMPT) + .containsEntry(MESSAGE_EXACTLY_ONCE_ATTR_KEY, EXACTLY_ONCE_ENABLED) + .containsEntry(MESSAGE_RESULT_ATTR_KEY, PROCESS_ACTION) + .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); + } + + @Test + public void testSubscribeConcurrencyControlSpanFailure() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder( + getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) + .build(); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); + tracer.startSubscribeConcurrencyControlSpan(messageWrapper); + + Exception e = new Exception("test-exception"); + tracer.setSubscribeConcurrencyControlSpanException(messageWrapper, e); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(2, allSpans.size()); + SpanData concurrencyControlSpanData = allSpans.get(0); + SpanData subscriberSpanData = allSpans.get(1); + + SpanDataAssert concurrencyControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(concurrencyControlSpanData); + StatusData expectedStatus = + StatusData.create( + StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); + concurrencyControlSpanDataAssert + .hasName(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + + SpanDataAssert subscriberSpanDataAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData); + subscriberSpanDataAssert + .hasName(SUBSCRIBER_SPAN_NAME) + .hasKind(SpanKind.CONSUMER) + .hasNoParent() + .hasEnded(); + } + + @Test + public void testSubscriberSpanFailure() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder( + getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) + .build(); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); + + Exception e = new Exception("test-exception"); + tracer.setSubscriberSpanException(messageWrapper, e, "Test exception"); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(1, allSpans.size()); + SpanData subscriberSpanData = allSpans.get(0); + + StatusData expectedStatus = StatusData.create(StatusCode.ERROR, "Test exception"); + SpanDataAssert subscriberSpanDataAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData); + subscriberSpanDataAssert + .hasName(SUBSCRIBER_SPAN_NAME) + .hasKind(SpanKind.CONSUMER) + .hasNoParent() + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + } + + @Test + public void testSubscribeRpcSpanFailures() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder( + getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) + .build(); + List messageWrappers = Arrays.asList(messageWrapper); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); + Span subscribeModackRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "modack", messageWrappers, ACK_DEADLINE, true); + Span subscribeAckRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "ack", messageWrappers, 0, false); + Span subscribeNackRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "nack", messageWrappers, 0, false); + + Exception e = new Exception("test-exception"); + tracer.setSubscribeRpcSpanException(subscribeModackRpcSpan, true, ACK_DEADLINE, e); + tracer.setSubscribeRpcSpanException(subscribeAckRpcSpan, false, 0, e); + tracer.setSubscribeRpcSpanException(subscribeNackRpcSpan, true, 0, e); + tracer.endSubscriberSpan(messageWrapper); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(4, allSpans.size()); + SpanData modackSpanData = allSpans.get(0); + SpanData ackSpanData = allSpans.get(1); + SpanData nackSpanData = allSpans.get(2); + SpanData subscriberSpanData = allSpans.get(3); + + StatusData expectedModackStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on modack RPC."); + SpanDataAssert modackSpanDataAssert = OpenTelemetryAssertions.assertThat(modackSpanData); + modackSpanDataAssert + .hasName(SUBSCRIBE_MODACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasStatus(expectedModackStatus) + .hasException(e) + .hasEnded(); + + StatusData expectedAckStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on ack RPC."); + SpanDataAssert ackSpanDataAssert = OpenTelemetryAssertions.assertThat(ackSpanData); + ackSpanDataAssert + .hasName(SUBSCRIBE_ACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasStatus(expectedAckStatus) + .hasException(e) + .hasEnded(); + + StatusData expectedNackStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on nack RPC."); + SpanDataAssert nackSpanDataAssert = OpenTelemetryAssertions.assertThat(nackSpanData); + nackSpanDataAssert + .hasName(SUBSCRIBE_NACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasStatus(expectedNackStatus) + .hasException(e) + .hasEnded(); + } + + private PubsubMessage getPubsubMessage() { + return PubsubMessage.newBuilder() + .setData(ByteString.copyFromUtf8("test-data")) + .setOrderingKey(ORDERING_KEY) + .build(); + } +} From d00b4aaece3f27a0a259a5654abbfff0e13bf1b3 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Mon, 30 Sep 2024 21:16:59 +0000 Subject: [PATCH 42/46] chore: generate libraries at Mon Sep 30 21:14:11 UTC 2024 --- .../pubsub/v1/OpenTelemetryPubsubTracer.java | 460 ------------ .../cloud/pubsub/v1/PubsubMessageWrapper.java | 430 ----------- .../cloud/pubsub/v1/OpenTelemetryTest.java | 669 ------------------ 3 files changed, 1559 deletions(-) delete mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java delete mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java delete mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java deleted file mode 100644 index b946f44bf..000000000 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java +++ /dev/null @@ -1,460 +0,0 @@ -/* - * 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.pubsub.v1; - -import com.google.pubsub.v1.PubsubMessage; -import com.google.pubsub.v1.SubscriptionName; -import com.google.pubsub.v1.TopicName; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanBuilder; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.context.Context; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.util.List; - -public class OpenTelemetryPubsubTracer { - private final Tracer tracer; - private boolean enabled = false; - - private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; - private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; - private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = - "subscriber concurrency control"; - private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; - - private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; - private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; - private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; - private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = - "messaging.gcp_pubsub.message.exactly_once_delivery"; - private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = - "messaging.gcp_pubsub.message.delivery_attempt"; - private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; - private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; - private static final String PROJECT_ATTR_KEY = "gcp.project_id"; - private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; - - private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; - - OpenTelemetryPubsubTracer(Tracer tracer, boolean enableOpenTelemetry) { - this.tracer = tracer; - if (this.tracer != null && enableOpenTelemetry) { - this.enabled = true; - } - } - - /** Populates attributes that are common the publisher parent span and publish RPC span. */ - private static final AttributesBuilder createCommonSpanAttributesBuilder( - String destinationName, String projectName, String codeFunction, String operation) { - AttributesBuilder attributesBuilder = - Attributes.builder() - .put(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .put(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName) - .put(PROJECT_ATTR_KEY, projectName) - .put(SemanticAttributes.CODE_FUNCTION, codeFunction); - if (operation != null) { - attributesBuilder.put(SemanticAttributes.MESSAGING_OPERATION, operation); - } - - return attributesBuilder; - } - - private Span startChildSpan(String name, Span parent) { - return tracer.spanBuilder(name).setParent(Context.current().with(parent)).startSpan(); - } - - /** - * Creates and starts the parent span with the appropriate span attributes and injects the span - * context into the {@link PubsubMessage} attributes. - */ - void startPublisherSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - AttributesBuilder attributesBuilder = - createCommonSpanAttributesBuilder( - message.getTopicName(), message.getTopicProject(), "publish", "create"); - - attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()); - if (!message.getOrderingKey().isEmpty()) { - attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); - } - - Span publisherSpan = - tracer - .spanBuilder(message.getTopicName() + " create") - .setSpanKind(SpanKind.PRODUCER) - .setAllAttributes(attributesBuilder.build()) - .startSpan(); - - message.setPublisherSpan(publisherSpan); - if (publisherSpan.getSpanContext().isValid()) { - message.injectSpanContext(); - } - } - - void endPublisherSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endPublisherSpan(); - } - - void setPublisherMessageIdSpanAttribute(PubsubMessageWrapper message, String messageId) { - if (!enabled) { - return; - } - message.setPublisherMessageIdSpanAttribute(messageId); - } - - /** Creates a span for publish-side flow control as a child of the parent publisher span. */ - void startPublishFlowControlSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span publisherSpan = message.getPublisherSpan(); - if (publisherSpan != null) - message.setPublishFlowControlSpan( - startChildSpan(PUBLISH_FLOW_CONTROL_SPAN_NAME, publisherSpan)); - } - - void endPublishFlowControlSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endPublishFlowControlSpan(); - } - - void setPublishFlowControlSpanException(PubsubMessageWrapper message, Throwable t) { - if (!enabled) { - return; - } - message.setPublishFlowControlSpanException(t); - } - - /** Creates a span for publish message batching as a child of the parent publisher span. */ - void startPublishBatchingSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span publisherSpan = message.getPublisherSpan(); - if (publisherSpan != null) { - message.setPublishBatchingSpan(startChildSpan(PUBLISH_BATCHING_SPAN_NAME, publisherSpan)); - } - } - - void endPublishBatchingSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endPublishBatchingSpan(); - } - - /** - * Creates, starts, and returns a publish RPC span for the given message batch. Bi-directional - * links with the publisher parent span are created for sampled messages in the batch. - */ - Span startPublishRpcSpan(String topic, List messages) { - if (!enabled) { - return null; - } - TopicName topicName = TopicName.parse(topic); - Attributes attributes = - createCommonSpanAttributesBuilder( - topicName.getTopic(), topicName.getProject(), "publishCall", "publish") - .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()) - .build(); - SpanBuilder publishRpcSpanBuilder = - tracer - .spanBuilder(topicName.getTopic() + PUBLISH_RPC_SPAN_SUFFIX) - .setSpanKind(SpanKind.CLIENT) - .setAllAttributes(attributes); - Attributes linkAttributes = - Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, "publish").build(); - for (PubsubMessageWrapper message : messages) { - if (message.getPublisherSpan().getSpanContext().isSampled()) - publishRpcSpanBuilder.addLink(message.getPublisherSpan().getSpanContext(), linkAttributes); - } - Span publishRpcSpan = publishRpcSpanBuilder.startSpan(); - - for (PubsubMessageWrapper message : messages) { - if (publishRpcSpan.getSpanContext().isSampled()) { - message.getPublisherSpan().addLink(publishRpcSpan.getSpanContext(), linkAttributes); - message.addPublishStartEvent(); - } - } - return publishRpcSpan; - } - - /** Ends the given publish RPC span if it exists. */ - void endPublishRpcSpan(Span publishRpcSpan) { - if (!enabled) { - return; - } - if (publishRpcSpan != null) { - publishRpcSpan.end(); - } - } - - /** - * Sets an error status and records an exception when an exception is thrown when publishing the - * message batch. - */ - void setPublishRpcSpanException(Span publishRpcSpan, Throwable t) { - if (!enabled) { - return; - } - if (publishRpcSpan != null) { - publishRpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on publish RPC."); - publishRpcSpan.recordException(t); - publishRpcSpan.end(); - } - } - - void startSubscriberSpan(PubsubMessageWrapper message, boolean exactlyOnceDeliveryEnabled) { - if (!enabled) { - return; - } - AttributesBuilder attributesBuilder = - createCommonSpanAttributesBuilder( - message.getSubscriptionName(), message.getSubscriptionProject(), "onResponse", null); - - attributesBuilder - .put(SemanticAttributes.MESSAGING_MESSAGE_ID, message.getMessageId()) - .put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()) - .put(MESSAGE_ACK_ID_ATTR_KEY, message.getAckId()) - .put(MESSAGE_EXACTLY_ONCE_ATTR_KEY, exactlyOnceDeliveryEnabled); - if (!message.getOrderingKey().isEmpty()) { - attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); - } - if (message.getDeliveryAttempt() > 0) { - attributesBuilder.put(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, message.getDeliveryAttempt()); - } - Attributes attributes = attributesBuilder.build(); - Context publisherSpanContext = message.extractSpanContext(attributes); - message.setPublisherSpan(Span.fromContextOrNull(publisherSpanContext)); - message.setSubscriberSpan( - tracer - .spanBuilder(message.getSubscriptionName() + " subscribe") - .setSpanKind(SpanKind.CONSUMER) - .setParent(publisherSpanContext) - .setAllAttributes(attributes) - .startSpan()); - } - - void endSubscriberSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endSubscriberSpan(); - } - - void setSubscriberSpanExpirationResult(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.setSubscriberSpanExpirationResult(); - } - - void setSubscriberSpanException(PubsubMessageWrapper message, Throwable t, String exception) { - if (!enabled) { - return; - } - message.setSubscriberSpanException(t, exception); - } - - /** Creates a span for subscribe concurrency control as a child of the parent subscriber span. */ - void startSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span subscriberSpan = message.getSubscriberSpan(); - if (subscriberSpan != null) { - message.setSubscribeConcurrencyControlSpan( - startChildSpan(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME, subscriberSpan)); - } - } - - void endSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endSubscribeConcurrencyControlSpan(); - } - - void setSubscribeConcurrencyControlSpanException(PubsubMessageWrapper message, Throwable t) { - if (!enabled) { - return; - } - message.setSubscribeConcurrencyControlSpanException(t); - } - - /** - * Creates a span for subscribe ordering key scheduling as a child of the parent subscriber span. - */ - void startSubscribeSchedulerSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span subscriberSpan = message.getSubscriberSpan(); - if (subscriberSpan != null) { - message.setSubscribeSchedulerSpan( - startChildSpan(SUBSCRIBE_SCHEDULER_SPAN_NAME, subscriberSpan)); - } - } - - void endSubscribeSchedulerSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - message.endSubscribeSchedulerSpan(); - } - - /** Creates a span for subscribe message processing as a child of the parent subscriber span. */ - void startSubscribeProcessSpan(PubsubMessageWrapper message) { - if (!enabled) { - return; - } - Span subscriberSpan = message.getSubscriberSpan(); - if (subscriberSpan != null) { - Span subscribeProcessSpan = - startChildSpan(message.getSubscriptionName() + " process", subscriberSpan); - subscribeProcessSpan.setAttribute( - SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE); - Span publisherSpan = message.getPublisherSpan(); - if (publisherSpan != null) { - subscribeProcessSpan.addLink(publisherSpan.getSpanContext()); - } - message.setSubscribeProcessSpan(subscribeProcessSpan); - } - } - - void endSubscribeProcessSpan(PubsubMessageWrapper message, String action) { - if (!enabled) { - return; - } - message.endSubscribeProcessSpan(action); - } - - /** - * Creates, starts, and returns spans for ModAck, Nack, and Ack RPC requests. Bi-directional links - * to parent subscribe span for sampled messages are added. - */ - Span startSubscribeRpcSpan( - String subscription, - String rpcOperation, - List messages, - int ackDeadline, - boolean isReceiptModack) { - if (!enabled) { - return null; - } - String codeFunction = rpcOperation == "ack" ? "sendAckOperations" : "sendModAckOperations"; - SubscriptionName subscriptionName = SubscriptionName.parse(subscription); - AttributesBuilder attributesBuilder = - createCommonSpanAttributesBuilder( - subscriptionName.getSubscription(), - subscriptionName.getProject(), - codeFunction, - rpcOperation) - .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()); - - // Ack deadline and receipt modack are specific to the modack operation - if (rpcOperation == "modack") { - attributesBuilder - .put(ACK_DEADLINE_ATTR_KEY, ackDeadline) - .put(RECEIPT_MODACK_ATTR_KEY, isReceiptModack); - } - - SpanBuilder rpcSpanBuilder = - tracer - .spanBuilder(subscriptionName.getSubscription() + " " + rpcOperation) - .setSpanKind(SpanKind.CLIENT) - .setAllAttributes(attributesBuilder.build()); - Attributes linkAttributes = - Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, rpcOperation).build(); - for (PubsubMessageWrapper message : messages) { - if (message.getSubscriberSpan().getSpanContext().isSampled()) { - rpcSpanBuilder.addLink(message.getSubscriberSpan().getSpanContext(), linkAttributes); - } - } - Span rpcSpan = rpcSpanBuilder.startSpan(); - - for (PubsubMessageWrapper message : messages) { - if (rpcSpan.getSpanContext().isSampled()) { - message.getSubscriberSpan().addLink(rpcSpan.getSpanContext(), linkAttributes); - switch (rpcOperation) { - case "ack": - message.addAckStartEvent(); - break; - case "modack": - message.addModAckStartEvent(); - break; - case "nack": - message.addNackStartEvent(); - break; - } - } - } - return rpcSpan; - } - - /** Ends the given subscribe RPC span if it exists. */ - void endSubscribeRpcSpan(Span rpcSpan) { - if (!enabled) { - return; - } - if (rpcSpan != null) { - rpcSpan.end(); - } - } - - /** - * Sets an error status and records an exception when an exception is thrown when handling a - * subscribe-side RPC. - */ - void setSubscribeRpcSpanException(Span rpcSpan, boolean isModack, int ackDeadline, Throwable t) { - if (!enabled) { - return; - } - if (rpcSpan != null) { - String operation = !isModack ? "ack" : (ackDeadline == 0 ? "nack" : "modack"); - rpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on " + operation + " RPC."); - rpcSpan.recordException(t); - rpcSpan.end(); - } - } - - /** Adds the appropriate subscribe-side RPC end event. */ - void addEndRpcEvent( - PubsubMessageWrapper message, boolean rpcSampled, boolean isModack, int ackDeadline) { - if (!enabled || !rpcSampled) { - return; - } - if (!isModack) { - message.addAckEndEvent(); - } else if (ackDeadline == 0) { - message.addNackEndEvent(); - } else { - message.addModAckEndEvent(); - } - } -} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java deleted file mode 100644 index 94fd13085..000000000 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java +++ /dev/null @@ -1,430 +0,0 @@ -/* - * 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.pubsub.v1; - -import com.google.pubsub.v1.PubsubMessage; -import com.google.pubsub.v1.SubscriptionName; -import com.google.pubsub.v1.TopicName; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.propagation.TextMapGetter; -import io.opentelemetry.context.propagation.TextMapSetter; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; - -/** - * A wrapper class for a {@link PubsubMessage} object that handles creation and tracking of - * OpenTelemetry {@link Span} objects for different operations that occur during publishing. - */ -public class PubsubMessageWrapper { - private PubsubMessage message; - - private final TopicName topicName; - private final SubscriptionName subscriptionName; - - // Attributes set only for messages received from a streaming pull response. - private final String ackId; - private final int deliveryAttempt; - - private static final String PUBLISH_START_EVENT = "publish start"; - private static final String PUBLISH_END_EVENT = "publish end"; - - private static final String MODACK_START_EVENT = "modack start"; - private static final String MODACK_END_EVENT = "modack end"; - private static final String NACK_START_EVENT = "nack start"; - private static final String NACK_END_EVENT = "nack end"; - private static final String ACK_START_EVENT = "ack start"; - private static final String ACK_END_EVENT = "ack end"; - - private static final String GOOGCLIENT_PREFIX = "googclient_"; - - private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; - - private Span publisherSpan; - private Span publishFlowControlSpan; - private Span publishBatchingSpan; - - private Span subscriberSpan; - private Span subscribeConcurrencyControlSpan; - private Span subscribeSchedulerSpan; - private Span subscribeProcessSpan; - - private PubsubMessageWrapper(Builder builder) { - this.message = builder.message; - this.topicName = builder.topicName; - this.subscriptionName = builder.subscriptionName; - this.ackId = builder.ackId; - this.deliveryAttempt = builder.deliveryAttempt; - } - - static Builder newBuilder(PubsubMessage message, String topicName) { - return new Builder(message, topicName); - } - - static Builder newBuilder( - PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { - return new Builder(message, subscriptionName, ackId, deliveryAttempt); - } - - /** Returns the PubsubMessage associated with this wrapper. */ - PubsubMessage getPubsubMessage() { - return message; - } - - void setPubsubMessage(PubsubMessage message) { - this.message = message; - } - - /** Returns the TopicName for this wrapper as a string. */ - String getTopicName() { - if (topicName != null) { - return topicName.getTopic(); - } - return ""; - } - - String getTopicProject() { - if (topicName != null) { - return topicName.getProject(); - } - return ""; - } - - /** Returns the SubscriptionName for this wrapper as a string. */ - String getSubscriptionName() { - if (subscriptionName != null) { - return subscriptionName.getSubscription(); - } - return ""; - } - - String getSubscriptionProject() { - if (subscriptionName != null) { - return subscriptionName.getProject(); - } - return ""; - } - - String getMessageId() { - return message.getMessageId(); - } - - String getAckId() { - return ackId; - } - - int getDataSize() { - return message.getData().size(); - } - - String getOrderingKey() { - return message.getOrderingKey(); - } - - int getDeliveryAttempt() { - return deliveryAttempt; - } - - Span getPublisherSpan() { - return publisherSpan; - } - - void setPublisherSpan(Span span) { - this.publisherSpan = span; - } - - void setPublishFlowControlSpan(Span span) { - this.publishFlowControlSpan = span; - } - - void setPublishBatchingSpan(Span span) { - this.publishBatchingSpan = span; - } - - Span getSubscriberSpan() { - return subscriberSpan; - } - - void setSubscriberSpan(Span span) { - this.subscriberSpan = span; - } - - void setSubscribeConcurrencyControlSpan(Span span) { - this.subscribeConcurrencyControlSpan = span; - } - - void setSubscribeSchedulerSpan(Span span) { - this.subscribeSchedulerSpan = span; - } - - void setSubscribeProcessSpan(Span span) { - this.subscribeProcessSpan = span; - } - - /** Creates a publish start event that is tied to the publish RPC span time. */ - void addPublishStartEvent() { - if (publisherSpan != null) { - publisherSpan.addEvent(PUBLISH_START_EVENT); - } - } - - /** - * Sets the message ID attribute in the publisher parent span. This is called after the publish - * RPC returns with a message ID. - */ - void setPublisherMessageIdSpanAttribute(String messageId) { - if (publisherSpan != null) { - publisherSpan.setAttribute(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId); - } - } - - /** Ends the publisher parent span if it exists. */ - void endPublisherSpan() { - if (publisherSpan != null) { - publisherSpan.addEvent(PUBLISH_END_EVENT); - publisherSpan.end(); - } - } - - /** Ends the publish flow control span if it exists. */ - void endPublishFlowControlSpan() { - if (publishFlowControlSpan != null) { - publishFlowControlSpan.end(); - } - } - - /** Ends the publish batching span if it exists. */ - void endPublishBatchingSpan() { - if (publishBatchingSpan != null) { - publishBatchingSpan.end(); - } - } - - /** - * Sets an error status and records an exception when an exception is thrown during flow control. - */ - void setPublishFlowControlSpanException(Throwable t) { - if (publishFlowControlSpan != null) { - publishFlowControlSpan.setStatus( - StatusCode.ERROR, "Exception thrown during publish flow control."); - publishFlowControlSpan.recordException(t); - endAllPublishSpans(); - } - } - - /** - * Creates start and end events for ModAcks, Nacks, and Acks that are tied to the corresponding - * RPC span start and end times. - */ - void addModAckStartEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(MODACK_START_EVENT); - } - } - - void addModAckEndEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(MODACK_END_EVENT); - } - } - - void addNackStartEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(NACK_START_EVENT); - } - } - - void addNackEndEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(NACK_END_EVENT); - } - } - - void addAckStartEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(ACK_START_EVENT); - } - } - - void addAckEndEvent() { - if (subscriberSpan != null) { - subscriberSpan.addEvent(ACK_END_EVENT); - } - } - - /** Ends the subscriber parent span if exists. */ - void endSubscriberSpan() { - if (subscriberSpan != null) { - subscriberSpan.end(); - } - } - - /** Ends the subscribe concurreny control span if exists. */ - void endSubscribeConcurrencyControlSpan() { - if (subscribeConcurrencyControlSpan != null) { - subscribeConcurrencyControlSpan.end(); - } - } - - /** Ends the subscribe scheduler span if exists. */ - void endSubscribeSchedulerSpan() { - if (subscribeSchedulerSpan != null) { - subscribeSchedulerSpan.end(); - } - } - - /** - * Ends the subscribe process span if it exists, creates an event with the appropriate result, and - * sets the result on the parent subscriber span. - */ - void endSubscribeProcessSpan(String action) { - if (subscribeProcessSpan != null) { - subscribeProcessSpan.addEvent(action + " called"); - subscribeProcessSpan.end(); - subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, action); - } - } - - /** Sets an exception on the subscriber span during Ack/ModAck/Nack failures */ - void setSubscriberSpanException(Throwable t, String exception) { - if (subscriberSpan != null) { - subscriberSpan.setStatus(StatusCode.ERROR, exception); - subscriberSpan.recordException(t); - endAllSubscribeSpans(); - } - } - - /** Sets result of the parent subscriber span to expired and ends its. */ - void setSubscriberSpanExpirationResult() { - if (subscriberSpan != null) { - subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, "expired"); - endSubscriberSpan(); - } - } - - /** - * Sets an error status and records an exception when an exception is thrown subscriber - * concurrency control. - */ - void setSubscribeConcurrencyControlSpanException(Throwable t) { - if (subscribeConcurrencyControlSpan != null) { - subscribeConcurrencyControlSpan.setStatus( - StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); - subscribeConcurrencyControlSpan.recordException(t); - endAllSubscribeSpans(); - } - } - - /** Ends all publisher-side spans associated with this message wrapper. */ - private void endAllPublishSpans() { - endPublishFlowControlSpan(); - endPublishBatchingSpan(); - endPublisherSpan(); - } - - /** Ends all subscriber-side spans associated with this message wrapper. */ - private void endAllSubscribeSpans() { - endSubscribeConcurrencyControlSpan(); - endSubscribeSchedulerSpan(); - endSubscriberSpan(); - } - - /** - * Injects the span context into the attributes of a Pub/Sub message for propagation to the - * subscriber client. - */ - void injectSpanContext() { - TextMapSetter injectMessageAttributes = - new TextMapSetter() { - @Override - public void set(PubsubMessageWrapper carrier, String key, String value) { - PubsubMessage newMessage = - PubsubMessage.newBuilder(carrier.message) - .putAttributes(GOOGCLIENT_PREFIX + key, value) - .build(); - carrier.message = newMessage; - } - }; - W3CTraceContextPropagator.getInstance() - .inject(Context.current().with(publisherSpan), this, injectMessageAttributes); - } - - /** - * Extracts the span context from the attributes of a Pub/Sub message and creates the parent - * subscriber span using that context. - */ - Context extractSpanContext(Attributes attributes) { - TextMapGetter extractMessageAttributes = - new TextMapGetter() { - @Override - public String get(PubsubMessageWrapper carrier, String key) { - return carrier.message.getAttributesOrDefault(GOOGCLIENT_PREFIX + key, ""); - } - - public Iterable keys(PubsubMessageWrapper carrier) { - return carrier.message.getAttributesMap().keySet(); - } - }; - Context context = - W3CTraceContextPropagator.getInstance() - .extract(Context.current(), this, extractMessageAttributes); - return context; - } - - /** Builder of {@link PubsubMessageWrapper PubsubMessageWrapper}. */ - static final class Builder { - private PubsubMessage message = null; - private TopicName topicName = null; - private SubscriptionName subscriptionName = null; - private String ackId = null; - private int deliveryAttempt = 0; - - public Builder(PubsubMessage message, String topicName) { - this.message = message; - if (topicName != null) { - this.topicName = TopicName.parse(topicName); - } - } - - public Builder( - PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { - this.message = message; - if (subscriptionName != null) { - this.subscriptionName = SubscriptionName.parse(subscriptionName); - } - this.ackId = ackId; - this.deliveryAttempt = deliveryAttempt; - } - - public Builder( - PubsubMessage message, - SubscriptionName subscriptionName, - String ackId, - int deliveryAttempt) { - this.message = message; - this.subscriptionName = subscriptionName; - this.ackId = ackId; - this.deliveryAttempt = deliveryAttempt; - } - - public PubsubMessageWrapper build() { - return new PubsubMessageWrapper(this); - } - } -} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java deleted file mode 100644 index b4433f41e..000000000 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java +++ /dev/null @@ -1,669 +0,0 @@ -/* - * 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.pubsub.v1; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import com.google.protobuf.ByteString; -import com.google.pubsub.v1.PubsubMessage; -import com.google.pubsub.v1.SubscriptionName; -import com.google.pubsub.v1.TopicName; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.sdk.testing.assertj.AttributesAssert; -import io.opentelemetry.sdk.testing.assertj.EventDataAssert; -import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; -import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; -import io.opentelemetry.sdk.testing.junit4.OpenTelemetryRule; -import io.opentelemetry.sdk.trace.data.LinkData; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.data.StatusData; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.util.Arrays; -import java.util.List; -import org.junit.Test; - -public class OpenTelemetryTest { - private static final TopicName FULL_TOPIC_NAME = - TopicName.parse("projects/test-project/topics/test-topic"); - private static final SubscriptionName FULL_SUBSCRIPTION_NAME = - SubscriptionName.parse("projects/test-project/subscriptions/test-sub"); - private static final String PROJECT_NAME = "test-project"; - private static final String ORDERING_KEY = "abc"; - private static final String MESSAGE_ID = "m0"; - private static final String ACK_ID = "def"; - private static final int DELIVERY_ATTEMPT = 1; - private static final int ACK_DEADLINE = 10; - private static final boolean EXACTLY_ONCE_ENABLED = true; - - private static final String PUBLISHER_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " create"; - private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; - private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; - private static final String PUBLISH_RPC_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " publish"; - private static final String PUBLISH_START_EVENT = "publish start"; - private static final String PUBLISH_END_EVENT = "publish end"; - - private static final String SUBSCRIBER_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " subscribe"; - private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = - "subscriber concurrency control"; - private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; - private static final String SUBSCRIBE_PROCESS_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " process"; - private static final String SUBSCRIBE_MODACK_RPC_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " modack"; - private static final String SUBSCRIBE_ACK_RPC_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " ack"; - private static final String SUBSCRIBE_NACK_RPC_SPAN_NAME = - FULL_SUBSCRIPTION_NAME.getSubscription() + " nack"; - - private static final String PROCESS_ACTION = "ack"; - private static final String MODACK_START_EVENT = "modack start"; - private static final String MODACK_END_EVENT = "modack end"; - private static final String NACK_START_EVENT = "nack start"; - private static final String NACK_END_EVENT = "nack end"; - private static final String ACK_START_EVENT = "ack start"; - private static final String ACK_END_EVENT = "ack end"; - - private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; - private static final String PROJECT_ATTR_KEY = "gcp.project_id"; - private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; - private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; - private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; - private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; - private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; - private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = - "messaging.gcp_pubsub.message.exactly_once_delivery"; - private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; - private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = - "messaging.gcp_pubsub.message.delivery_attempt"; - - private static final String TRACEPARENT_ATTRIBUTE = "googclient_traceparent"; - - private static final OpenTelemetryRule openTelemetryTesting = OpenTelemetryRule.create(); - - @Test - public void testPublishSpansSuccess() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); - List messageWrappers = Arrays.asList(messageWrapper); - - long messageSize = messageWrapper.getPubsubMessage().getData().size(); - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - // Call all span start/end methods in the expected order - tracer.startPublisherSpan(messageWrapper); - tracer.startPublishFlowControlSpan(messageWrapper); - tracer.endPublishFlowControlSpan(messageWrapper); - tracer.startPublishBatchingSpan(messageWrapper); - tracer.endPublishBatchingSpan(messageWrapper); - Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); - tracer.endPublishRpcSpan(publishRpcSpan); - tracer.setPublisherMessageIdSpanAttribute(messageWrapper, MESSAGE_ID); - tracer.endPublisherSpan(messageWrapper); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(4, allSpans.size()); - SpanData flowControlSpanData = allSpans.get(0); - SpanData batchingSpanData = allSpans.get(1); - SpanData publishRpcSpanData = allSpans.get(2); - SpanData publisherSpanData = allSpans.get(3); - - SpanDataAssert flowControlSpanDataAssert = - OpenTelemetryAssertions.assertThat(flowControlSpanData); - flowControlSpanDataAssert - .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) - .hasParent(publisherSpanData) - .hasEnded(); - - SpanDataAssert batchingSpanDataAssert = OpenTelemetryAssertions.assertThat(batchingSpanData); - batchingSpanDataAssert - .hasName(PUBLISH_BATCHING_SPAN_NAME) - .hasParent(publisherSpanData) - .hasEnded(); - - // Check span data, links, and attributes for the publish RPC span - SpanDataAssert publishRpcSpanDataAssert = - OpenTelemetryAssertions.assertThat(publishRpcSpanData); - publishRpcSpanDataAssert - .hasName(PUBLISH_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasEnded(); - - List publishRpcLinks = publishRpcSpanData.getLinks(); - assertEquals(messageWrappers.size(), publishRpcLinks.size()); - assertEquals(publisherSpanData.getSpanContext(), publishRpcLinks.get(0).getSpanContext()); - - assertEquals(6, publishRpcSpanData.getAttributes().size()); - AttributesAssert publishRpcSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(publishRpcSpanData.getAttributes()); - publishRpcSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) - .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "publishCall") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "publish") - .containsEntry(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messageWrappers.size()); - - // Check span data, events, links, and attributes for the publisher create span - SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); - publisherSpanDataAssert - .hasName(PUBLISHER_SPAN_NAME) - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasEnded(); - - assertEquals(2, publisherSpanData.getEvents().size()); - EventDataAssert startEventAssert = - OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(0)); - startEventAssert.hasName(PUBLISH_START_EVENT); - EventDataAssert endEventAssert = - OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(1)); - endEventAssert.hasName(PUBLISH_END_EVENT); - - List publisherLinks = publisherSpanData.getLinks(); - assertEquals(1, publisherLinks.size()); - assertEquals(publishRpcSpanData.getSpanContext(), publisherLinks.get(0).getSpanContext()); - - assertEquals(8, publisherSpanData.getAttributes().size()); - AttributesAssert publisherSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(publisherSpanData.getAttributes()); - publisherSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) - .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "publish") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "create") - .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) - .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) - .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); - - // Check that the message has the attribute containing the trace context. - PubsubMessage message = messageWrapper.getPubsubMessage(); - assertEquals(1, message.getAttributesMap().size()); - assertTrue(message.containsAttributes(TRACEPARENT_ATTRIBUTE)); - assertTrue( - message - .getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "") - .contains(publisherSpanData.getTraceId())); - assertTrue( - message - .getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "") - .contains(publisherSpanData.getSpanId())); - } - - @Test - public void testPublishFlowControlSpanFailure() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startPublisherSpan(messageWrapper); - tracer.startPublishFlowControlSpan(messageWrapper); - - Exception e = new Exception("test-exception"); - tracer.setPublishFlowControlSpanException(messageWrapper, e); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(2, allSpans.size()); - SpanData flowControlSpanData = allSpans.get(0); - SpanData publisherSpanData = allSpans.get(1); - - SpanDataAssert flowControlSpanDataAssert = - OpenTelemetryAssertions.assertThat(flowControlSpanData); - StatusData expectedStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown during publish flow control."); - flowControlSpanDataAssert - .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) - .hasParent(publisherSpanData) - .hasStatus(expectedStatus) - .hasException(e) - .hasEnded(); - - SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); - publisherSpanDataAssert - .hasName(PUBLISHER_SPAN_NAME) - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasEnded(); - } - - @Test - public void testPublishRpcSpanFailure() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); - - List messageWrappers = Arrays.asList(messageWrapper); - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startPublisherSpan(messageWrapper); - Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); - - Exception e = new Exception("test-exception"); - tracer.setPublishRpcSpanException(publishRpcSpan, e); - tracer.endPublisherSpan(messageWrapper); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(2, allSpans.size()); - SpanData rpcSpanData = allSpans.get(0); - SpanData publisherSpanData = allSpans.get(1); - - SpanDataAssert rpcSpanDataAssert = OpenTelemetryAssertions.assertThat(rpcSpanData); - StatusData expectedStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown on publish RPC."); - rpcSpanDataAssert - .hasName(PUBLISH_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasStatus(expectedStatus) - .hasException(e) - .hasEnded(); - - SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); - publisherSpanDataAssert - .hasName(PUBLISHER_SPAN_NAME) - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasEnded(); - } - - @Test - public void testSubscribeSpansSuccess() { - openTelemetryTesting.clearSpans(); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - PubsubMessageWrapper publishMessageWrapper = - PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); - // Initialize the Publisher span to inject the context in the message - tracer.startPublisherSpan(publishMessageWrapper); - tracer.endPublisherSpan(publishMessageWrapper); - - PubsubMessage publishedMessage = - publishMessageWrapper.getPubsubMessage().toBuilder().setMessageId(MESSAGE_ID).build(); - PubsubMessageWrapper subscribeMessageWrapper = - PubsubMessageWrapper.newBuilder( - publishedMessage, FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, 1) - .build(); - List subscribeMessageWrappers = Arrays.asList(subscribeMessageWrapper); - - long messageSize = subscribeMessageWrapper.getPubsubMessage().getData().size(); - - // Call all span start/end methods in the expected order - tracer.startSubscriberSpan(subscribeMessageWrapper, EXACTLY_ONCE_ENABLED); - tracer.startSubscribeConcurrencyControlSpan(subscribeMessageWrapper); - tracer.endSubscribeConcurrencyControlSpan(subscribeMessageWrapper); - tracer.startSubscribeSchedulerSpan(subscribeMessageWrapper); - tracer.endSubscribeSchedulerSpan(subscribeMessageWrapper); - tracer.startSubscribeProcessSpan(subscribeMessageWrapper); - tracer.endSubscribeProcessSpan(subscribeMessageWrapper, PROCESS_ACTION); - Span subscribeModackRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), - "modack", - subscribeMessageWrappers, - ACK_DEADLINE, - true); - tracer.endSubscribeRpcSpan(subscribeModackRpcSpan); - tracer.addEndRpcEvent(subscribeMessageWrapper, true, true, ACK_DEADLINE); - Span subscribeAckRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "ack", subscribeMessageWrappers, 0, false); - tracer.endSubscribeRpcSpan(subscribeAckRpcSpan); - tracer.addEndRpcEvent(subscribeMessageWrapper, true, false, 0); - Span subscribeNackRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "nack", subscribeMessageWrappers, 0, false); - tracer.endSubscribeRpcSpan(subscribeNackRpcSpan); - tracer.addEndRpcEvent(subscribeMessageWrapper, true, true, 0); - tracer.endSubscriberSpan(subscribeMessageWrapper); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(8, allSpans.size()); - - SpanData publisherSpanData = allSpans.get(0); - SpanData concurrencyControlSpanData = allSpans.get(1); - SpanData schedulerSpanData = allSpans.get(2); - SpanData processSpanData = allSpans.get(3); - SpanData modackRpcSpanData = allSpans.get(4); - SpanData ackRpcSpanData = allSpans.get(5); - SpanData nackRpcSpanData = allSpans.get(6); - SpanData subscriberSpanData = allSpans.get(7); - - SpanDataAssert concurrencyControlSpanDataAssert = - OpenTelemetryAssertions.assertThat(concurrencyControlSpanData); - concurrencyControlSpanDataAssert - .hasName(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME) - .hasParent(subscriberSpanData) - .hasEnded(); - - SpanDataAssert schedulerSpanDataAssert = OpenTelemetryAssertions.assertThat(schedulerSpanData); - schedulerSpanDataAssert - .hasName(SUBSCRIBE_SCHEDULER_SPAN_NAME) - .hasParent(subscriberSpanData) - .hasEnded(); - - SpanDataAssert processSpanDataAssert = OpenTelemetryAssertions.assertThat(processSpanData); - processSpanDataAssert - .hasName(SUBSCRIBE_PROCESS_SPAN_NAME) - .hasParent(subscriberSpanData) - .hasEnded(); - - assertEquals(1, processSpanData.getEvents().size()); - EventDataAssert actionCalledEventAssert = - OpenTelemetryAssertions.assertThat(processSpanData.getEvents().get(0)); - actionCalledEventAssert.hasName(PROCESS_ACTION + " called"); - - // Check span data, links, and attributes for the modack RPC span - SpanDataAssert modackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(modackRpcSpanData); - modackRpcSpanDataAssert - .hasName(SUBSCRIBE_MODACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasEnded(); - - List modackRpcLinks = modackRpcSpanData.getLinks(); - assertEquals(subscribeMessageWrappers.size(), modackRpcLinks.size()); - assertEquals(subscriberSpanData.getSpanContext(), modackRpcLinks.get(0).getSpanContext()); - - assertEquals(8, modackRpcSpanData.getAttributes().size()); - AttributesAssert modackRpcSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(modackRpcSpanData.getAttributes()); - modackRpcSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry( - SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) - .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "modack") - .containsEntry( - SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()) - .containsEntry(ACK_DEADLINE_ATTR_KEY, 10) - .containsEntry(RECEIPT_MODACK_ATTR_KEY, true); - - // Check span data, links, and attributes for the ack RPC span - SpanDataAssert ackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(ackRpcSpanData); - ackRpcSpanDataAssert - .hasName(SUBSCRIBE_ACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasEnded(); - - List ackRpcLinks = ackRpcSpanData.getLinks(); - assertEquals(subscribeMessageWrappers.size(), ackRpcLinks.size()); - assertEquals(subscriberSpanData.getSpanContext(), ackRpcLinks.get(0).getSpanContext()); - - assertEquals(6, ackRpcSpanData.getAttributes().size()); - AttributesAssert ackRpcSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(ackRpcSpanData.getAttributes()); - ackRpcSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry( - SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) - .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendAckOperations") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "ack") - .containsEntry( - SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); - - // Check span data, links, and attributes for the nack RPC span - SpanDataAssert nackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(nackRpcSpanData); - nackRpcSpanDataAssert - .hasName(SUBSCRIBE_NACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasEnded(); - - List nackRpcLinks = nackRpcSpanData.getLinks(); - assertEquals(subscribeMessageWrappers.size(), nackRpcLinks.size()); - assertEquals(subscriberSpanData.getSpanContext(), nackRpcLinks.get(0).getSpanContext()); - - assertEquals(6, nackRpcSpanData.getAttributes().size()); - AttributesAssert nackRpcSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(nackRpcSpanData.getAttributes()); - nackRpcSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry( - SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) - .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") - .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "nack") - .containsEntry( - SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); - - // Check span data, events, links, and attributes for the publisher create span - SpanDataAssert subscriberSpanDataAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData); - subscriberSpanDataAssert - .hasName(SUBSCRIBER_SPAN_NAME) - .hasKind(SpanKind.CONSUMER) - .hasParent(publisherSpanData) - .hasEnded(); - - assertEquals(6, subscriberSpanData.getEvents().size()); - EventDataAssert startModackEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(0)); - startModackEventAssert.hasName(MODACK_START_EVENT); - EventDataAssert endModackEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(1)); - endModackEventAssert.hasName(MODACK_END_EVENT); - EventDataAssert startAckEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(2)); - startAckEventAssert.hasName(ACK_START_EVENT); - EventDataAssert endAckEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(3)); - endAckEventAssert.hasName(ACK_END_EVENT); - EventDataAssert startNackEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(4)); - startNackEventAssert.hasName(NACK_START_EVENT); - EventDataAssert endNackEventAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(5)); - endNackEventAssert.hasName(NACK_END_EVENT); - - List subscriberLinks = subscriberSpanData.getLinks(); - assertEquals(3, subscriberLinks.size()); - assertEquals(modackRpcSpanData.getSpanContext(), subscriberLinks.get(0).getSpanContext()); - assertEquals(ackRpcSpanData.getSpanContext(), subscriberLinks.get(1).getSpanContext()); - assertEquals(nackRpcSpanData.getSpanContext(), subscriberLinks.get(2).getSpanContext()); - - assertEquals(11, subscriberSpanData.getAttributes().size()); - AttributesAssert subscriberSpanAttributesAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData.getAttributes()); - subscriberSpanAttributesAssert - .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) - .containsEntry( - SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) - .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) - .containsEntry(SemanticAttributes.CODE_FUNCTION, "onResponse") - .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) - .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) - .containsEntry(MESSAGE_ACK_ID_ATTR_KEY, ACK_ID) - .containsEntry(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, DELIVERY_ATTEMPT) - .containsEntry(MESSAGE_EXACTLY_ONCE_ATTR_KEY, EXACTLY_ONCE_ENABLED) - .containsEntry(MESSAGE_RESULT_ATTR_KEY, PROCESS_ACTION) - .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); - } - - @Test - public void testSubscribeConcurrencyControlSpanFailure() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder( - getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) - .build(); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); - tracer.startSubscribeConcurrencyControlSpan(messageWrapper); - - Exception e = new Exception("test-exception"); - tracer.setSubscribeConcurrencyControlSpanException(messageWrapper, e); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(2, allSpans.size()); - SpanData concurrencyControlSpanData = allSpans.get(0); - SpanData subscriberSpanData = allSpans.get(1); - - SpanDataAssert concurrencyControlSpanDataAssert = - OpenTelemetryAssertions.assertThat(concurrencyControlSpanData); - StatusData expectedStatus = - StatusData.create( - StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); - concurrencyControlSpanDataAssert - .hasName(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME) - .hasParent(subscriberSpanData) - .hasStatus(expectedStatus) - .hasException(e) - .hasEnded(); - - SpanDataAssert subscriberSpanDataAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData); - subscriberSpanDataAssert - .hasName(SUBSCRIBER_SPAN_NAME) - .hasKind(SpanKind.CONSUMER) - .hasNoParent() - .hasEnded(); - } - - @Test - public void testSubscriberSpanFailure() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder( - getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) - .build(); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); - - Exception e = new Exception("test-exception"); - tracer.setSubscriberSpanException(messageWrapper, e, "Test exception"); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(1, allSpans.size()); - SpanData subscriberSpanData = allSpans.get(0); - - StatusData expectedStatus = StatusData.create(StatusCode.ERROR, "Test exception"); - SpanDataAssert subscriberSpanDataAssert = - OpenTelemetryAssertions.assertThat(subscriberSpanData); - subscriberSpanDataAssert - .hasName(SUBSCRIBER_SPAN_NAME) - .hasKind(SpanKind.CONSUMER) - .hasNoParent() - .hasStatus(expectedStatus) - .hasException(e) - .hasEnded(); - } - - @Test - public void testSubscribeRpcSpanFailures() { - openTelemetryTesting.clearSpans(); - - PubsubMessageWrapper messageWrapper = - PubsubMessageWrapper.newBuilder( - getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) - .build(); - List messageWrappers = Arrays.asList(messageWrapper); - - Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); - OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); - - tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); - Span subscribeModackRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "modack", messageWrappers, ACK_DEADLINE, true); - Span subscribeAckRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "ack", messageWrappers, 0, false); - Span subscribeNackRpcSpan = - tracer.startSubscribeRpcSpan( - FULL_SUBSCRIPTION_NAME.toString(), "nack", messageWrappers, 0, false); - - Exception e = new Exception("test-exception"); - tracer.setSubscribeRpcSpanException(subscribeModackRpcSpan, true, ACK_DEADLINE, e); - tracer.setSubscribeRpcSpanException(subscribeAckRpcSpan, false, 0, e); - tracer.setSubscribeRpcSpanException(subscribeNackRpcSpan, true, 0, e); - tracer.endSubscriberSpan(messageWrapper); - - List allSpans = openTelemetryTesting.getSpans(); - assertEquals(4, allSpans.size()); - SpanData modackSpanData = allSpans.get(0); - SpanData ackSpanData = allSpans.get(1); - SpanData nackSpanData = allSpans.get(2); - SpanData subscriberSpanData = allSpans.get(3); - - StatusData expectedModackStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown on modack RPC."); - SpanDataAssert modackSpanDataAssert = OpenTelemetryAssertions.assertThat(modackSpanData); - modackSpanDataAssert - .hasName(SUBSCRIBE_MODACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasStatus(expectedModackStatus) - .hasException(e) - .hasEnded(); - - StatusData expectedAckStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown on ack RPC."); - SpanDataAssert ackSpanDataAssert = OpenTelemetryAssertions.assertThat(ackSpanData); - ackSpanDataAssert - .hasName(SUBSCRIBE_ACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasStatus(expectedAckStatus) - .hasException(e) - .hasEnded(); - - StatusData expectedNackStatus = - StatusData.create(StatusCode.ERROR, "Exception thrown on nack RPC."); - SpanDataAssert nackSpanDataAssert = OpenTelemetryAssertions.assertThat(nackSpanData); - nackSpanDataAssert - .hasName(SUBSCRIBE_NACK_RPC_SPAN_NAME) - .hasKind(SpanKind.CLIENT) - .hasNoParent() - .hasStatus(expectedNackStatus) - .hasException(e) - .hasEnded(); - } - - private PubsubMessage getPubsubMessage() { - return PubsubMessage.newBuilder() - .setData(ByteString.copyFromUtf8("test-data")) - .setOrderingKey(ORDERING_KEY) - .build(); - } -} From c776056cf827426ec096eb0129bd56617358bef4 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 30 Sep 2024 21:37:09 +0000 Subject: [PATCH 43/46] feat: Prevent new files for OpenTelemetry from being overwritten --- .github/.OwlBot-hermetic.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/.OwlBot-hermetic.yaml b/.github/.OwlBot-hermetic.yaml index 1757987e4..8a75909c6 100644 --- a/.github/.OwlBot-hermetic.yaml +++ b/.github/.OwlBot-hermetic.yaml @@ -33,6 +33,7 @@ deep-preserve-regex: - "/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/MessageDataMatcher.java" - "/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/MessageDispatcherTest.java" - "/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenCensusUtilTest.java" +- "/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java" - "/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/PublisherImplTest.java" - "/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/SequentialExecutorServiceTest.java" - "/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/StatusUtilTest.java" @@ -51,8 +52,10 @@ deep-preserve-regex: - "/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageReceiverWithAckResponse.java" - "/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/ModackRequestData.java" - "/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenCensusUtil.java" +- "/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java" - "/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/Publisher.java" - "/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PublisherInterface.java" +- "/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java" - "/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/SequentialExecutorService.java" - "/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StatusUtil.java" - "/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StreamingSubscriberConnection.java" From 110b46c5e3e563d20b9aac045652eff729bb6067 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 30 Sep 2024 21:38:12 +0000 Subject: [PATCH 44/46] feat: Revert automated file deletion for OpenTelemetry changes --- .../pubsub/v1/OpenTelemetryPubsubTracer.java | 460 ++++++++++++ .../cloud/pubsub/v1/PubsubMessageWrapper.java | 430 +++++++++++ .../cloud/pubsub/v1/OpenTelemetryTest.java | 669 ++++++++++++++++++ 3 files changed, 1559 insertions(+) create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java new file mode 100644 index 000000000..b946f44bf --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/OpenTelemetryPubsubTracer.java @@ -0,0 +1,460 @@ +/* + * 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.pubsub.v1; + +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.SubscriptionName; +import com.google.pubsub.v1.TopicName; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.List; + +public class OpenTelemetryPubsubTracer { + private final Tracer tracer; + private boolean enabled = false; + + private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; + private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; + private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = + "subscriber concurrency control"; + private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; + + private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; + private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; + private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; + private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = + "messaging.gcp_pubsub.message.exactly_once_delivery"; + private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = + "messaging.gcp_pubsub.message.delivery_attempt"; + private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; + private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; + private static final String PROJECT_ATTR_KEY = "gcp.project_id"; + private static final String PUBLISH_RPC_SPAN_SUFFIX = " publish"; + + private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; + + OpenTelemetryPubsubTracer(Tracer tracer, boolean enableOpenTelemetry) { + this.tracer = tracer; + if (this.tracer != null && enableOpenTelemetry) { + this.enabled = true; + } + } + + /** Populates attributes that are common the publisher parent span and publish RPC span. */ + private static final AttributesBuilder createCommonSpanAttributesBuilder( + String destinationName, String projectName, String codeFunction, String operation) { + AttributesBuilder attributesBuilder = + Attributes.builder() + .put(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .put(SemanticAttributes.MESSAGING_DESTINATION_NAME, destinationName) + .put(PROJECT_ATTR_KEY, projectName) + .put(SemanticAttributes.CODE_FUNCTION, codeFunction); + if (operation != null) { + attributesBuilder.put(SemanticAttributes.MESSAGING_OPERATION, operation); + } + + return attributesBuilder; + } + + private Span startChildSpan(String name, Span parent) { + return tracer.spanBuilder(name).setParent(Context.current().with(parent)).startSpan(); + } + + /** + * Creates and starts the parent span with the appropriate span attributes and injects the span + * context into the {@link PubsubMessage} attributes. + */ + void startPublisherSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + AttributesBuilder attributesBuilder = + createCommonSpanAttributesBuilder( + message.getTopicName(), message.getTopicProject(), "publish", "create"); + + attributesBuilder.put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()); + if (!message.getOrderingKey().isEmpty()) { + attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); + } + + Span publisherSpan = + tracer + .spanBuilder(message.getTopicName() + " create") + .setSpanKind(SpanKind.PRODUCER) + .setAllAttributes(attributesBuilder.build()) + .startSpan(); + + message.setPublisherSpan(publisherSpan); + if (publisherSpan.getSpanContext().isValid()) { + message.injectSpanContext(); + } + } + + void endPublisherSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endPublisherSpan(); + } + + void setPublisherMessageIdSpanAttribute(PubsubMessageWrapper message, String messageId) { + if (!enabled) { + return; + } + message.setPublisherMessageIdSpanAttribute(messageId); + } + + /** Creates a span for publish-side flow control as a child of the parent publisher span. */ + void startPublishFlowControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span publisherSpan = message.getPublisherSpan(); + if (publisherSpan != null) + message.setPublishFlowControlSpan( + startChildSpan(PUBLISH_FLOW_CONTROL_SPAN_NAME, publisherSpan)); + } + + void endPublishFlowControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endPublishFlowControlSpan(); + } + + void setPublishFlowControlSpanException(PubsubMessageWrapper message, Throwable t) { + if (!enabled) { + return; + } + message.setPublishFlowControlSpanException(t); + } + + /** Creates a span for publish message batching as a child of the parent publisher span. */ + void startPublishBatchingSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span publisherSpan = message.getPublisherSpan(); + if (publisherSpan != null) { + message.setPublishBatchingSpan(startChildSpan(PUBLISH_BATCHING_SPAN_NAME, publisherSpan)); + } + } + + void endPublishBatchingSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endPublishBatchingSpan(); + } + + /** + * Creates, starts, and returns a publish RPC span for the given message batch. Bi-directional + * links with the publisher parent span are created for sampled messages in the batch. + */ + Span startPublishRpcSpan(String topic, List messages) { + if (!enabled) { + return null; + } + TopicName topicName = TopicName.parse(topic); + Attributes attributes = + createCommonSpanAttributesBuilder( + topicName.getTopic(), topicName.getProject(), "publishCall", "publish") + .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()) + .build(); + SpanBuilder publishRpcSpanBuilder = + tracer + .spanBuilder(topicName.getTopic() + PUBLISH_RPC_SPAN_SUFFIX) + .setSpanKind(SpanKind.CLIENT) + .setAllAttributes(attributes); + Attributes linkAttributes = + Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, "publish").build(); + for (PubsubMessageWrapper message : messages) { + if (message.getPublisherSpan().getSpanContext().isSampled()) + publishRpcSpanBuilder.addLink(message.getPublisherSpan().getSpanContext(), linkAttributes); + } + Span publishRpcSpan = publishRpcSpanBuilder.startSpan(); + + for (PubsubMessageWrapper message : messages) { + if (publishRpcSpan.getSpanContext().isSampled()) { + message.getPublisherSpan().addLink(publishRpcSpan.getSpanContext(), linkAttributes); + message.addPublishStartEvent(); + } + } + return publishRpcSpan; + } + + /** Ends the given publish RPC span if it exists. */ + void endPublishRpcSpan(Span publishRpcSpan) { + if (!enabled) { + return; + } + if (publishRpcSpan != null) { + publishRpcSpan.end(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown when publishing the + * message batch. + */ + void setPublishRpcSpanException(Span publishRpcSpan, Throwable t) { + if (!enabled) { + return; + } + if (publishRpcSpan != null) { + publishRpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on publish RPC."); + publishRpcSpan.recordException(t); + publishRpcSpan.end(); + } + } + + void startSubscriberSpan(PubsubMessageWrapper message, boolean exactlyOnceDeliveryEnabled) { + if (!enabled) { + return; + } + AttributesBuilder attributesBuilder = + createCommonSpanAttributesBuilder( + message.getSubscriptionName(), message.getSubscriptionProject(), "onResponse", null); + + attributesBuilder + .put(SemanticAttributes.MESSAGING_MESSAGE_ID, message.getMessageId()) + .put(MESSAGE_SIZE_ATTR_KEY, message.getDataSize()) + .put(MESSAGE_ACK_ID_ATTR_KEY, message.getAckId()) + .put(MESSAGE_EXACTLY_ONCE_ATTR_KEY, exactlyOnceDeliveryEnabled); + if (!message.getOrderingKey().isEmpty()) { + attributesBuilder.put(ORDERING_KEY_ATTR_KEY, message.getOrderingKey()); + } + if (message.getDeliveryAttempt() > 0) { + attributesBuilder.put(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, message.getDeliveryAttempt()); + } + Attributes attributes = attributesBuilder.build(); + Context publisherSpanContext = message.extractSpanContext(attributes); + message.setPublisherSpan(Span.fromContextOrNull(publisherSpanContext)); + message.setSubscriberSpan( + tracer + .spanBuilder(message.getSubscriptionName() + " subscribe") + .setSpanKind(SpanKind.CONSUMER) + .setParent(publisherSpanContext) + .setAllAttributes(attributes) + .startSpan()); + } + + void endSubscriberSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endSubscriberSpan(); + } + + void setSubscriberSpanExpirationResult(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.setSubscriberSpanExpirationResult(); + } + + void setSubscriberSpanException(PubsubMessageWrapper message, Throwable t, String exception) { + if (!enabled) { + return; + } + message.setSubscriberSpanException(t, exception); + } + + /** Creates a span for subscribe concurrency control as a child of the parent subscriber span. */ + void startSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span subscriberSpan = message.getSubscriberSpan(); + if (subscriberSpan != null) { + message.setSubscribeConcurrencyControlSpan( + startChildSpan(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME, subscriberSpan)); + } + } + + void endSubscribeConcurrencyControlSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endSubscribeConcurrencyControlSpan(); + } + + void setSubscribeConcurrencyControlSpanException(PubsubMessageWrapper message, Throwable t) { + if (!enabled) { + return; + } + message.setSubscribeConcurrencyControlSpanException(t); + } + + /** + * Creates a span for subscribe ordering key scheduling as a child of the parent subscriber span. + */ + void startSubscribeSchedulerSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span subscriberSpan = message.getSubscriberSpan(); + if (subscriberSpan != null) { + message.setSubscribeSchedulerSpan( + startChildSpan(SUBSCRIBE_SCHEDULER_SPAN_NAME, subscriberSpan)); + } + } + + void endSubscribeSchedulerSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + message.endSubscribeSchedulerSpan(); + } + + /** Creates a span for subscribe message processing as a child of the parent subscriber span. */ + void startSubscribeProcessSpan(PubsubMessageWrapper message) { + if (!enabled) { + return; + } + Span subscriberSpan = message.getSubscriberSpan(); + if (subscriberSpan != null) { + Span subscribeProcessSpan = + startChildSpan(message.getSubscriptionName() + " process", subscriberSpan); + subscribeProcessSpan.setAttribute( + SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE); + Span publisherSpan = message.getPublisherSpan(); + if (publisherSpan != null) { + subscribeProcessSpan.addLink(publisherSpan.getSpanContext()); + } + message.setSubscribeProcessSpan(subscribeProcessSpan); + } + } + + void endSubscribeProcessSpan(PubsubMessageWrapper message, String action) { + if (!enabled) { + return; + } + message.endSubscribeProcessSpan(action); + } + + /** + * Creates, starts, and returns spans for ModAck, Nack, and Ack RPC requests. Bi-directional links + * to parent subscribe span for sampled messages are added. + */ + Span startSubscribeRpcSpan( + String subscription, + String rpcOperation, + List messages, + int ackDeadline, + boolean isReceiptModack) { + if (!enabled) { + return null; + } + String codeFunction = rpcOperation == "ack" ? "sendAckOperations" : "sendModAckOperations"; + SubscriptionName subscriptionName = SubscriptionName.parse(subscription); + AttributesBuilder attributesBuilder = + createCommonSpanAttributesBuilder( + subscriptionName.getSubscription(), + subscriptionName.getProject(), + codeFunction, + rpcOperation) + .put(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messages.size()); + + // Ack deadline and receipt modack are specific to the modack operation + if (rpcOperation == "modack") { + attributesBuilder + .put(ACK_DEADLINE_ATTR_KEY, ackDeadline) + .put(RECEIPT_MODACK_ATTR_KEY, isReceiptModack); + } + + SpanBuilder rpcSpanBuilder = + tracer + .spanBuilder(subscriptionName.getSubscription() + " " + rpcOperation) + .setSpanKind(SpanKind.CLIENT) + .setAllAttributes(attributesBuilder.build()); + Attributes linkAttributes = + Attributes.builder().put(SemanticAttributes.MESSAGING_OPERATION, rpcOperation).build(); + for (PubsubMessageWrapper message : messages) { + if (message.getSubscriberSpan().getSpanContext().isSampled()) { + rpcSpanBuilder.addLink(message.getSubscriberSpan().getSpanContext(), linkAttributes); + } + } + Span rpcSpan = rpcSpanBuilder.startSpan(); + + for (PubsubMessageWrapper message : messages) { + if (rpcSpan.getSpanContext().isSampled()) { + message.getSubscriberSpan().addLink(rpcSpan.getSpanContext(), linkAttributes); + switch (rpcOperation) { + case "ack": + message.addAckStartEvent(); + break; + case "modack": + message.addModAckStartEvent(); + break; + case "nack": + message.addNackStartEvent(); + break; + } + } + } + return rpcSpan; + } + + /** Ends the given subscribe RPC span if it exists. */ + void endSubscribeRpcSpan(Span rpcSpan) { + if (!enabled) { + return; + } + if (rpcSpan != null) { + rpcSpan.end(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown when handling a + * subscribe-side RPC. + */ + void setSubscribeRpcSpanException(Span rpcSpan, boolean isModack, int ackDeadline, Throwable t) { + if (!enabled) { + return; + } + if (rpcSpan != null) { + String operation = !isModack ? "ack" : (ackDeadline == 0 ? "nack" : "modack"); + rpcSpan.setStatus(StatusCode.ERROR, "Exception thrown on " + operation + " RPC."); + rpcSpan.recordException(t); + rpcSpan.end(); + } + } + + /** Adds the appropriate subscribe-side RPC end event. */ + void addEndRpcEvent( + PubsubMessageWrapper message, boolean rpcSampled, boolean isModack, int ackDeadline) { + if (!enabled || !rpcSampled) { + return; + } + if (!isModack) { + message.addAckEndEvent(); + } else if (ackDeadline == 0) { + message.addNackEndEvent(); + } else { + message.addModAckEndEvent(); + } + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java new file mode 100644 index 000000000..94fd13085 --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/PubsubMessageWrapper.java @@ -0,0 +1,430 @@ +/* + * 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.pubsub.v1; + +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.SubscriptionName; +import com.google.pubsub.v1.TopicName; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.context.propagation.TextMapSetter; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; + +/** + * A wrapper class for a {@link PubsubMessage} object that handles creation and tracking of + * OpenTelemetry {@link Span} objects for different operations that occur during publishing. + */ +public class PubsubMessageWrapper { + private PubsubMessage message; + + private final TopicName topicName; + private final SubscriptionName subscriptionName; + + // Attributes set only for messages received from a streaming pull response. + private final String ackId; + private final int deliveryAttempt; + + private static final String PUBLISH_START_EVENT = "publish start"; + private static final String PUBLISH_END_EVENT = "publish end"; + + private static final String MODACK_START_EVENT = "modack start"; + private static final String MODACK_END_EVENT = "modack end"; + private static final String NACK_START_EVENT = "nack start"; + private static final String NACK_END_EVENT = "nack end"; + private static final String ACK_START_EVENT = "ack start"; + private static final String ACK_END_EVENT = "ack end"; + + private static final String GOOGCLIENT_PREFIX = "googclient_"; + + private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; + + private Span publisherSpan; + private Span publishFlowControlSpan; + private Span publishBatchingSpan; + + private Span subscriberSpan; + private Span subscribeConcurrencyControlSpan; + private Span subscribeSchedulerSpan; + private Span subscribeProcessSpan; + + private PubsubMessageWrapper(Builder builder) { + this.message = builder.message; + this.topicName = builder.topicName; + this.subscriptionName = builder.subscriptionName; + this.ackId = builder.ackId; + this.deliveryAttempt = builder.deliveryAttempt; + } + + static Builder newBuilder(PubsubMessage message, String topicName) { + return new Builder(message, topicName); + } + + static Builder newBuilder( + PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { + return new Builder(message, subscriptionName, ackId, deliveryAttempt); + } + + /** Returns the PubsubMessage associated with this wrapper. */ + PubsubMessage getPubsubMessage() { + return message; + } + + void setPubsubMessage(PubsubMessage message) { + this.message = message; + } + + /** Returns the TopicName for this wrapper as a string. */ + String getTopicName() { + if (topicName != null) { + return topicName.getTopic(); + } + return ""; + } + + String getTopicProject() { + if (topicName != null) { + return topicName.getProject(); + } + return ""; + } + + /** Returns the SubscriptionName for this wrapper as a string. */ + String getSubscriptionName() { + if (subscriptionName != null) { + return subscriptionName.getSubscription(); + } + return ""; + } + + String getSubscriptionProject() { + if (subscriptionName != null) { + return subscriptionName.getProject(); + } + return ""; + } + + String getMessageId() { + return message.getMessageId(); + } + + String getAckId() { + return ackId; + } + + int getDataSize() { + return message.getData().size(); + } + + String getOrderingKey() { + return message.getOrderingKey(); + } + + int getDeliveryAttempt() { + return deliveryAttempt; + } + + Span getPublisherSpan() { + return publisherSpan; + } + + void setPublisherSpan(Span span) { + this.publisherSpan = span; + } + + void setPublishFlowControlSpan(Span span) { + this.publishFlowControlSpan = span; + } + + void setPublishBatchingSpan(Span span) { + this.publishBatchingSpan = span; + } + + Span getSubscriberSpan() { + return subscriberSpan; + } + + void setSubscriberSpan(Span span) { + this.subscriberSpan = span; + } + + void setSubscribeConcurrencyControlSpan(Span span) { + this.subscribeConcurrencyControlSpan = span; + } + + void setSubscribeSchedulerSpan(Span span) { + this.subscribeSchedulerSpan = span; + } + + void setSubscribeProcessSpan(Span span) { + this.subscribeProcessSpan = span; + } + + /** Creates a publish start event that is tied to the publish RPC span time. */ + void addPublishStartEvent() { + if (publisherSpan != null) { + publisherSpan.addEvent(PUBLISH_START_EVENT); + } + } + + /** + * Sets the message ID attribute in the publisher parent span. This is called after the publish + * RPC returns with a message ID. + */ + void setPublisherMessageIdSpanAttribute(String messageId) { + if (publisherSpan != null) { + publisherSpan.setAttribute(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId); + } + } + + /** Ends the publisher parent span if it exists. */ + void endPublisherSpan() { + if (publisherSpan != null) { + publisherSpan.addEvent(PUBLISH_END_EVENT); + publisherSpan.end(); + } + } + + /** Ends the publish flow control span if it exists. */ + void endPublishFlowControlSpan() { + if (publishFlowControlSpan != null) { + publishFlowControlSpan.end(); + } + } + + /** Ends the publish batching span if it exists. */ + void endPublishBatchingSpan() { + if (publishBatchingSpan != null) { + publishBatchingSpan.end(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown during flow control. + */ + void setPublishFlowControlSpanException(Throwable t) { + if (publishFlowControlSpan != null) { + publishFlowControlSpan.setStatus( + StatusCode.ERROR, "Exception thrown during publish flow control."); + publishFlowControlSpan.recordException(t); + endAllPublishSpans(); + } + } + + /** + * Creates start and end events for ModAcks, Nacks, and Acks that are tied to the corresponding + * RPC span start and end times. + */ + void addModAckStartEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(MODACK_START_EVENT); + } + } + + void addModAckEndEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(MODACK_END_EVENT); + } + } + + void addNackStartEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(NACK_START_EVENT); + } + } + + void addNackEndEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(NACK_END_EVENT); + } + } + + void addAckStartEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(ACK_START_EVENT); + } + } + + void addAckEndEvent() { + if (subscriberSpan != null) { + subscriberSpan.addEvent(ACK_END_EVENT); + } + } + + /** Ends the subscriber parent span if exists. */ + void endSubscriberSpan() { + if (subscriberSpan != null) { + subscriberSpan.end(); + } + } + + /** Ends the subscribe concurreny control span if exists. */ + void endSubscribeConcurrencyControlSpan() { + if (subscribeConcurrencyControlSpan != null) { + subscribeConcurrencyControlSpan.end(); + } + } + + /** Ends the subscribe scheduler span if exists. */ + void endSubscribeSchedulerSpan() { + if (subscribeSchedulerSpan != null) { + subscribeSchedulerSpan.end(); + } + } + + /** + * Ends the subscribe process span if it exists, creates an event with the appropriate result, and + * sets the result on the parent subscriber span. + */ + void endSubscribeProcessSpan(String action) { + if (subscribeProcessSpan != null) { + subscribeProcessSpan.addEvent(action + " called"); + subscribeProcessSpan.end(); + subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, action); + } + } + + /** Sets an exception on the subscriber span during Ack/ModAck/Nack failures */ + void setSubscriberSpanException(Throwable t, String exception) { + if (subscriberSpan != null) { + subscriberSpan.setStatus(StatusCode.ERROR, exception); + subscriberSpan.recordException(t); + endAllSubscribeSpans(); + } + } + + /** Sets result of the parent subscriber span to expired and ends its. */ + void setSubscriberSpanExpirationResult() { + if (subscriberSpan != null) { + subscriberSpan.setAttribute(MESSAGE_RESULT_ATTR_KEY, "expired"); + endSubscriberSpan(); + } + } + + /** + * Sets an error status and records an exception when an exception is thrown subscriber + * concurrency control. + */ + void setSubscribeConcurrencyControlSpanException(Throwable t) { + if (subscribeConcurrencyControlSpan != null) { + subscribeConcurrencyControlSpan.setStatus( + StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); + subscribeConcurrencyControlSpan.recordException(t); + endAllSubscribeSpans(); + } + } + + /** Ends all publisher-side spans associated with this message wrapper. */ + private void endAllPublishSpans() { + endPublishFlowControlSpan(); + endPublishBatchingSpan(); + endPublisherSpan(); + } + + /** Ends all subscriber-side spans associated with this message wrapper. */ + private void endAllSubscribeSpans() { + endSubscribeConcurrencyControlSpan(); + endSubscribeSchedulerSpan(); + endSubscriberSpan(); + } + + /** + * Injects the span context into the attributes of a Pub/Sub message for propagation to the + * subscriber client. + */ + void injectSpanContext() { + TextMapSetter injectMessageAttributes = + new TextMapSetter() { + @Override + public void set(PubsubMessageWrapper carrier, String key, String value) { + PubsubMessage newMessage = + PubsubMessage.newBuilder(carrier.message) + .putAttributes(GOOGCLIENT_PREFIX + key, value) + .build(); + carrier.message = newMessage; + } + }; + W3CTraceContextPropagator.getInstance() + .inject(Context.current().with(publisherSpan), this, injectMessageAttributes); + } + + /** + * Extracts the span context from the attributes of a Pub/Sub message and creates the parent + * subscriber span using that context. + */ + Context extractSpanContext(Attributes attributes) { + TextMapGetter extractMessageAttributes = + new TextMapGetter() { + @Override + public String get(PubsubMessageWrapper carrier, String key) { + return carrier.message.getAttributesOrDefault(GOOGCLIENT_PREFIX + key, ""); + } + + public Iterable keys(PubsubMessageWrapper carrier) { + return carrier.message.getAttributesMap().keySet(); + } + }; + Context context = + W3CTraceContextPropagator.getInstance() + .extract(Context.current(), this, extractMessageAttributes); + return context; + } + + /** Builder of {@link PubsubMessageWrapper PubsubMessageWrapper}. */ + static final class Builder { + private PubsubMessage message = null; + private TopicName topicName = null; + private SubscriptionName subscriptionName = null; + private String ackId = null; + private int deliveryAttempt = 0; + + public Builder(PubsubMessage message, String topicName) { + this.message = message; + if (topicName != null) { + this.topicName = TopicName.parse(topicName); + } + } + + public Builder( + PubsubMessage message, String subscriptionName, String ackId, int deliveryAttempt) { + this.message = message; + if (subscriptionName != null) { + this.subscriptionName = SubscriptionName.parse(subscriptionName); + } + this.ackId = ackId; + this.deliveryAttempt = deliveryAttempt; + } + + public Builder( + PubsubMessage message, + SubscriptionName subscriptionName, + String ackId, + int deliveryAttempt) { + this.message = message; + this.subscriptionName = subscriptionName; + this.ackId = ackId; + this.deliveryAttempt = deliveryAttempt; + } + + public PubsubMessageWrapper build() { + return new PubsubMessageWrapper(this); + } + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java new file mode 100644 index 000000000..b4433f41e --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/v1/OpenTelemetryTest.java @@ -0,0 +1,669 @@ +/* + * 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.pubsub.v1; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.google.protobuf.ByteString; +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.SubscriptionName; +import com.google.pubsub.v1.TopicName; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.sdk.testing.assertj.AttributesAssert; +import io.opentelemetry.sdk.testing.assertj.EventDataAssert; +import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.testing.junit4.OpenTelemetryRule; +import io.opentelemetry.sdk.trace.data.LinkData; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.data.StatusData; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.Arrays; +import java.util.List; +import org.junit.Test; + +public class OpenTelemetryTest { + private static final TopicName FULL_TOPIC_NAME = + TopicName.parse("projects/test-project/topics/test-topic"); + private static final SubscriptionName FULL_SUBSCRIPTION_NAME = + SubscriptionName.parse("projects/test-project/subscriptions/test-sub"); + private static final String PROJECT_NAME = "test-project"; + private static final String ORDERING_KEY = "abc"; + private static final String MESSAGE_ID = "m0"; + private static final String ACK_ID = "def"; + private static final int DELIVERY_ATTEMPT = 1; + private static final int ACK_DEADLINE = 10; + private static final boolean EXACTLY_ONCE_ENABLED = true; + + private static final String PUBLISHER_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " create"; + private static final String PUBLISH_FLOW_CONTROL_SPAN_NAME = "publisher flow control"; + private static final String PUBLISH_BATCHING_SPAN_NAME = "publisher batching"; + private static final String PUBLISH_RPC_SPAN_NAME = FULL_TOPIC_NAME.getTopic() + " publish"; + private static final String PUBLISH_START_EVENT = "publish start"; + private static final String PUBLISH_END_EVENT = "publish end"; + + private static final String SUBSCRIBER_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " subscribe"; + private static final String SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME = + "subscriber concurrency control"; + private static final String SUBSCRIBE_SCHEDULER_SPAN_NAME = "subscriber scheduler"; + private static final String SUBSCRIBE_PROCESS_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " process"; + private static final String SUBSCRIBE_MODACK_RPC_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " modack"; + private static final String SUBSCRIBE_ACK_RPC_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " ack"; + private static final String SUBSCRIBE_NACK_RPC_SPAN_NAME = + FULL_SUBSCRIPTION_NAME.getSubscription() + " nack"; + + private static final String PROCESS_ACTION = "ack"; + private static final String MODACK_START_EVENT = "modack start"; + private static final String MODACK_END_EVENT = "modack end"; + private static final String NACK_START_EVENT = "nack start"; + private static final String NACK_END_EVENT = "nack end"; + private static final String ACK_START_EVENT = "ack start"; + private static final String ACK_END_EVENT = "ack end"; + + private static final String MESSAGING_SYSTEM_VALUE = "gcp_pubsub"; + private static final String PROJECT_ATTR_KEY = "gcp.project_id"; + private static final String MESSAGE_SIZE_ATTR_KEY = "messaging.message.body.size"; + private static final String ORDERING_KEY_ATTR_KEY = "messaging.gcp_pubsub.message.ordering_key"; + private static final String ACK_DEADLINE_ATTR_KEY = "messaging.gcp_pubsub.message.ack_deadline"; + private static final String RECEIPT_MODACK_ATTR_KEY = "messaging.gcp_pubsub.is_receipt_modack"; + private static final String MESSAGE_ACK_ID_ATTR_KEY = "messaging.gcp_pubsub.message.ack_id"; + private static final String MESSAGE_EXACTLY_ONCE_ATTR_KEY = + "messaging.gcp_pubsub.message.exactly_once_delivery"; + private static final String MESSAGE_RESULT_ATTR_KEY = "messaging.gcp_pubsub.result"; + private static final String MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY = + "messaging.gcp_pubsub.message.delivery_attempt"; + + private static final String TRACEPARENT_ATTRIBUTE = "googclient_traceparent"; + + private static final OpenTelemetryRule openTelemetryTesting = OpenTelemetryRule.create(); + + @Test + public void testPublishSpansSuccess() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); + List messageWrappers = Arrays.asList(messageWrapper); + + long messageSize = messageWrapper.getPubsubMessage().getData().size(); + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + // Call all span start/end methods in the expected order + tracer.startPublisherSpan(messageWrapper); + tracer.startPublishFlowControlSpan(messageWrapper); + tracer.endPublishFlowControlSpan(messageWrapper); + tracer.startPublishBatchingSpan(messageWrapper); + tracer.endPublishBatchingSpan(messageWrapper); + Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); + tracer.endPublishRpcSpan(publishRpcSpan); + tracer.setPublisherMessageIdSpanAttribute(messageWrapper, MESSAGE_ID); + tracer.endPublisherSpan(messageWrapper); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(4, allSpans.size()); + SpanData flowControlSpanData = allSpans.get(0); + SpanData batchingSpanData = allSpans.get(1); + SpanData publishRpcSpanData = allSpans.get(2); + SpanData publisherSpanData = allSpans.get(3); + + SpanDataAssert flowControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(flowControlSpanData); + flowControlSpanDataAssert + .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) + .hasParent(publisherSpanData) + .hasEnded(); + + SpanDataAssert batchingSpanDataAssert = OpenTelemetryAssertions.assertThat(batchingSpanData); + batchingSpanDataAssert + .hasName(PUBLISH_BATCHING_SPAN_NAME) + .hasParent(publisherSpanData) + .hasEnded(); + + // Check span data, links, and attributes for the publish RPC span + SpanDataAssert publishRpcSpanDataAssert = + OpenTelemetryAssertions.assertThat(publishRpcSpanData); + publishRpcSpanDataAssert + .hasName(PUBLISH_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List publishRpcLinks = publishRpcSpanData.getLinks(); + assertEquals(messageWrappers.size(), publishRpcLinks.size()); + assertEquals(publisherSpanData.getSpanContext(), publishRpcLinks.get(0).getSpanContext()); + + assertEquals(6, publishRpcSpanData.getAttributes().size()); + AttributesAssert publishRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(publishRpcSpanData.getAttributes()); + publishRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "publishCall") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "publish") + .containsEntry(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, messageWrappers.size()); + + // Check span data, events, links, and attributes for the publisher create span + SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publisherSpanDataAssert + .hasName(PUBLISHER_SPAN_NAME) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasEnded(); + + assertEquals(2, publisherSpanData.getEvents().size()); + EventDataAssert startEventAssert = + OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(0)); + startEventAssert.hasName(PUBLISH_START_EVENT); + EventDataAssert endEventAssert = + OpenTelemetryAssertions.assertThat(publisherSpanData.getEvents().get(1)); + endEventAssert.hasName(PUBLISH_END_EVENT); + + List publisherLinks = publisherSpanData.getLinks(); + assertEquals(1, publisherLinks.size()); + assertEquals(publishRpcSpanData.getSpanContext(), publisherLinks.get(0).getSpanContext()); + + assertEquals(8, publisherSpanData.getAttributes().size()); + AttributesAssert publisherSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(publisherSpanData.getAttributes()); + publisherSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry(SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_TOPIC_NAME.getTopic()) + .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "publish") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "create") + .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) + .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) + .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); + + // Check that the message has the attribute containing the trace context. + PubsubMessage message = messageWrapper.getPubsubMessage(); + assertEquals(1, message.getAttributesMap().size()); + assertTrue(message.containsAttributes(TRACEPARENT_ATTRIBUTE)); + assertTrue( + message + .getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "") + .contains(publisherSpanData.getTraceId())); + assertTrue( + message + .getAttributesOrDefault(TRACEPARENT_ATTRIBUTE, "") + .contains(publisherSpanData.getSpanId())); + } + + @Test + public void testPublishFlowControlSpanFailure() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startPublisherSpan(messageWrapper); + tracer.startPublishFlowControlSpan(messageWrapper); + + Exception e = new Exception("test-exception"); + tracer.setPublishFlowControlSpanException(messageWrapper, e); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(2, allSpans.size()); + SpanData flowControlSpanData = allSpans.get(0); + SpanData publisherSpanData = allSpans.get(1); + + SpanDataAssert flowControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(flowControlSpanData); + StatusData expectedStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown during publish flow control."); + flowControlSpanDataAssert + .hasName(PUBLISH_FLOW_CONTROL_SPAN_NAME) + .hasParent(publisherSpanData) + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + + SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publisherSpanDataAssert + .hasName(PUBLISHER_SPAN_NAME) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasEnded(); + } + + @Test + public void testPublishRpcSpanFailure() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); + + List messageWrappers = Arrays.asList(messageWrapper); + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startPublisherSpan(messageWrapper); + Span publishRpcSpan = tracer.startPublishRpcSpan(FULL_TOPIC_NAME.toString(), messageWrappers); + + Exception e = new Exception("test-exception"); + tracer.setPublishRpcSpanException(publishRpcSpan, e); + tracer.endPublisherSpan(messageWrapper); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(2, allSpans.size()); + SpanData rpcSpanData = allSpans.get(0); + SpanData publisherSpanData = allSpans.get(1); + + SpanDataAssert rpcSpanDataAssert = OpenTelemetryAssertions.assertThat(rpcSpanData); + StatusData expectedStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on publish RPC."); + rpcSpanDataAssert + .hasName(PUBLISH_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + + SpanDataAssert publisherSpanDataAssert = OpenTelemetryAssertions.assertThat(publisherSpanData); + publisherSpanDataAssert + .hasName(PUBLISHER_SPAN_NAME) + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasEnded(); + } + + @Test + public void testSubscribeSpansSuccess() { + openTelemetryTesting.clearSpans(); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + PubsubMessageWrapper publishMessageWrapper = + PubsubMessageWrapper.newBuilder(getPubsubMessage(), FULL_TOPIC_NAME.toString()).build(); + // Initialize the Publisher span to inject the context in the message + tracer.startPublisherSpan(publishMessageWrapper); + tracer.endPublisherSpan(publishMessageWrapper); + + PubsubMessage publishedMessage = + publishMessageWrapper.getPubsubMessage().toBuilder().setMessageId(MESSAGE_ID).build(); + PubsubMessageWrapper subscribeMessageWrapper = + PubsubMessageWrapper.newBuilder( + publishedMessage, FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, 1) + .build(); + List subscribeMessageWrappers = Arrays.asList(subscribeMessageWrapper); + + long messageSize = subscribeMessageWrapper.getPubsubMessage().getData().size(); + + // Call all span start/end methods in the expected order + tracer.startSubscriberSpan(subscribeMessageWrapper, EXACTLY_ONCE_ENABLED); + tracer.startSubscribeConcurrencyControlSpan(subscribeMessageWrapper); + tracer.endSubscribeConcurrencyControlSpan(subscribeMessageWrapper); + tracer.startSubscribeSchedulerSpan(subscribeMessageWrapper); + tracer.endSubscribeSchedulerSpan(subscribeMessageWrapper); + tracer.startSubscribeProcessSpan(subscribeMessageWrapper); + tracer.endSubscribeProcessSpan(subscribeMessageWrapper, PROCESS_ACTION); + Span subscribeModackRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), + "modack", + subscribeMessageWrappers, + ACK_DEADLINE, + true); + tracer.endSubscribeRpcSpan(subscribeModackRpcSpan); + tracer.addEndRpcEvent(subscribeMessageWrapper, true, true, ACK_DEADLINE); + Span subscribeAckRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "ack", subscribeMessageWrappers, 0, false); + tracer.endSubscribeRpcSpan(subscribeAckRpcSpan); + tracer.addEndRpcEvent(subscribeMessageWrapper, true, false, 0); + Span subscribeNackRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "nack", subscribeMessageWrappers, 0, false); + tracer.endSubscribeRpcSpan(subscribeNackRpcSpan); + tracer.addEndRpcEvent(subscribeMessageWrapper, true, true, 0); + tracer.endSubscriberSpan(subscribeMessageWrapper); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(8, allSpans.size()); + + SpanData publisherSpanData = allSpans.get(0); + SpanData concurrencyControlSpanData = allSpans.get(1); + SpanData schedulerSpanData = allSpans.get(2); + SpanData processSpanData = allSpans.get(3); + SpanData modackRpcSpanData = allSpans.get(4); + SpanData ackRpcSpanData = allSpans.get(5); + SpanData nackRpcSpanData = allSpans.get(6); + SpanData subscriberSpanData = allSpans.get(7); + + SpanDataAssert concurrencyControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(concurrencyControlSpanData); + concurrencyControlSpanDataAssert + .hasName(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasEnded(); + + SpanDataAssert schedulerSpanDataAssert = OpenTelemetryAssertions.assertThat(schedulerSpanData); + schedulerSpanDataAssert + .hasName(SUBSCRIBE_SCHEDULER_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasEnded(); + + SpanDataAssert processSpanDataAssert = OpenTelemetryAssertions.assertThat(processSpanData); + processSpanDataAssert + .hasName(SUBSCRIBE_PROCESS_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasEnded(); + + assertEquals(1, processSpanData.getEvents().size()); + EventDataAssert actionCalledEventAssert = + OpenTelemetryAssertions.assertThat(processSpanData.getEvents().get(0)); + actionCalledEventAssert.hasName(PROCESS_ACTION + " called"); + + // Check span data, links, and attributes for the modack RPC span + SpanDataAssert modackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(modackRpcSpanData); + modackRpcSpanDataAssert + .hasName(SUBSCRIBE_MODACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List modackRpcLinks = modackRpcSpanData.getLinks(); + assertEquals(subscribeMessageWrappers.size(), modackRpcLinks.size()); + assertEquals(subscriberSpanData.getSpanContext(), modackRpcLinks.get(0).getSpanContext()); + + assertEquals(8, modackRpcSpanData.getAttributes().size()); + AttributesAssert modackRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(modackRpcSpanData.getAttributes()); + modackRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "modack") + .containsEntry( + SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()) + .containsEntry(ACK_DEADLINE_ATTR_KEY, 10) + .containsEntry(RECEIPT_MODACK_ATTR_KEY, true); + + // Check span data, links, and attributes for the ack RPC span + SpanDataAssert ackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(ackRpcSpanData); + ackRpcSpanDataAssert + .hasName(SUBSCRIBE_ACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List ackRpcLinks = ackRpcSpanData.getLinks(); + assertEquals(subscribeMessageWrappers.size(), ackRpcLinks.size()); + assertEquals(subscriberSpanData.getSpanContext(), ackRpcLinks.get(0).getSpanContext()); + + assertEquals(6, ackRpcSpanData.getAttributes().size()); + AttributesAssert ackRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(ackRpcSpanData.getAttributes()); + ackRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendAckOperations") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "ack") + .containsEntry( + SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); + + // Check span data, links, and attributes for the nack RPC span + SpanDataAssert nackRpcSpanDataAssert = OpenTelemetryAssertions.assertThat(nackRpcSpanData); + nackRpcSpanDataAssert + .hasName(SUBSCRIBE_NACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasEnded(); + + List nackRpcLinks = nackRpcSpanData.getLinks(); + assertEquals(subscribeMessageWrappers.size(), nackRpcLinks.size()); + assertEquals(subscriberSpanData.getSpanContext(), nackRpcLinks.get(0).getSpanContext()); + + assertEquals(6, nackRpcSpanData.getAttributes().size()); + AttributesAssert nackRpcSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(nackRpcSpanData.getAttributes()); + nackRpcSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, FULL_TOPIC_NAME.getProject()) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "sendModAckOperations") + .containsEntry(SemanticAttributes.MESSAGING_OPERATION, "nack") + .containsEntry( + SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, subscribeMessageWrappers.size()); + + // Check span data, events, links, and attributes for the publisher create span + SpanDataAssert subscriberSpanDataAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData); + subscriberSpanDataAssert + .hasName(SUBSCRIBER_SPAN_NAME) + .hasKind(SpanKind.CONSUMER) + .hasParent(publisherSpanData) + .hasEnded(); + + assertEquals(6, subscriberSpanData.getEvents().size()); + EventDataAssert startModackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(0)); + startModackEventAssert.hasName(MODACK_START_EVENT); + EventDataAssert endModackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(1)); + endModackEventAssert.hasName(MODACK_END_EVENT); + EventDataAssert startAckEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(2)); + startAckEventAssert.hasName(ACK_START_EVENT); + EventDataAssert endAckEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(3)); + endAckEventAssert.hasName(ACK_END_EVENT); + EventDataAssert startNackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(4)); + startNackEventAssert.hasName(NACK_START_EVENT); + EventDataAssert endNackEventAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getEvents().get(5)); + endNackEventAssert.hasName(NACK_END_EVENT); + + List subscriberLinks = subscriberSpanData.getLinks(); + assertEquals(3, subscriberLinks.size()); + assertEquals(modackRpcSpanData.getSpanContext(), subscriberLinks.get(0).getSpanContext()); + assertEquals(ackRpcSpanData.getSpanContext(), subscriberLinks.get(1).getSpanContext()); + assertEquals(nackRpcSpanData.getSpanContext(), subscriberLinks.get(2).getSpanContext()); + + assertEquals(11, subscriberSpanData.getAttributes().size()); + AttributesAssert subscriberSpanAttributesAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData.getAttributes()); + subscriberSpanAttributesAssert + .containsEntry(SemanticAttributes.MESSAGING_SYSTEM, MESSAGING_SYSTEM_VALUE) + .containsEntry( + SemanticAttributes.MESSAGING_DESTINATION_NAME, FULL_SUBSCRIPTION_NAME.getSubscription()) + .containsEntry(PROJECT_ATTR_KEY, PROJECT_NAME) + .containsEntry(SemanticAttributes.CODE_FUNCTION, "onResponse") + .containsEntry(MESSAGE_SIZE_ATTR_KEY, messageSize) + .containsEntry(ORDERING_KEY_ATTR_KEY, ORDERING_KEY) + .containsEntry(MESSAGE_ACK_ID_ATTR_KEY, ACK_ID) + .containsEntry(MESSAGE_DELIVERY_ATTEMPT_ATTR_KEY, DELIVERY_ATTEMPT) + .containsEntry(MESSAGE_EXACTLY_ONCE_ATTR_KEY, EXACTLY_ONCE_ENABLED) + .containsEntry(MESSAGE_RESULT_ATTR_KEY, PROCESS_ACTION) + .containsEntry(SemanticAttributes.MESSAGING_MESSAGE_ID, MESSAGE_ID); + } + + @Test + public void testSubscribeConcurrencyControlSpanFailure() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder( + getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) + .build(); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); + tracer.startSubscribeConcurrencyControlSpan(messageWrapper); + + Exception e = new Exception("test-exception"); + tracer.setSubscribeConcurrencyControlSpanException(messageWrapper, e); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(2, allSpans.size()); + SpanData concurrencyControlSpanData = allSpans.get(0); + SpanData subscriberSpanData = allSpans.get(1); + + SpanDataAssert concurrencyControlSpanDataAssert = + OpenTelemetryAssertions.assertThat(concurrencyControlSpanData); + StatusData expectedStatus = + StatusData.create( + StatusCode.ERROR, "Exception thrown during subscribe concurrency control."); + concurrencyControlSpanDataAssert + .hasName(SUBSCRIBE_CONCURRENCY_CONTROL_SPAN_NAME) + .hasParent(subscriberSpanData) + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + + SpanDataAssert subscriberSpanDataAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData); + subscriberSpanDataAssert + .hasName(SUBSCRIBER_SPAN_NAME) + .hasKind(SpanKind.CONSUMER) + .hasNoParent() + .hasEnded(); + } + + @Test + public void testSubscriberSpanFailure() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder( + getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) + .build(); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); + + Exception e = new Exception("test-exception"); + tracer.setSubscriberSpanException(messageWrapper, e, "Test exception"); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(1, allSpans.size()); + SpanData subscriberSpanData = allSpans.get(0); + + StatusData expectedStatus = StatusData.create(StatusCode.ERROR, "Test exception"); + SpanDataAssert subscriberSpanDataAssert = + OpenTelemetryAssertions.assertThat(subscriberSpanData); + subscriberSpanDataAssert + .hasName(SUBSCRIBER_SPAN_NAME) + .hasKind(SpanKind.CONSUMER) + .hasNoParent() + .hasStatus(expectedStatus) + .hasException(e) + .hasEnded(); + } + + @Test + public void testSubscribeRpcSpanFailures() { + openTelemetryTesting.clearSpans(); + + PubsubMessageWrapper messageWrapper = + PubsubMessageWrapper.newBuilder( + getPubsubMessage(), FULL_SUBSCRIPTION_NAME.toString(), ACK_ID, DELIVERY_ATTEMPT) + .build(); + List messageWrappers = Arrays.asList(messageWrapper); + + Tracer openTelemetryTracer = openTelemetryTesting.getOpenTelemetry().getTracer("test"); + OpenTelemetryPubsubTracer tracer = new OpenTelemetryPubsubTracer(openTelemetryTracer, true); + + tracer.startSubscriberSpan(messageWrapper, EXACTLY_ONCE_ENABLED); + Span subscribeModackRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "modack", messageWrappers, ACK_DEADLINE, true); + Span subscribeAckRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "ack", messageWrappers, 0, false); + Span subscribeNackRpcSpan = + tracer.startSubscribeRpcSpan( + FULL_SUBSCRIPTION_NAME.toString(), "nack", messageWrappers, 0, false); + + Exception e = new Exception("test-exception"); + tracer.setSubscribeRpcSpanException(subscribeModackRpcSpan, true, ACK_DEADLINE, e); + tracer.setSubscribeRpcSpanException(subscribeAckRpcSpan, false, 0, e); + tracer.setSubscribeRpcSpanException(subscribeNackRpcSpan, true, 0, e); + tracer.endSubscriberSpan(messageWrapper); + + List allSpans = openTelemetryTesting.getSpans(); + assertEquals(4, allSpans.size()); + SpanData modackSpanData = allSpans.get(0); + SpanData ackSpanData = allSpans.get(1); + SpanData nackSpanData = allSpans.get(2); + SpanData subscriberSpanData = allSpans.get(3); + + StatusData expectedModackStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on modack RPC."); + SpanDataAssert modackSpanDataAssert = OpenTelemetryAssertions.assertThat(modackSpanData); + modackSpanDataAssert + .hasName(SUBSCRIBE_MODACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasStatus(expectedModackStatus) + .hasException(e) + .hasEnded(); + + StatusData expectedAckStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on ack RPC."); + SpanDataAssert ackSpanDataAssert = OpenTelemetryAssertions.assertThat(ackSpanData); + ackSpanDataAssert + .hasName(SUBSCRIBE_ACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasStatus(expectedAckStatus) + .hasException(e) + .hasEnded(); + + StatusData expectedNackStatus = + StatusData.create(StatusCode.ERROR, "Exception thrown on nack RPC."); + SpanDataAssert nackSpanDataAssert = OpenTelemetryAssertions.assertThat(nackSpanData); + nackSpanDataAssert + .hasName(SUBSCRIBE_NACK_RPC_SPAN_NAME) + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasStatus(expectedNackStatus) + .hasException(e) + .hasEnded(); + } + + private PubsubMessage getPubsubMessage() { + return PubsubMessage.newBuilder() + .setData(ByteString.copyFromUtf8("test-data")) + .setOrderingKey(ORDERING_KEY) + .build(); + } +} From 149f3e73cd27767ffbf18f182f5e7294b74d16e1 Mon Sep 17 00:00:00 2001 From: Mike Prieto Date: Mon, 30 Sep 2024 22:10:52 +0000 Subject: [PATCH 45/46] feat: Remove OpenTelemetry samples as the samples use a released library version to run --- .../pubsub/OpenTelemetryPublisherExample.java | 99 ----------------- .../OpenTelemetrySubscriberExample.java | 100 ------------------ 2 files changed, 199 deletions(-) delete mode 100644 samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java delete mode 100644 samples/snippets/src/main/java/pubsub/OpenTelemetrySubscriberExample.java diff --git a/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java b/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java deleted file mode 100644 index 606c6fe36..000000000 --- a/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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 pubsub; - -// [START pubsub_publish_otel_tracing] - -import com.google.api.core.ApiFuture; -import com.google.cloud.opentelemetry.trace.TraceConfiguration; -import com.google.cloud.opentelemetry.trace.TraceExporter; -import com.google.cloud.pubsub.v1.Publisher; -import com.google.protobuf.ByteString; -import com.google.pubsub.v1.PubsubMessage; -import com.google.pubsub.v1.TopicName; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import io.opentelemetry.sdk.trace.samplers.Sampler; -import io.opentelemetry.semconv.ResourceAttributes; -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; - -public class OpenTelemetryPublisherExample { - public static void main(String... args) throws Exception { - // TODO(developer): Replace these variables before running the sample. - String projectId = "your-project-id"; - String topicId = "your-topic-id"; - - openTelemetryPublisherExample(projectId, topicId); - } - - public static void openTelemetryPublisherExample(String projectId, String topicId) - throws IOException, ExecutionException, InterruptedException { - Resource resource = - Resource.getDefault().toBuilder() - .put(ResourceAttributes.SERVICE_NAME, "publisher-example") - .build(); - - // Creates a Cloud Trace exporter. - SpanExporter traceExporter = - TraceExporter.createWithConfiguration( - TraceConfiguration.builder().setProjectId(projectId).build()); - - SdkTracerProvider sdkTracerProvider = - SdkTracerProvider.builder() - .setResource(resource) - .addSpanProcessor(SimpleSpanProcessor.create(traceExporter)) - .setSampler(Sampler.alwaysOn()) - .build(); - - OpenTelemetry openTelemetry = - OpenTelemetrySdk.builder().setTracerProvider(sdkTracerProvider).buildAndRegisterGlobal(); - - TopicName topicName = TopicName.of(projectId, topicId); - - Publisher publisher = null; - try { - // Create a publisher instance with the created OpenTelemetry object and enabling tracing. - publisher = - Publisher.newBuilder(topicName) - .setOpenTelemetry(openTelemetry) - .setEnableOpenTelemetryTracing(true) - .build(); - - String message = "Hello World!"; - ByteString data = ByteString.copyFromUtf8(message); - PubsubMessage pubsubMessage = PubsubMessage.newBuilder().setData(data).build(); - - // Once published, returns a server-assigned message id (unique within the topic) - ApiFuture messageIdFuture = publisher.publish(pubsubMessage); - String messageId = messageIdFuture.get(); - System.out.println("Published message ID: " + messageId); - } finally { - if (publisher != null) { - // When finished with the publisher, shutdown to free up resources. - publisher.shutdown(); - publisher.awaitTermination(1, TimeUnit.MINUTES); - } - } - } -} -// [END pubsub_publish_otel_tracing] diff --git a/samples/snippets/src/main/java/pubsub/OpenTelemetrySubscriberExample.java b/samples/snippets/src/main/java/pubsub/OpenTelemetrySubscriberExample.java deleted file mode 100644 index f78c38d19..000000000 --- a/samples/snippets/src/main/java/pubsub/OpenTelemetrySubscriberExample.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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 pubsub; - -// [START pubsub_subscribe_otel_tracing] - -import com.google.cloud.opentelemetry.trace.TraceConfiguration; -import com.google.cloud.opentelemetry.trace.TraceExporter; -import com.google.cloud.pubsub.v1.AckReplyConsumer; -import com.google.cloud.pubsub.v1.MessageReceiver; -import com.google.cloud.pubsub.v1.Subscriber; -import com.google.pubsub.v1.ProjectSubscriptionName; -import com.google.pubsub.v1.PubsubMessage; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import io.opentelemetry.sdk.trace.samplers.Sampler; -import io.opentelemetry.semconv.ResourceAttributes; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -public class OpenTelemetrySubscriberExample { - public static void main(String... args) throws Exception { - // TODO(developer): Replace these variables before running the sample. - String projectId = "your-project-id"; - String subscriptionId = "your-subscription-id"; - - openTelemetrySubscriberExample(projectId, subscriptionId); - } - - public static void openTelemetrySubscriberExample(String projectId, String subscriptionId) { - Resource resource = - Resource.getDefault().toBuilder() - .put(ResourceAttributes.SERVICE_NAME, "subscriber-example") - .build(); - - // Creates a Cloud Trace exporter. - SpanExporter traceExporter = - TraceExporter.createWithConfiguration( - TraceConfiguration.builder().setProjectId(projectId).build()); - - SdkTracerProvider sdkTracerProvider = - SdkTracerProvider.builder() - .setResource(resource) - .addSpanProcessor(SimpleSpanProcessor.create(traceExporter)) - .setSampler(Sampler.alwaysOn()) - .build(); - - OpenTelemetry openTelemetry = - OpenTelemetrySdk.builder().setTracerProvider(sdkTracerProvider).buildAndRegisterGlobal(); - - ProjectSubscriptionName subscriptionName = - ProjectSubscriptionName.of(projectId, subscriptionId); - - // Instantiate an asynchronous message receiver. - MessageReceiver receiver = - (PubsubMessage message, AckReplyConsumer consumer) -> { - // Handle incoming message, then ack the received message. - System.out.println("Id: " + message.getMessageId()); - System.out.println("Data: " + message.getData().toStringUtf8()); - consumer.ack(); - }; - - Subscriber subscriber = null; - try { - subscriber = - Subscriber.newBuilder(subscriptionName, receiver) - .setOpenTelemetry(openTelemetry) - .setEnableOpenTelemetryTracing(true) - .build(); - - // Start the subscriber. - subscriber.startAsync().awaitRunning(); - System.out.printf("Listening for messages on %s:\n", subscriptionName.toString()); - // Allow the subscriber to run for 30s unless an unrecoverable error occurs. - subscriber.awaitTerminated(30, TimeUnit.SECONDS); - } catch (TimeoutException timeoutException) { - // Shut down the subscriber after 30s. Stop receiving messages. - subscriber.stopAsync(); - } - } -} - // [END pubsub_subscribe_otel_tracing] From 425964678d93530bab06fdb692e0985a4b69812f Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Mon, 30 Sep 2024 22:13:53 +0000 Subject: [PATCH 46/46] chore: generate libraries at Mon Sep 30 22:11:14 UTC 2024 --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index c107aeac3..c5729ff0e 100644 --- a/README.md +++ b/README.md @@ -273,8 +273,6 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-pubsub/tree/m | List Subscriptions In Project Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/ListSubscriptionsInProjectExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/ListSubscriptionsInProjectExample.java) | | List Subscriptions In Topic Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/ListSubscriptionsInTopicExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/ListSubscriptionsInTopicExample.java) | | List Topics Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/ListTopicsExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/ListTopicsExample.java) | -| Open Telemetry Publisher Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/OpenTelemetryPublisherExample.java) | -| Open Telemetry Subscriber Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/OpenTelemetrySubscriberExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/OpenTelemetrySubscriberExample.java) | | Optimistic Subscribe Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/OptimisticSubscribeExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/OptimisticSubscribeExample.java) | | Publish Avro Records Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/PublishAvroRecordsExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/PublishAvroRecordsExample.java) | | Publish Protobuf Messages Example | [source code](https://github.com/googleapis/java-pubsub/blob/main/samples/snippets/src/main/java/pubsub/PublishProtobufMessagesExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-pubsub&page=editor&open_in_editor=samples/snippets/src/main/java/pubsub/PublishProtobufMessagesExample.java) |