From 8a6dc8cc4067bfff7a925e2be9cebdaf3c49757e Mon Sep 17 00:00:00 2001 From: cirilla-zmh Date: Wed, 19 Mar 2025 20:16:42 +0800 Subject: [PATCH 01/17] add messaging wrappers to support lightweight manual instrumentation --- messaging-wrappers/README.md | 26 ++++ messaging-wrappers/api/build.gradle.kts | 25 +++ messaging-wrappers/api/gradle.properties | 2 + ...DefaultMessagingProcessWrapperBuilder.java | 57 +++++++ .../wrappers/MessagingProcessWrapper.java | 142 ++++++++++++++++++ .../wrappers/MessagingSpanCustomizer.java | 14 ++ .../messaging/wrappers/package-info.java | 2 + ...DefaultMessagingProcessSpanCustomizer.java | 70 +++++++++ .../semconv/MessagingProcessRequest.java | 70 +++++++++ .../semconv/MessagingProcessResponse.java | 21 +++ ...nfigure.spi.ConfigurablePropagatorProvider | 2 + ...toconfigure.spi.internal.ComponentProvider | 2 + messaging-wrappers/mns/build.gradle.kts | 28 ++++ messaging-wrappers/mns/gradle.properties | 2 + .../messaging/wrappers/mns/MNSHelper.java | 43 ++++++ .../wrappers/mns/MNSProcessWrapper.java | 19 +++ .../mns/MNSProcessWrapperBuilder.java | 58 +++++++ .../wrappers/mns/MNSTextMapGetter.java | 85 +++++++++++ .../wrappers/mns/example/MNSConsumer.java | 73 +++++++++ .../messaging/wrappers/mns/package-info.java | 2 + .../mns/semconv/MNSProcessRequest.java | 95 ++++++++++++ .../mns/semconv/MNSProcessResponse.java | 21 +++ ...nfigure.spi.ConfigurablePropagatorProvider | 2 + ...toconfigure.spi.internal.ComponentProvider | 2 + settings.gradle.kts | 2 + 25 files changed, 865 insertions(+) create mode 100644 messaging-wrappers/README.md create mode 100644 messaging-wrappers/api/build.gradle.kts create mode 100644 messaging-wrappers/api/gradle.properties create mode 100644 messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java create mode 100644 messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java create mode 100644 messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingSpanCustomizer.java create mode 100644 messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/package-info.java create mode 100644 messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingProcessSpanCustomizer.java create mode 100644 messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java create mode 100644 messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessResponse.java create mode 100644 messaging-wrappers/api/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider create mode 100644 messaging-wrappers/api/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider create mode 100644 messaging-wrappers/mns/build.gradle.kts create mode 100644 messaging-wrappers/mns/gradle.properties create mode 100644 messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSHelper.java create mode 100644 messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapper.java create mode 100644 messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapperBuilder.java create mode 100644 messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSTextMapGetter.java create mode 100644 messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/example/MNSConsumer.java create mode 100644 messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/package-info.java create mode 100644 messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java create mode 100644 messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessResponse.java create mode 100644 messaging-wrappers/mns/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider create mode 100644 messaging-wrappers/mns/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider diff --git a/messaging-wrappers/README.md b/messaging-wrappers/README.md new file mode 100644 index 000000000..7a9129cac --- /dev/null +++ b/messaging-wrappers/README.md @@ -0,0 +1,26 @@ +# OpenTelemetry Messaging Wrappers + +This is a lightweight messaging wrappers API designed to help you quickly add instrumentation to any +type of messaging system client. To further ease the burden of instrumentation, we will also provide +predefined implementations for certain messaging systems, helping you seamlessly address the issue +of broken traces. + +## Overview + +The primary goal of this API is to simplify the process of adding instrumentation to your messaging +systems, thereby enhancing observability without introducing significant overhead. Inspired by +[#13340](https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/13340) and +[opentelemetry-java-instrumentation](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractor.java), +this tool aims to streamline the tracing and monitoring process. + +## Predefined Implementations + +| Messaging system | Version | Wrapper type | +|-------------------|----------------|--------------| +| Aliyun mns-client | 1.3.0-SNAPSHOT | process | + +## Component owners + +- [Minghui Zhang](https://github.com/Cirilla-zmh), Alibaba Cloud + +Learn more about component owners in [component_owners.yml](../.github/component_owners.yml). diff --git a/messaging-wrappers/api/build.gradle.kts b/messaging-wrappers/api/build.gradle.kts new file mode 100644 index 000000000..dc8005e95 --- /dev/null +++ b/messaging-wrappers/api/build.gradle.kts @@ -0,0 +1,25 @@ +plugins { + id("otel.java-conventions") + + id("otel.publish-conventions") +} + +description = "OpenTelemetry Messaging Wrappers" +otelJava.moduleName.set("io.opentelemetry.contrib.messaging.wrappers") + +dependencies { + api("io.opentelemetry:opentelemetry-api") + + compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") + compileOnly("io.opentelemetry:opentelemetry-api-incubator") + + implementation("io.opentelemetry.semconv:opentelemetry-semconv") + implementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating") + + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + testImplementation("io.opentelemetry:opentelemetry-sdk-trace") + testImplementation("io.opentelemetry:opentelemetry-sdk-testing") + + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") + testImplementation("uk.org.webcompere:system-stubs-jupiter:2.0.3") +} diff --git a/messaging-wrappers/api/gradle.properties b/messaging-wrappers/api/gradle.properties new file mode 100644 index 000000000..a0402e1e2 --- /dev/null +++ b/messaging-wrappers/api/gradle.properties @@ -0,0 +1,2 @@ +# TODO: uncomment when ready to mark as stable +# otel.stable=true diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java new file mode 100644 index 000000000..1fbb87aa5 --- /dev/null +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java @@ -0,0 +1,57 @@ +package io.opentelemetry.contrib.messaging.wrappers; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.contrib.messaging.wrappers.semconv.DefaultMessagingProcessSpanCustomizer; +import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; +import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessResponse; + +import java.util.ArrayList; +import java.util.List; + +public class DefaultMessagingProcessWrapperBuilder> { + + private OpenTelemetry openTelemetry; + + private TextMapGetter textMapGetter; + + private List> spanCustomizers; + + public static > DefaultMessagingProcessWrapperBuilder create() { + return new DefaultMessagingProcessWrapperBuilder<>(); + } + + public DefaultMessagingProcessWrapperBuilder openTelemetry(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + return this; + } + + public DefaultMessagingProcessWrapperBuilder textMapGetter(TextMapGetter textMapGetter) { + this.textMapGetter = textMapGetter; + return this; + } + + public DefaultMessagingProcessWrapperBuilder spanCustomizers( + List> spanCustomizers) { + this.spanCustomizers = spanCustomizers; + return this; + } + + public DefaultMessagingProcessWrapperBuilder addSpanCustomizers( + MessagingSpanCustomizer spanCustomizer) { + this.spanCustomizers.add(spanCustomizer); + return this; + } + + public MessagingProcessWrapper build() { + return new MessagingProcessWrapper<>(this.openTelemetry, this.textMapGetter, this.spanCustomizers); + } + + private DefaultMessagingProcessWrapperBuilder() { + // init by default + this.openTelemetry = GlobalOpenTelemetry.get(); + this.spanCustomizers = new ArrayList<>(); + this.spanCustomizers.add(DefaultMessagingProcessSpanCustomizer.create()); + } +} diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java new file mode 100644 index 000000000..b978e12ed --- /dev/null +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java @@ -0,0 +1,142 @@ +package io.opentelemetry.contrib.messaging.wrappers; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; +import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessResponse; + +import java.util.List; +import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class MessagingProcessWrapper> { + + private static final Logger LOG = Logger.getLogger(MessagingProcessWrapper.class.getName()); + + private static final String INSTRUMENTATION_SCOPE = "messaging-process-wrapper"; + + private static final String INSTRUMENTATION_VERSION = "1.0.0"; + + private static final String OPERATION_NAME = "process"; + + private final TextMapPropagator textMapPropagator; + + private final Tracer tracer; + + private final TextMapGetter textMapGetter; + + private final List> spanCustomizers; + + public Runnable wrap(REQUEST request, Runnable runnable) { + return () -> { + Span span = handleStart(request); + Scope scope = span.makeCurrent(); + + try { + runnable.run(); + } catch (Throwable t) { + handleEnd(span, request, null, t); + scope.close(); + throw t; + } + + handleEnd(span, request, null, null); + scope.close(); + }; + } + + public Callable wrap(REQUEST request, Callable callable) { + return () -> { + Span span = handleStart(request); + Scope scope = span.makeCurrent(); + RESPONSE response = null; + + R result = null; + try { + result = callable.call(); + if (result instanceof MessagingProcessResponse) { + response = (RESPONSE) result; + } + } catch (Throwable t) { + handleEnd(span, request, response, t); + scope.close(); + throw t; + } + + handleEnd(span, request, response, null); + scope.close(); + return result; + }; + } + + public R doProcess(REQUEST request, Callable process) throws Exception { + Span span = handleStart(request); + Scope scope = span.makeCurrent(); + RESPONSE response = null; + + R result = null; + try { + result = process.call(); + if (result instanceof MessagingProcessResponse) { + response = (RESPONSE) result; + } + } catch (Throwable t) { + handleEnd(span, request, response, t); + scope.close(); + throw t; + } + + // noop response by default + handleEnd(span, request, response, null); + scope.close(); + return result; + } + + protected Span handleStart(REQUEST request) { + Context context = this.textMapPropagator.extract(Context.current(), request, this.textMapGetter); + SpanBuilder spanBuilder = this.tracer.spanBuilder(getDefaultSpanName(request.getDestination())); + spanBuilder.setParent(context); + for (MessagingSpanCustomizer customizer : spanCustomizers) { + try { + context = customizer.onStart(spanBuilder, context, request); + } catch (Exception e) { + LOG.log(Level.WARNING, "Exception occurred while customizing span on start.", e); + } + } + return spanBuilder.startSpan(); + } + + protected void handleEnd(Span span, REQUEST request, RESPONSE response, Throwable t) { + for (MessagingSpanCustomizer customizer : spanCustomizers) { + try { + customizer.onEnd(span, Context.current(), request, response, t); + } catch (Exception e) { + LOG.log(Level.WARNING, "Exception occurred while customizing span on end.", e); + } + } + span.end(); + } + + protected String getDefaultSpanName(String destination) { + if (destination == null) { + destination = "unknown"; + } + return OPERATION_NAME + " " + destination; + } + + protected MessagingProcessWrapper(OpenTelemetry openTelemetry, + TextMapGetter textMapGetter, + List> spanCustomizers) { + this.textMapPropagator = openTelemetry.getPropagators().getTextMapPropagator(); + this.tracer = openTelemetry.getTracer(INSTRUMENTATION_SCOPE + "-" + INSTRUMENTATION_VERSION); + this.textMapGetter = textMapGetter; + this.spanCustomizers = spanCustomizers; + } +} diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingSpanCustomizer.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingSpanCustomizer.java new file mode 100644 index 000000000..c08e6a89f --- /dev/null +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingSpanCustomizer.java @@ -0,0 +1,14 @@ +package io.opentelemetry.contrib.messaging.wrappers; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; +import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessResponse; + +public interface MessagingSpanCustomizer> { + + Context onStart(SpanBuilder spanBuilder, Context parentContext, REQUEST request); + + void onEnd(Span span, Context context, REQUEST request, RESPONSE response, Throwable t); +} diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/package-info.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/package-info.java new file mode 100644 index 000000000..502b96ca6 --- /dev/null +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/package-info.java @@ -0,0 +1,2 @@ +/** OpenTelemetry messaging wrappers extension. */ +package io.opentelemetry.contrib.messaging.wrappers; diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingProcessSpanCustomizer.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingProcessSpanCustomizer.java new file mode 100644 index 000000000..feef8b561 --- /dev/null +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingProcessSpanCustomizer.java @@ -0,0 +1,70 @@ +package io.opentelemetry.contrib.messaging.wrappers.semconv; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.context.Context; +import io.opentelemetry.contrib.messaging.wrappers.MessagingSpanCustomizer; + +import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.*; + +public class DefaultMessagingProcessSpanCustomizer> + implements MessagingSpanCustomizer { + + public Context onStart(SpanBuilder spanBuilder, Context parentContext, REQUEST request) { + if (request == null || spanBuilder == null) { + return parentContext; + } + setAttributeIfNotNull(spanBuilder, MESSAGING_OPERATION_NAME, request.getOperationName()); + setAttributeIfNotNull(spanBuilder, MESSAGING_SYSTEM, request.getSystem()); + setAttributeIfNotNull(spanBuilder, MESSAGING_CONSUMER_GROUP_NAME, request.getConsumerGroupName()); + if (request.isAnonymousDestination()) { + spanBuilder.setAttribute(MESSAGING_DESTINATION_ANONYMOUS, request.isAnonymousDestination()); + } + setAttributeIfNotNull(spanBuilder, MESSAGING_DESTINATION_NAME, request.getDestination()); + setAttributeIfNotNull(spanBuilder, MESSAGING_DESTINATION_SUBSCRIPTION_NAME, request.getDestinationSubscriptionName()); + setAttributeIfNotNull(spanBuilder, MESSAGING_DESTINATION_TEMPLATE, request.getDestinationTemplate()); + if (request.isTemporaryDestination()) { + spanBuilder.setAttribute(MESSAGING_DESTINATION_TEMPORARY, request.isTemporaryDestination()); + } + setAttributeIfNotNull(spanBuilder, MESSAGING_OPERATION_TYPE, request.getOperationType()); + setAttributeIfNotNull(spanBuilder, MESSAGING_CLIENT_ID, request.getClientId()); + setAttributeIfNotNull(spanBuilder, MESSAGING_DESTINATION_PARTITION_ID, request.getDestinationPartitionId()); + setAttributeIfNotNull(spanBuilder, MESSAGING_MESSAGE_CONVERSATION_ID, request.getConversationId()); + setAttributeIfNotNull(spanBuilder, MESSAGING_MESSAGE_ID, request.getMessageId()); + setAttributeIfNotNull(spanBuilder, MESSAGING_MESSAGE_BODY_SIZE, request.getMessageBodySize()); + setAttributeIfNotNull(spanBuilder, MESSAGING_MESSAGE_ENVELOPE_SIZE, request.getMessageEnvelopeSize()); + + return parentContext; + } + + public void onEnd(Span span, Context context, REQUEST request, RESPONSE response, Throwable t) { + if (t != null) { + span.recordException(t); + span.setAttribute(ERROR_TYPE, t.getClass().getCanonicalName()); + span.setStatus(StatusCode.ERROR, t.getMessage()); + } + } + + protected void setAttributeIfNotNull(SpanBuilder spanBuilder, AttributeKey attributeKey, T value) { + if (value == null) { + return; + } + spanBuilder.setAttribute(attributeKey, value); + } + + protected void setAttributeIfNotNull(Span span, AttributeKey attributeKey, T value) { + if (value == null) { + return; + } + span.setAttribute(attributeKey, value); + } + + public static > MessagingSpanCustomizer create() { + return new DefaultMessagingProcessSpanCustomizer<>(); + } + + DefaultMessagingProcessSpanCustomizer() {} +} diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java new file mode 100644 index 000000000..6780d433a --- /dev/null +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java @@ -0,0 +1,70 @@ +package io.opentelemetry.contrib.messaging.wrappers.semconv; + +import java.util.List; + +import static java.util.Collections.emptyList; + +/** + * An interface to expose messaging properties for the pre-defined process wrapper. + * + *

Inspired from MessagingAttributesGetter. + */ +public interface MessagingProcessRequest { + + String getSystem(); + + String getDestination(); + + String getDestinationTemplate(); + + boolean isTemporaryDestination(); + + boolean isAnonymousDestination(); + + String getConversationId(); + + Long getMessageBodySize(); + + Long getMessageEnvelopeSize(); + + String getMessageId(); + + default String getOperationName() { + return "process"; + } + + default String getOperationType() { + return "process"; + } + + default String getConsumerGroupName() { + return null; + } + + default String getDestinationSubscriptionName() { + return null; + } + + default String getClientId() { + return null; + } + + default Long getBatchMessageCount() { + return null; + } + + default String getDestinationPartitionId() { + return null; + } + + /** + * Extracts all values of header named {@code name} from the request, or an empty list if there + * were none. + * + *

Implementations of this method must not return a null value; an empty list should be + * returned instead. + */ + default List getMessageHeader(String name) { + return emptyList(); + } +} diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessResponse.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessResponse.java new file mode 100644 index 000000000..8c469277a --- /dev/null +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessResponse.java @@ -0,0 +1,21 @@ +package io.opentelemetry.contrib.messaging.wrappers.semconv; + +/** + * An interface to expose messaging properties for the pre-defined process wrapper. + * + *

Only be created on demand. + * + *

Inspired from MessagingAttributesGetter. + */ +public interface MessagingProcessResponse { + + default String getMessageId() { + return null; + } + + default Long getBatchMessageCount() { + return null; + } + + T getOriginalResponse(); +} diff --git a/messaging-wrappers/api/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider b/messaging-wrappers/api/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider new file mode 100644 index 000000000..553d222bc --- /dev/null +++ b/messaging-wrappers/api/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider @@ -0,0 +1,2 @@ +internal.io.opentelemetry.contrib.messaging.wrappers.AwsConfigurablePropagator +internal.io.opentelemetry.contrib.messaging.wrappers.AwsXrayLambdaConfigurablePropagator diff --git a/messaging-wrappers/api/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider b/messaging-wrappers/api/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider new file mode 100644 index 000000000..a7f8dd670 --- /dev/null +++ b/messaging-wrappers/api/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider @@ -0,0 +1,2 @@ +internal.io.opentelemetry.contrib.messaging.wrappers.AwsXrayComponentProvider +internal.io.opentelemetry.contrib.messaging.wrappers.AwsXrayLambdaComponentProvider diff --git a/messaging-wrappers/mns/build.gradle.kts b/messaging-wrappers/mns/build.gradle.kts new file mode 100644 index 000000000..f2bd0081c --- /dev/null +++ b/messaging-wrappers/mns/build.gradle.kts @@ -0,0 +1,28 @@ +plugins { + id("otel.java-conventions") + + id("otel.publish-conventions") +} + +description = "OpenTelemetry Messaging Wrappers" +otelJava.moduleName.set("io.opentelemetry.contrib.messaging.wrappers") + +repositories { + // FIXME: publish mns-client to maven central repository + maven { + url = uri("https://mvnrepo.alibaba-inc.com/mvn/repository") + } +} + +dependencies { + api(project(":messaging-wrappers:api")) + + compileOnly("com.aliyun.mns:aliyun-sdk-mns:1.3.0-SNAPSHOT") + + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + testImplementation("io.opentelemetry:opentelemetry-sdk-trace") + testImplementation("io.opentelemetry:opentelemetry-sdk-testing") + + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") + testImplementation("uk.org.webcompere:system-stubs-jupiter:2.0.3") +} diff --git a/messaging-wrappers/mns/gradle.properties b/messaging-wrappers/mns/gradle.properties new file mode 100644 index 000000000..a0402e1e2 --- /dev/null +++ b/messaging-wrappers/mns/gradle.properties @@ -0,0 +1,2 @@ +# TODO: uncomment when ready to mark as stable +# otel.stable=true diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSHelper.java b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSHelper.java new file mode 100644 index 000000000..e5beb9393 --- /dev/null +++ b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSHelper.java @@ -0,0 +1,43 @@ +package io.opentelemetry.contrib.messaging.wrappers.mns; + +import com.aliyun.mns.model.BaseMessage; +import com.aliyun.mns.model.MessagePropertyValue; +import com.aliyun.mns.model.MessageSystemPropertyName; +import com.aliyun.mns.model.MessageSystemPropertyValue; + +public final class MNSHelper { + + private MNSHelper() { + } + + public static String getMessageHeader(BaseMessage message, String name) { + MessageSystemPropertyName key = convert2SystemPropertyName(name); + if (key != null) { + MessageSystemPropertyValue systemProperty = message.getSystemProperty(key); + if (systemProperty != null) { + return systemProperty.getStringValueByType(); + } + } + MessagePropertyValue messagePropertyValue = message.getUserProperties().get(name); + if (messagePropertyValue != null) { + return messagePropertyValue.getStringValueByType(); + } + return null; + } + + /** + * see {@link MessageSystemPropertyName} + * */ + public static MessageSystemPropertyName convert2SystemPropertyName(String name) { + if (name == null) { + return null; + } else if (name.equals(MessageSystemPropertyName.TRACE_PARENT.getValue())) { + return MessageSystemPropertyName.TRACE_PARENT; + } else if (name.equals(MessageSystemPropertyName.BAGGAGE.getValue())) { + return MessageSystemPropertyName.BAGGAGE; + } else if (name.equals(MessageSystemPropertyName.TRACE_STATE.getValue())) { + return MessageSystemPropertyName.TRACE_STATE; + } + return null; + } +} diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapper.java b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapper.java new file mode 100644 index 000000000..1f2fc85fc --- /dev/null +++ b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapper.java @@ -0,0 +1,19 @@ +package io.opentelemetry.contrib.messaging.wrappers.mns; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.contrib.messaging.wrappers.MessagingProcessWrapper; +import io.opentelemetry.contrib.messaging.wrappers.MessagingSpanCustomizer; +import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessRequest; +import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessResponse; +import java.util.List; + +public class MNSProcessWrapper> + extends MessagingProcessWrapper { + + protected MNSProcessWrapper(OpenTelemetry openTelemetry, + TextMapGetter textMapGetter, + List> spanCustomizers) { + super(openTelemetry, textMapGetter, spanCustomizers); + } +} diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapperBuilder.java b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapperBuilder.java new file mode 100644 index 000000000..732a0e2d6 --- /dev/null +++ b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapperBuilder.java @@ -0,0 +1,58 @@ +package io.opentelemetry.contrib.messaging.wrappers.mns; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.contrib.messaging.wrappers.MessagingSpanCustomizer; +import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessRequest; +import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessResponse; +import io.opentelemetry.contrib.messaging.wrappers.semconv.DefaultMessagingProcessSpanCustomizer; +import java.util.ArrayList; +import java.util.List; + +public class MNSProcessWrapperBuilder> { + + private OpenTelemetry openTelemetry; + + private TextMapGetter textMapGetter; + + private List> spanCustomizers; + + public static > MNSProcessWrapperBuilder create() { + return new MNSProcessWrapperBuilder<>(); + } + + public MNSProcessWrapperBuilder openTelemetry(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + return this; + } + + public MNSProcessWrapperBuilder textMapGetter(TextMapGetter textMapGetter) { + this.textMapGetter = textMapGetter; + return this; + } + + public MNSProcessWrapperBuilder spanCustomizers( + List> spanCustomizers) { + this.spanCustomizers = spanCustomizers; + return this; + } + + public MNSProcessWrapperBuilder addSpanCustomizers( + MessagingSpanCustomizer spanCustomizer) { + this.spanCustomizers.add(spanCustomizer); + return this; + } + + public MNSProcessWrapper build() { + return new MNSProcessWrapper<>(this.openTelemetry, this.textMapGetter, this.spanCustomizers); + } + + private MNSProcessWrapperBuilder() { + // init by default + this.openTelemetry = GlobalOpenTelemetry.get(); + this.spanCustomizers = new ArrayList<>(); + this.spanCustomizers.add(DefaultMessagingProcessSpanCustomizer.create()); + this.textMapGetter = MNSTextMapGetter.create(); + } +} diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSTextMapGetter.java b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSTextMapGetter.java new file mode 100644 index 000000000..7b11efc14 --- /dev/null +++ b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSTextMapGetter.java @@ -0,0 +1,85 @@ +package io.opentelemetry.contrib.messaging.wrappers.mns; + +import static java.util.Collections.emptyList; + +import com.aliyun.mns.model.*; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessRequest; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class MNSTextMapGetter implements TextMapGetter { + + public static TextMapGetter create() { + return new MNSTextMapGetter<>(); + } + + @Override + public Iterable keys(REQUEST carrier) { + if (carrier == null || carrier.getMessage() == null) { + return emptyList(); + } + Message message = carrier.getMessage(); + + Map systemProperties = message.getSystemProperties(); + if (systemProperties == null) { + systemProperties = Collections.emptyMap(); + } + Map userProperties = message.getUserProperties(); + if (userProperties == null) { + userProperties = Collections.emptyMap(); + } + List keys = new ArrayList<>(systemProperties.size() + userProperties.size()); + keys.addAll(systemProperties.keySet()); + keys.addAll(userProperties.keySet()); + return keys; + } + + @Override + public String get(REQUEST carrier, String key) { + if (carrier == null || carrier.getMessage() == null) { + return null; + } + Message message = carrier.getMessage(); + + // the system property should always take precedence over the user property + MessageSystemPropertyName systemPropertyName = convert2SystemPropertyName(key); + if (systemPropertyName != null) { + MessageSystemPropertyValue value = message.getSystemProperty(systemPropertyName); + if (value != null) { + return value.getStringValueByType(); + } + } + + Map userProperties = message.getUserProperties(); + if (userProperties == null || userProperties.isEmpty()) { + return null; + } + MessagePropertyValue value = userProperties.getOrDefault(key, null); + if (value != null) { + return value.getStringValueByType(); + } + return null; + } + + /** + * see {@link MessageSystemPropertyName} + * */ + private MessageSystemPropertyName convert2SystemPropertyName(String name) { + if (name == null) { + return null; + } else if (name.equals(MessageSystemPropertyName.TRACE_PARENT.getValue())) { + return MessageSystemPropertyName.TRACE_PARENT; + } else if (name.equals(MessageSystemPropertyName.BAGGAGE.getValue())) { + return MessageSystemPropertyName.BAGGAGE; + } else if (name.equals(MessageSystemPropertyName.TRACE_STATE.getValue())) { + return MessageSystemPropertyName.TRACE_STATE; + } + return null; + } + + private MNSTextMapGetter() { + } +} diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/example/MNSConsumer.java b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/example/MNSConsumer.java new file mode 100644 index 000000000..a2b0aa613 --- /dev/null +++ b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/example/MNSConsumer.java @@ -0,0 +1,73 @@ +package io.opentelemetry.contrib.messaging.wrappers.mns.example; + +import com.aliyun.mns.client.CloudAccount; +import com.aliyun.mns.client.CloudQueue; +import com.aliyun.mns.client.MNSClient; +import com.aliyun.mns.common.ClientException; +import com.aliyun.mns.common.ServiceException; +import com.aliyun.mns.model.Message; +import com.aliyun.mns.model.MessageSystemPropertyValue; +import io.opentelemetry.contrib.messaging.wrappers.mns.MNSProcessWrapper; +import io.opentelemetry.contrib.messaging.wrappers.mns.MNSProcessWrapperBuilder; +import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessRequest; +import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessResponse; + +import static com.aliyun.mns.model.MessageSystemPropertyName.TRACE_PARENT; + +public class MNSConsumer { + + public static void main(String[] args) { + // 1. create wrapper by default + MNSProcessWrapper> wrapper = MNSProcessWrapperBuilder.create().build(); + CloudAccount account = new CloudAccount("my-ak", "my-sk", "endpoint"); + MNSClient client = account.getMNSClient(); + + final String queueName = "test-queue"; + try { + CloudQueue queue = client.getQueueRef(queueName); + while (true) { + Message popMsg = queue.popMessage(5); + if (popMsg != null) { + // 2. wrap your consume block + String result = wrapper.doProcess(MNSProcessRequest.of(popMsg, queueName), () -> { + System.out.println("message handle: " + popMsg.getReceiptHandle()); + System.out.println("message body: " + popMsg.getMessageBodyAsString()); + System.out.println("message id: " + popMsg.getMessageId()); + System.out.println("message dequeue count:" + popMsg.getDequeueCount()); + MessageSystemPropertyValue systemProperty = popMsg.getSystemProperty(TRACE_PARENT); + if (systemProperty != null) { + System.out.println("message trace parent: " + systemProperty.getStringValueByType()); + } else { + System.out.println("empty system property"); + } + //<> + + queue.deleteMessage(popMsg.getReceiptHandle()); + System.out.println("delete message successfully\n"); + return "success"; + }); + } + } + } catch (ClientException ce) { + System.out.println("Something wrong with the network connection between client and MNS service." + + "Please check your network and DNS availablity."); + ce.printStackTrace(); + } catch (ServiceException se) { + if (se.getErrorCode().equals("QueueNotExist")) { + System.out.println("Queue is not exist.Please create queue before use"); + } else if (se.getErrorCode().equals("TimeExpired")) { + System.out.println("The request is time expired. Please check your local machine timeclock"); + } + /* + you can get more MNS service error code in following link. + https://help.aliyun.com/document_detail/mns/api_reference/error_code/error_code.html?spm=5176.docmns/api_reference/error_code/error_response + */ + se.printStackTrace(); + } catch (Exception e) { + System.out.println("Unknown exception happened!"); + e.printStackTrace(); + } + + client.close(); + } +} diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/package-info.java b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/package-info.java new file mode 100644 index 000000000..0fb38eb18 --- /dev/null +++ b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/package-info.java @@ -0,0 +1,2 @@ +/** OpenTelemetry messaging wrappers extension. */ +package io.opentelemetry.contrib.messaging.wrappers.mns; diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java new file mode 100644 index 000000000..798a7be5f --- /dev/null +++ b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java @@ -0,0 +1,95 @@ +package io.opentelemetry.contrib.messaging.wrappers.mns.semconv; + +import com.aliyun.mns.model.Message; +import io.opentelemetry.contrib.messaging.wrappers.mns.MNSHelper; +import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; + +import java.util.Collections; +import java.util.List; + +public class MNSProcessRequest implements MessagingProcessRequest { + + private final Message message; + + private final String destination; + + public static MNSProcessRequest of(Message message) { + return of(message, null); + } + + public static MNSProcessRequest of(Message message, String destination) { + return new MNSProcessRequest(message, destination); + } + + @Override + public String getSystem() { + return "mns"; + } + + @Override + public String getDestination() { + return this.destination; + } + + @Override + public String getDestinationTemplate() { + return null; + } + + @Override + public boolean isTemporaryDestination() { + return false; + } + + @Override + public boolean isAnonymousDestination() { + return false; + } + + @Override + public String getConversationId() { + return null; + } + + @Override + public Long getMessageBodySize() { + if (message == null) { + return null; + } + return (long) message.getMessageBodyAsBytes().length; + } + + @Override + public Long getMessageEnvelopeSize() { + return null; + } + + @Override + public String getMessageId() { + if (message == null) { + return null; + } + return message.getMessageId(); + } + + @Override + public List getMessageHeader(String name) { + if (message == null) { + return Collections.emptyList(); + } + String header = MNSHelper.getMessageHeader(message, name); + if (header == null) { + return Collections.emptyList(); + } + return Collections.singletonList(header); + } + + public Message getMessage() { + return message; + } + + private MNSProcessRequest(Message message, String destination) { + this.message = message; + this.destination = destination; + } +} diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessResponse.java b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessResponse.java new file mode 100644 index 000000000..cfefc72c8 --- /dev/null +++ b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessResponse.java @@ -0,0 +1,21 @@ +package io.opentelemetry.contrib.messaging.wrappers.mns.semconv; + +import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessResponse; + +public class MNSProcessResponse implements MessagingProcessResponse { + + private final T originalResponse; + + public static MNSProcessResponse of(T originalResponse) { + return new MNSProcessResponse<>(originalResponse); + } + + @Override + public T getOriginalResponse() { + return originalResponse; + } + + public MNSProcessResponse(T originalResponse) { + this.originalResponse = originalResponse; + } +} diff --git a/messaging-wrappers/mns/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider b/messaging-wrappers/mns/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider new file mode 100644 index 000000000..553d222bc --- /dev/null +++ b/messaging-wrappers/mns/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider @@ -0,0 +1,2 @@ +internal.io.opentelemetry.contrib.messaging.wrappers.AwsConfigurablePropagator +internal.io.opentelemetry.contrib.messaging.wrappers.AwsXrayLambdaConfigurablePropagator diff --git a/messaging-wrappers/mns/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider b/messaging-wrappers/mns/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider new file mode 100644 index 000000000..a7f8dd670 --- /dev/null +++ b/messaging-wrappers/mns/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider @@ -0,0 +1,2 @@ +internal.io.opentelemetry.contrib.messaging.wrappers.AwsXrayComponentProvider +internal.io.opentelemetry.contrib.messaging.wrappers.AwsXrayLambdaComponentProvider diff --git a/settings.gradle.kts b/settings.gradle.kts index fa43236dc..385baf346 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -53,6 +53,8 @@ include(":jmx-scraper") include(":jmx-scraper:test-app") include(":jmx-scraper:test-webapp") include(":maven-extension") +include(":messaging-wrappers:api") +include(":messaging-wrappers:mns") include(":micrometer-meter-provider") include(":noop-api") include(":processors") From 8ef16f8e044e4a15f5b84bd518d98c52b30470be Mon Sep 17 00:00:00 2001 From: cirilla-zmh Date: Tue, 1 Apr 2025 11:15:52 +0800 Subject: [PATCH 02/17] refactor messaging wrappers & add the kafka-clients implementation --- messaging-wrappers/README.md | 1 + messaging-wrappers/api/build.gradle.kts | 2 +- ...DefaultMessagingProcessWrapperBuilder.java | 53 +++--- .../wrappers/MessagingProcessWrapper.java | 111 +++++-------- .../wrappers/MessagingSpanCustomizer.java | 14 -- .../messaging/wrappers/ThrowingRunnable.java | 13 ++ .../messaging/wrappers/ThrowingSupplier.java | 15 ++ .../DefaultMessagingAttributesGetter.java | 89 ++++++++++ ...DefaultMessagingProcessSpanCustomizer.java | 70 -------- .../semconv/MessagingProcessRequest.java | 27 +--- .../semconv/MessagingProcessResponse.java | 21 --- ...nfigure.spi.ConfigurablePropagatorProvider | 2 - ...toconfigure.spi.internal.ComponentProvider | 2 - .../kafka-clients/build.gradle.kts | 25 +++ .../kafka-clients/gradle.properties | 2 + .../messaging/wrappers/kafka/KafkaHelper.java | 13 ++ .../kafka/KafkaProcessWrapperBuilder.java | 13 ++ .../wrappers/kafka/KafkaTextMapGetter.java | 48 ++++++ .../wrappers/kafka/package-info.java | 2 + .../KafkaConsumerAttributesExtractor.java | 72 +++++++++ .../kafka/semconv/KafkaProcessRequest.java | 122 ++++++++++++++ messaging-wrappers/mns/build.gradle.kts | 4 +- .../messaging/wrappers/mns/MNSHelper.java | 7 +- .../wrappers/mns/MNSProcessWrapper.java | 19 --- .../mns/MNSProcessWrapperBuilder.java | 59 +------ .../wrappers/mns/MNSTextMapGetter.java | 108 ++++++------- .../wrappers/mns/example/MNSConsumer.java | 7 +- .../mns/semconv/MNSProcessRequest.java | 152 +++++++++--------- .../mns/semconv/MNSProcessResponse.java | 21 --- ...nfigure.spi.ConfigurablePropagatorProvider | 2 - ...toconfigure.spi.internal.ComponentProvider | 2 - settings.gradle.kts | 1 + 32 files changed, 628 insertions(+), 471 deletions(-) delete mode 100644 messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingSpanCustomizer.java create mode 100644 messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/ThrowingRunnable.java create mode 100644 messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/ThrowingSupplier.java create mode 100644 messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingAttributesGetter.java delete mode 100644 messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingProcessSpanCustomizer.java delete mode 100644 messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessResponse.java delete mode 100644 messaging-wrappers/api/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider delete mode 100644 messaging-wrappers/api/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider create mode 100644 messaging-wrappers/kafka-clients/build.gradle.kts create mode 100644 messaging-wrappers/kafka-clients/gradle.properties create mode 100644 messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaHelper.java create mode 100644 messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaProcessWrapperBuilder.java create mode 100644 messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaTextMapGetter.java create mode 100644 messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/package-info.java create mode 100644 messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesExtractor.java create mode 100644 messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaProcessRequest.java delete mode 100644 messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapper.java delete mode 100644 messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessResponse.java delete mode 100644 messaging-wrappers/mns/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider delete mode 100644 messaging-wrappers/mns/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider diff --git a/messaging-wrappers/README.md b/messaging-wrappers/README.md index 7a9129cac..ba9b7042d 100644 --- a/messaging-wrappers/README.md +++ b/messaging-wrappers/README.md @@ -22,5 +22,6 @@ this tool aims to streamline the tracing and monitoring process. ## Component owners - [Minghui Zhang](https://github.com/Cirilla-zmh), Alibaba Cloud +- [Steve Rao](https://github.com/steverao), Alibaba Cloud Learn more about component owners in [component_owners.yml](../.github/component_owners.yml). diff --git a/messaging-wrappers/api/build.gradle.kts b/messaging-wrappers/api/build.gradle.kts index dc8005e95..dcf2a00b0 100644 --- a/messaging-wrappers/api/build.gradle.kts +++ b/messaging-wrappers/api/build.gradle.kts @@ -8,7 +8,7 @@ description = "OpenTelemetry Messaging Wrappers" otelJava.moduleName.set("io.opentelemetry.contrib.messaging.wrappers") dependencies { - api("io.opentelemetry:opentelemetry-api") + api("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator") compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") compileOnly("io.opentelemetry:opentelemetry-api-incubator") diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java index 1fbb87aa5..b2eb03d01 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java @@ -3,55 +3,60 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.context.propagation.TextMapGetter; -import io.opentelemetry.contrib.messaging.wrappers.semconv.DefaultMessagingProcessSpanCustomizer; +import io.opentelemetry.contrib.messaging.wrappers.semconv.DefaultMessagingAttributesGetter; import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; -import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessResponse; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import java.util.ArrayList; +import java.util.Collection; import java.util.List; -public class DefaultMessagingProcessWrapperBuilder> { +public class DefaultMessagingProcessWrapperBuilder { private OpenTelemetry openTelemetry; - private TextMapGetter textMapGetter; + protected TextMapGetter textMapGetter; - private List> spanCustomizers; + protected List> attributesExtractors; - public static > DefaultMessagingProcessWrapperBuilder create() { - return new DefaultMessagingProcessWrapperBuilder<>(); - } - - public DefaultMessagingProcessWrapperBuilder openTelemetry(OpenTelemetry openTelemetry) { + public DefaultMessagingProcessWrapperBuilder openTelemetry(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; return this; } - public DefaultMessagingProcessWrapperBuilder textMapGetter(TextMapGetter textMapGetter) { + public DefaultMessagingProcessWrapperBuilder textMapGetter(TextMapGetter textMapGetter) { this.textMapGetter = textMapGetter; return this; } - public DefaultMessagingProcessWrapperBuilder spanCustomizers( - List> spanCustomizers) { - this.spanCustomizers = spanCustomizers; + /** + * This method overrides the original items. + *

See {@link DefaultMessagingProcessWrapperBuilder#addAttributesExtractor} if you just want to append one.

+ * */ + public DefaultMessagingProcessWrapperBuilder attributesExtractors( + Collection> attributesExtractors) { + this.attributesExtractors = new ArrayList<>(); + this.attributesExtractors.addAll(attributesExtractors); return this; } - public DefaultMessagingProcessWrapperBuilder addSpanCustomizers( - MessagingSpanCustomizer spanCustomizer) { - this.spanCustomizers.add(spanCustomizer); + public DefaultMessagingProcessWrapperBuilder addAttributesExtractor( + AttributesExtractor attributesExtractor) { + this.attributesExtractors.add(attributesExtractor); return this; } - public MessagingProcessWrapper build() { - return new MessagingProcessWrapper<>(this.openTelemetry, this.textMapGetter, this.spanCustomizers); + public MessagingProcessWrapper build() { + return new MessagingProcessWrapper<>(this.openTelemetry == null ? GlobalOpenTelemetry.get() : this.openTelemetry, + this.textMapGetter, this.attributesExtractors); } - private DefaultMessagingProcessWrapperBuilder() { - // init by default - this.openTelemetry = GlobalOpenTelemetry.get(); - this.spanCustomizers = new ArrayList<>(); - this.spanCustomizers.add(DefaultMessagingProcessSpanCustomizer.create()); + protected DefaultMessagingProcessWrapperBuilder() { + // init attributes extractors by default + this.attributesExtractors = new ArrayList<>(); + this.attributesExtractors.add(MessagingAttributesExtractor.create( + DefaultMessagingAttributesGetter.create(), MessageOperation.PROCESS)); } } diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java index b978e12ed..8b6111d64 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java @@ -1,6 +1,8 @@ package io.opentelemetry.contrib.messaging.wrappers; 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.Tracer; @@ -9,16 +11,12 @@ import io.opentelemetry.context.propagation.TextMapGetter; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; -import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessResponse; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import javax.annotation.Nullable; import java.util.List; -import java.util.concurrent.Callable; -import java.util.logging.Level; -import java.util.logging.Logger; -public class MessagingProcessWrapper> { - - private static final Logger LOG = Logger.getLogger(MessagingProcessWrapper.class.getName()); +public class MessagingProcessWrapper { private static final String INSTRUMENTATION_SCOPE = "messaging-process-wrapper"; @@ -32,70 +30,38 @@ public class MessagingProcessWrapper textMapGetter; - private final List> spanCustomizers; + // no attributes need to be extracted from responses in process operations + private final List> attributesExtractors; - public Runnable wrap(REQUEST request, Runnable runnable) { - return () -> { - Span span = handleStart(request); - Scope scope = span.makeCurrent(); + public static DefaultMessagingProcessWrapperBuilder defaultBuilder() { + return new DefaultMessagingProcessWrapperBuilder<>(); + } - try { - runnable.run(); - } catch (Throwable t) { - handleEnd(span, request, null, t); - scope.close(); - throw t; - } + public void doProcess(REQUEST request, ThrowingRunnable runnable) throws E { + Span span = handleStart(request); - handleEnd(span, request, null, null); - scope.close(); - }; - } + try (Scope scope = span.makeCurrent()) { + runnable.run(); + } catch (Throwable t) { + handleEnd(span, request, t); + throw t; + } - public Callable wrap(REQUEST request, Callable callable) { - return () -> { - Span span = handleStart(request); - Scope scope = span.makeCurrent(); - RESPONSE response = null; - - R result = null; - try { - result = callable.call(); - if (result instanceof MessagingProcessResponse) { - response = (RESPONSE) result; - } - } catch (Throwable t) { - handleEnd(span, request, response, t); - scope.close(); - throw t; - } - - handleEnd(span, request, response, null); - scope.close(); - return result; - }; + handleEnd(span, request, null); } - public R doProcess(REQUEST request, Callable process) throws Exception { + public R doProcess(REQUEST request, ThrowingSupplier supplier) throws E { Span span = handleStart(request); - Scope scope = span.makeCurrent(); - RESPONSE response = null; R result = null; - try { - result = process.call(); - if (result instanceof MessagingProcessResponse) { - response = (RESPONSE) result; - } + try (Scope scope = span.makeCurrent()) { + result = supplier.get(); } catch (Throwable t) { - handleEnd(span, request, response, t); - scope.close(); + handleEnd(span, request, t); throw t; } - // noop response by default - handleEnd(span, request, response, null); - scope.close(); + handleEnd(span, request, null); return result; } @@ -103,23 +69,18 @@ protected Span handleStart(REQUEST request) { Context context = this.textMapPropagator.extract(Context.current(), request, this.textMapGetter); SpanBuilder spanBuilder = this.tracer.spanBuilder(getDefaultSpanName(request.getDestination())); spanBuilder.setParent(context); - for (MessagingSpanCustomizer customizer : spanCustomizers) { - try { - context = customizer.onStart(spanBuilder, context, request); - } catch (Exception e) { - LOG.log(Level.WARNING, "Exception occurred while customizing span on start.", e); - } + + AttributesBuilder builder = Attributes.builder(); + for (AttributesExtractor extractor : this.attributesExtractors) { + extractor.onStart(builder, context, request); } - return spanBuilder.startSpan(); + return spanBuilder.setAllAttributes(builder.build()).startSpan(); } - protected void handleEnd(Span span, REQUEST request, RESPONSE response, Throwable t) { - for (MessagingSpanCustomizer customizer : spanCustomizers) { - try { - customizer.onEnd(span, Context.current(), request, response, t); - } catch (Exception e) { - LOG.log(Level.WARNING, "Exception occurred while customizing span on end.", e); - } + protected void handleEnd(Span span, REQUEST request, Throwable t) { + AttributesBuilder builder = Attributes.builder(); + for (AttributesExtractor extractor : this.attributesExtractors) { + extractor.onEnd(builder, Context.current(), request, null, t); } span.end(); } @@ -132,11 +93,11 @@ protected String getDefaultSpanName(String destination) { } protected MessagingProcessWrapper(OpenTelemetry openTelemetry, - TextMapGetter textMapGetter, - List> spanCustomizers) { + @Nullable TextMapGetter textMapGetter, + List> attributesExtractors) { this.textMapPropagator = openTelemetry.getPropagators().getTextMapPropagator(); this.tracer = openTelemetry.getTracer(INSTRUMENTATION_SCOPE + "-" + INSTRUMENTATION_VERSION); this.textMapGetter = textMapGetter; - this.spanCustomizers = spanCustomizers; + this.attributesExtractors = attributesExtractors; } } diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingSpanCustomizer.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingSpanCustomizer.java deleted file mode 100644 index c08e6a89f..000000000 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingSpanCustomizer.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.opentelemetry.contrib.messaging.wrappers; - -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanBuilder; -import io.opentelemetry.context.Context; -import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; -import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessResponse; - -public interface MessagingSpanCustomizer> { - - Context onStart(SpanBuilder spanBuilder, Context parentContext, REQUEST request); - - void onEnd(Span span, Context context, REQUEST request, RESPONSE response, Throwable t); -} diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/ThrowingRunnable.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/ThrowingRunnable.java new file mode 100644 index 000000000..6e5279632 --- /dev/null +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/ThrowingRunnable.java @@ -0,0 +1,13 @@ +package io.opentelemetry.contrib.messaging.wrappers; + +/** + * A utility interface representing a {@link Runnable} that may throw. + * + *

Inspired from ThrowingRunnable. + * + * @param Thrown exception type. + */ +@FunctionalInterface +public interface ThrowingRunnable { + void run() throws E; +} diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/ThrowingSupplier.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/ThrowingSupplier.java new file mode 100644 index 000000000..1f1f1c974 --- /dev/null +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/ThrowingSupplier.java @@ -0,0 +1,15 @@ +package io.opentelemetry.contrib.messaging.wrappers; + +import java.util.function.Supplier; + +/** + * A utility interface representing a {@link Supplier} that may throw. + * + *

Inspired from ThrowingSupplier. + * + * @param Thrown exception type. + */ +@FunctionalInterface +public interface ThrowingSupplier { + T get() throws E; +} diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingAttributesGetter.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingAttributesGetter.java new file mode 100644 index 000000000..88e7531b0 --- /dev/null +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingAttributesGetter.java @@ -0,0 +1,89 @@ +package io.opentelemetry.contrib.messaging.wrappers.semconv; + +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; + +import javax.annotation.Nullable; +import java.util.List; + +public class DefaultMessagingAttributesGetter + implements MessagingAttributesGetter { + + public static MessagingAttributesGetter create() { + return new DefaultMessagingAttributesGetter<>(); + } + + @Nullable + @Override + public String getDestinationPartitionId(REQUEST request) { + return request.getDestinationPartitionId(); + } + + @Override + public List getMessageHeader(REQUEST request, String name) { + return request.getMessageHeader(name); + } + + @Nullable + @Override + public String getSystem(REQUEST request) { + return request.getSystem(); + } + + @Nullable + @Override + public String getDestination(REQUEST request) { + return request.getDestination(); + } + + @Nullable + @Override + public String getDestinationTemplate(REQUEST request) { + return request.getDestinationTemplate(); + } + + @Override + public boolean isTemporaryDestination(REQUEST request) { + return request.isTemporaryDestination(); + } + + @Override + public boolean isAnonymousDestination(REQUEST request) { + return request.isAnonymousDestination(); + } + + @Nullable + @Override + public String getConversationId(REQUEST request) { + return request.getConversationId(); + } + + @Nullable + @Override + public Long getMessageBodySize(REQUEST request) { + return request.getMessageBodySize(); + } + + @Nullable + @Override + public Long getMessageEnvelopeSize(REQUEST request) { + return request.getMessageEnvelopeSize(); + } + + @Nullable + @Override + public String getMessageId(REQUEST request, @Nullable Void unused) { + return request.getMessageId(); + } + + @Nullable + @Override + public String getClientId(REQUEST request) { + return request.getClientId(); + } + + @Nullable + @Override + public Long getBatchMessageCount(REQUEST request, @Nullable Void unused) { + return request.getBatchMessageCount(); + } +} diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingProcessSpanCustomizer.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingProcessSpanCustomizer.java deleted file mode 100644 index feef8b561..000000000 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingProcessSpanCustomizer.java +++ /dev/null @@ -1,70 +0,0 @@ -package io.opentelemetry.contrib.messaging.wrappers.semconv; - -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanBuilder; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.context.Context; -import io.opentelemetry.contrib.messaging.wrappers.MessagingSpanCustomizer; - -import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.*; - -public class DefaultMessagingProcessSpanCustomizer> - implements MessagingSpanCustomizer { - - public Context onStart(SpanBuilder spanBuilder, Context parentContext, REQUEST request) { - if (request == null || spanBuilder == null) { - return parentContext; - } - setAttributeIfNotNull(spanBuilder, MESSAGING_OPERATION_NAME, request.getOperationName()); - setAttributeIfNotNull(spanBuilder, MESSAGING_SYSTEM, request.getSystem()); - setAttributeIfNotNull(spanBuilder, MESSAGING_CONSUMER_GROUP_NAME, request.getConsumerGroupName()); - if (request.isAnonymousDestination()) { - spanBuilder.setAttribute(MESSAGING_DESTINATION_ANONYMOUS, request.isAnonymousDestination()); - } - setAttributeIfNotNull(spanBuilder, MESSAGING_DESTINATION_NAME, request.getDestination()); - setAttributeIfNotNull(spanBuilder, MESSAGING_DESTINATION_SUBSCRIPTION_NAME, request.getDestinationSubscriptionName()); - setAttributeIfNotNull(spanBuilder, MESSAGING_DESTINATION_TEMPLATE, request.getDestinationTemplate()); - if (request.isTemporaryDestination()) { - spanBuilder.setAttribute(MESSAGING_DESTINATION_TEMPORARY, request.isTemporaryDestination()); - } - setAttributeIfNotNull(spanBuilder, MESSAGING_OPERATION_TYPE, request.getOperationType()); - setAttributeIfNotNull(spanBuilder, MESSAGING_CLIENT_ID, request.getClientId()); - setAttributeIfNotNull(spanBuilder, MESSAGING_DESTINATION_PARTITION_ID, request.getDestinationPartitionId()); - setAttributeIfNotNull(spanBuilder, MESSAGING_MESSAGE_CONVERSATION_ID, request.getConversationId()); - setAttributeIfNotNull(spanBuilder, MESSAGING_MESSAGE_ID, request.getMessageId()); - setAttributeIfNotNull(spanBuilder, MESSAGING_MESSAGE_BODY_SIZE, request.getMessageBodySize()); - setAttributeIfNotNull(spanBuilder, MESSAGING_MESSAGE_ENVELOPE_SIZE, request.getMessageEnvelopeSize()); - - return parentContext; - } - - public void onEnd(Span span, Context context, REQUEST request, RESPONSE response, Throwable t) { - if (t != null) { - span.recordException(t); - span.setAttribute(ERROR_TYPE, t.getClass().getCanonicalName()); - span.setStatus(StatusCode.ERROR, t.getMessage()); - } - } - - protected void setAttributeIfNotNull(SpanBuilder spanBuilder, AttributeKey attributeKey, T value) { - if (value == null) { - return; - } - spanBuilder.setAttribute(attributeKey, value); - } - - protected void setAttributeIfNotNull(Span span, AttributeKey attributeKey, T value) { - if (value == null) { - return; - } - span.setAttribute(attributeKey, value); - } - - public static > MessagingSpanCustomizer create() { - return new DefaultMessagingProcessSpanCustomizer<>(); - } - - DefaultMessagingProcessSpanCustomizer() {} -} diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java index 6780d433a..fcc4958c5 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java @@ -29,32 +29,15 @@ public interface MessagingProcessRequest { String getMessageId(); - default String getOperationName() { - return "process"; - } - - default String getOperationType() { - return "process"; - } - - default String getConsumerGroupName() { - return null; - } - - default String getDestinationSubscriptionName() { - return null; - } - default String getClientId() { - return null; + return null; } - default Long getBatchMessageCount() { - return null; + return null; } default String getDestinationPartitionId() { - return null; + return null; } /** @@ -65,6 +48,6 @@ default String getDestinationPartitionId() { * returned instead. */ default List getMessageHeader(String name) { - return emptyList(); - } + return emptyList(); + } } diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessResponse.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessResponse.java deleted file mode 100644 index 8c469277a..000000000 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessResponse.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.opentelemetry.contrib.messaging.wrappers.semconv; - -/** - * An interface to expose messaging properties for the pre-defined process wrapper. - * - *

Only be created on demand. - * - *

Inspired from MessagingAttributesGetter. - */ -public interface MessagingProcessResponse { - - default String getMessageId() { - return null; - } - - default Long getBatchMessageCount() { - return null; - } - - T getOriginalResponse(); -} diff --git a/messaging-wrappers/api/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider b/messaging-wrappers/api/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider deleted file mode 100644 index 553d222bc..000000000 --- a/messaging-wrappers/api/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider +++ /dev/null @@ -1,2 +0,0 @@ -internal.io.opentelemetry.contrib.messaging.wrappers.AwsConfigurablePropagator -internal.io.opentelemetry.contrib.messaging.wrappers.AwsXrayLambdaConfigurablePropagator diff --git a/messaging-wrappers/api/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider b/messaging-wrappers/api/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider deleted file mode 100644 index a7f8dd670..000000000 --- a/messaging-wrappers/api/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider +++ /dev/null @@ -1,2 +0,0 @@ -internal.io.opentelemetry.contrib.messaging.wrappers.AwsXrayComponentProvider -internal.io.opentelemetry.contrib.messaging.wrappers.AwsXrayLambdaComponentProvider diff --git a/messaging-wrappers/kafka-clients/build.gradle.kts b/messaging-wrappers/kafka-clients/build.gradle.kts new file mode 100644 index 000000000..6f89a96f4 --- /dev/null +++ b/messaging-wrappers/kafka-clients/build.gradle.kts @@ -0,0 +1,25 @@ +plugins { + id("otel.java-conventions") + + id("otel.publish-conventions") +} + +description = "OpenTelemetry Messaging Wrappers - kafka-clients implementation" +otelJava.moduleName.set("io.opentelemetry.contrib.messaging.wrappers.kafka") + +dependencies { + api(project(":messaging-wrappers:api")) + + // FIXME: We shouldn't depend on the library "opentelemetry-kafka-clients-common" directly because the api in this + // package could be mutable, unless the components were maintained in "opentelemetry-java-instrumentation" project. + // implementation("io.opentelemetry.instrumentation:opentelemetry-kafka-clients-common:2.13.3-alpha") + + compileOnly("org.apache.kafka:kafka-clients:0.11.0.0") + + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + testImplementation("io.opentelemetry:opentelemetry-sdk-trace") + testImplementation("io.opentelemetry:opentelemetry-sdk-testing") + + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") + testImplementation("uk.org.webcompere:system-stubs-jupiter:2.0.3") +} diff --git a/messaging-wrappers/kafka-clients/gradle.properties b/messaging-wrappers/kafka-clients/gradle.properties new file mode 100644 index 000000000..a0402e1e2 --- /dev/null +++ b/messaging-wrappers/kafka-clients/gradle.properties @@ -0,0 +1,2 @@ +# TODO: uncomment when ready to mark as stable +# otel.stable=true diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaHelper.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaHelper.java new file mode 100644 index 000000000..88d32cc50 --- /dev/null +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaHelper.java @@ -0,0 +1,13 @@ +package io.opentelemetry.contrib.messaging.wrappers.kafka; + +import io.opentelemetry.contrib.messaging.wrappers.kafka.semconv.KafkaProcessRequest; + +public final class KafkaHelper { + + public static KafkaProcessWrapperBuilder processWrapperBuilder() { + return new KafkaProcessWrapperBuilder<>(); + } + + private KafkaHelper() { + } +} diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaProcessWrapperBuilder.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaProcessWrapperBuilder.java new file mode 100644 index 000000000..80630b7f8 --- /dev/null +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaProcessWrapperBuilder.java @@ -0,0 +1,13 @@ +package io.opentelemetry.contrib.messaging.wrappers.kafka; + +import io.opentelemetry.contrib.messaging.wrappers.DefaultMessagingProcessWrapperBuilder; +import io.opentelemetry.contrib.messaging.wrappers.kafka.semconv.KafkaProcessRequest; + +public class KafkaProcessWrapperBuilder + extends DefaultMessagingProcessWrapperBuilder { + + KafkaProcessWrapperBuilder() { + super(); + super.textMapGetter = KafkaTextMapGetter.create(); + } +} diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaTextMapGetter.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaTextMapGetter.java new file mode 100644 index 000000000..efcb7718b --- /dev/null +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaTextMapGetter.java @@ -0,0 +1,48 @@ +package io.opentelemetry.contrib.messaging.wrappers.kafka; + +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.contrib.messaging.wrappers.kafka.semconv.KafkaProcessRequest; +import org.apache.kafka.common.header.Header; + +import javax.annotation.Nullable; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +/** + * Copied from KafkaConsumerRecordGetter. + * */ +public class KafkaTextMapGetter implements TextMapGetter { + + public static TextMapGetter create() { + return new KafkaTextMapGetter<>(); + } + + @Override + public Iterable keys(@Nullable REQUEST carrier) { + if (carrier == null || carrier.getRecord() == null) { + return Collections.emptyList(); + } + return StreamSupport.stream(carrier.getRecord().headers().spliterator(), false) + .map(Header::key) + .collect(Collectors.toList()); + } + + @Nullable + @Override + public String get(@Nullable REQUEST carrier, String key) { + if (carrier == null || carrier.getRecord() == null) { + return null; + } + Header header = carrier.getRecord().headers().lastHeader(key); + if (header == null) { + return null; + } + byte[] value = header.value(); + if (value == null) { + return null; + } + return new String(value, StandardCharsets.UTF_8); + } +} \ No newline at end of file diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/package-info.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/package-info.java new file mode 100644 index 000000000..31364e41f --- /dev/null +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/package-info.java @@ -0,0 +1,2 @@ +/** OpenTelemetry messaging wrappers extension. */ +package io.opentelemetry.contrib.messaging.wrappers.kafka; diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesExtractor.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesExtractor.java new file mode 100644 index 000000000..2950e7f7f --- /dev/null +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesExtractor.java @@ -0,0 +1,72 @@ +package io.opentelemetry.contrib.messaging.wrappers.kafka.semconv; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import org.apache.kafka.clients.consumer.ConsumerRecord; + +import javax.annotation.Nullable; +import java.nio.ByteBuffer; + +/** + * Copied from KafkaConsumerAttributesExtractor. + * */ +public final class KafkaConsumerAttributesExtractor + implements AttributesExtractor { + + // copied from MessagingIncubatingAttributes + private static final AttributeKey MESSAGING_DESTINATION_PARTITION_ID = + AttributeKey.stringKey("messaging.destination.partition.id"); + private static final AttributeKey MESSAGING_KAFKA_CONSUMER_GROUP = + AttributeKey.stringKey("messaging.kafka.consumer.group"); + private static final AttributeKey MESSAGING_KAFKA_MESSAGE_KEY = + AttributeKey.stringKey("messaging.kafka.message.key"); + private static final AttributeKey MESSAGING_KAFKA_MESSAGE_OFFSET = + AttributeKey.longKey("messaging.kafka.message.offset"); + private static final AttributeKey MESSAGING_KAFKA_MESSAGE_TOMBSTONE = + AttributeKey.booleanKey("messaging.kafka.message.tombstone"); + + public static AttributesExtractor create() { + return new KafkaConsumerAttributesExtractor<>(); + } + + @Override + public void onStart( + AttributesBuilder attributes, Context parentContext, REQUEST request) { + + ConsumerRecord record = request.getRecord(); + + attributes.put(MESSAGING_DESTINATION_PARTITION_ID, String.valueOf(record.partition())); + attributes.put(MESSAGING_KAFKA_MESSAGE_OFFSET, record.offset()); + + Object key = record.key(); + if (key != null && canSerialize(key.getClass())) { + attributes.put(MESSAGING_KAFKA_MESSAGE_KEY, key.toString()); + } + if (record.value() == null) { + attributes.put(MESSAGING_KAFKA_MESSAGE_TOMBSTONE, true); + } + + String consumerGroup = request.getConsumerGroup(); + if (consumerGroup != null) { + attributes.put(MESSAGING_KAFKA_CONSUMER_GROUP, consumerGroup); + } + } + + private static boolean canSerialize(Class keyClass) { + // we make a simple assumption here that we can serialize keys by simply calling toString() + // and that does not work for byte[] or ByteBuffer + return !(keyClass.isArray() || keyClass == ByteBuffer.class); + } + + @Override + public void onEnd( + AttributesBuilder attributes, + Context context, + REQUEST request, + @Nullable Void unused, + @Nullable Throwable error) {} + + private KafkaConsumerAttributesExtractor() {} +} \ No newline at end of file diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaProcessRequest.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaProcessRequest.java new file mode 100644 index 000000000..ae81d7072 --- /dev/null +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaProcessRequest.java @@ -0,0 +1,122 @@ +package io.opentelemetry.contrib.messaging.wrappers.kafka.semconv; + +import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; +import org.apache.kafka.clients.consumer.ConsumerRecord; + +import javax.annotation.Nullable; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class KafkaProcessRequest implements MessagingProcessRequest { + + private final ConsumerRecord consumerRecord; + + private final String clientId; + + private final String consumerGroup; + + public static KafkaProcessRequest of(ConsumerRecord consumerRecord) { + return of(consumerRecord, null, null); + } + + public static KafkaProcessRequest of(ConsumerRecord consumerRecord, String consumerGroup, String clientId) { + return new KafkaProcessRequest(consumerRecord, consumerGroup, clientId); + } + + @Override + public String getSystem() { + return "kafka"; + } + + @Override + public String getDestination() { + if (this.consumerRecord == null) { + return null; + } + return this.consumerRecord.topic(); + } + + @Nullable + @Override + public String getDestinationTemplate() { + return null; + } + + @Override + public boolean isTemporaryDestination() { + return false; + } + + @Override + public boolean isAnonymousDestination() { + return false; + } + + @Override + @Nullable + public String getConversationId() { + return null; + } + + @Nullable + @Override + public Long getMessageBodySize() { + if (this.consumerRecord == null) { + return null; + } + long size = this.consumerRecord.serializedValueSize(); + return size >= 0 ? size : null; + } + + @Nullable + @Override + public Long getMessageEnvelopeSize() { + return null; + } + + @Override + @Nullable + public String getMessageId() { + return null; + } + + @Nullable + @Override + public String getClientId() { + return this.clientId; + } + + @Nullable + @Override + public Long getBatchMessageCount() { + return null; + } + + @Override + public List getMessageHeader(String name) { + if (this.consumerRecord == null) { + return Collections.emptyList(); + } + return StreamSupport.stream(this.consumerRecord.headers().headers(name).spliterator(), false) + .map(header -> new String(header.value(), StandardCharsets.UTF_8)) + .collect(Collectors.toList()); + } + + @Nullable + public String getConsumerGroup() { + return this.consumerGroup; + } + + public ConsumerRecord getRecord() { + return consumerRecord; + } + + private KafkaProcessRequest(ConsumerRecord consumerRecord, String consumerGroup, String clientId) { + this.consumerRecord = consumerRecord; + this.consumerGroup = consumerGroup; + this.clientId = clientId; + } +} diff --git a/messaging-wrappers/mns/build.gradle.kts b/messaging-wrappers/mns/build.gradle.kts index f2bd0081c..6fb17688c 100644 --- a/messaging-wrappers/mns/build.gradle.kts +++ b/messaging-wrappers/mns/build.gradle.kts @@ -4,8 +4,8 @@ plugins { id("otel.publish-conventions") } -description = "OpenTelemetry Messaging Wrappers" -otelJava.moduleName.set("io.opentelemetry.contrib.messaging.wrappers") +description = "OpenTelemetry Messaging Wrappers - mns-clients implementation" +otelJava.moduleName.set("io.opentelemetry.contrib.messaging.wrappers.mns") repositories { // FIXME: publish mns-client to maven central repository diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSHelper.java b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSHelper.java index e5beb9393..99214e861 100644 --- a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSHelper.java +++ b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSHelper.java @@ -4,10 +4,12 @@ import com.aliyun.mns.model.MessagePropertyValue; import com.aliyun.mns.model.MessageSystemPropertyName; import com.aliyun.mns.model.MessageSystemPropertyValue; +import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessRequest; public final class MNSHelper { - private MNSHelper() { + public static MNSProcessWrapperBuilder processWrapperBuilder() { + return new MNSProcessWrapperBuilder<>(); } public static String getMessageHeader(BaseMessage message, String name) { @@ -40,4 +42,7 @@ public static MessageSystemPropertyName convert2SystemPropertyName(String name) } return null; } + + private MNSHelper() { + } } diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapper.java b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapper.java deleted file mode 100644 index 1f2fc85fc..000000000 --- a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapper.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.opentelemetry.contrib.messaging.wrappers.mns; - -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.context.propagation.TextMapGetter; -import io.opentelemetry.contrib.messaging.wrappers.MessagingProcessWrapper; -import io.opentelemetry.contrib.messaging.wrappers.MessagingSpanCustomizer; -import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessRequest; -import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessResponse; -import java.util.List; - -public class MNSProcessWrapper> - extends MessagingProcessWrapper { - - protected MNSProcessWrapper(OpenTelemetry openTelemetry, - TextMapGetter textMapGetter, - List> spanCustomizers) { - super(openTelemetry, textMapGetter, spanCustomizers); - } -} diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapperBuilder.java b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapperBuilder.java index 732a0e2d6..5a302d851 100644 --- a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapperBuilder.java +++ b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapperBuilder.java @@ -1,58 +1,13 @@ package io.opentelemetry.contrib.messaging.wrappers.mns; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.context.propagation.TextMapGetter; -import io.opentelemetry.contrib.messaging.wrappers.MessagingSpanCustomizer; +import io.opentelemetry.contrib.messaging.wrappers.DefaultMessagingProcessWrapperBuilder; import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessRequest; -import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessResponse; -import io.opentelemetry.contrib.messaging.wrappers.semconv.DefaultMessagingProcessSpanCustomizer; -import java.util.ArrayList; -import java.util.List; -public class MNSProcessWrapperBuilder> { +public class MNSProcessWrapperBuilder + extends DefaultMessagingProcessWrapperBuilder { - private OpenTelemetry openTelemetry; - - private TextMapGetter textMapGetter; - - private List> spanCustomizers; - - public static > MNSProcessWrapperBuilder create() { - return new MNSProcessWrapperBuilder<>(); - } - - public MNSProcessWrapperBuilder openTelemetry(OpenTelemetry openTelemetry) { - this.openTelemetry = openTelemetry; - return this; - } - - public MNSProcessWrapperBuilder textMapGetter(TextMapGetter textMapGetter) { - this.textMapGetter = textMapGetter; - return this; - } - - public MNSProcessWrapperBuilder spanCustomizers( - List> spanCustomizers) { - this.spanCustomizers = spanCustomizers; - return this; - } - - public MNSProcessWrapperBuilder addSpanCustomizers( - MessagingSpanCustomizer spanCustomizer) { - this.spanCustomizers.add(spanCustomizer); - return this; - } - - public MNSProcessWrapper build() { - return new MNSProcessWrapper<>(this.openTelemetry, this.textMapGetter, this.spanCustomizers); - } - - private MNSProcessWrapperBuilder() { - // init by default - this.openTelemetry = GlobalOpenTelemetry.get(); - this.spanCustomizers = new ArrayList<>(); - this.spanCustomizers.add(DefaultMessagingProcessSpanCustomizer.create()); - this.textMapGetter = MNSTextMapGetter.create(); - } + MNSProcessWrapperBuilder() { + super(); + super.textMapGetter = MNSTextMapGetter.create(); + } } diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSTextMapGetter.java b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSTextMapGetter.java index 7b11efc14..e56f1c1f2 100644 --- a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSTextMapGetter.java +++ b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSTextMapGetter.java @@ -5,6 +5,8 @@ import com.aliyun.mns.model.*; import io.opentelemetry.context.propagation.TextMapGetter; import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessRequest; + +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -12,74 +14,58 @@ public class MNSTextMapGetter implements TextMapGetter { - public static TextMapGetter create() { - return new MNSTextMapGetter<>(); - } - - @Override - public Iterable keys(REQUEST carrier) { - if (carrier == null || carrier.getMessage() == null) { - return emptyList(); - } - Message message = carrier.getMessage(); + public static TextMapGetter create() { + return new MNSTextMapGetter<>(); + } - Map systemProperties = message.getSystemProperties(); - if (systemProperties == null) { - systemProperties = Collections.emptyMap(); - } - Map userProperties = message.getUserProperties(); - if (userProperties == null) { - userProperties = Collections.emptyMap(); - } - List keys = new ArrayList<>(systemProperties.size() + userProperties.size()); - keys.addAll(systemProperties.keySet()); - keys.addAll(userProperties.keySet()); - return keys; + @Override + public Iterable keys(@Nullable REQUEST carrier) { + if (carrier == null || carrier.getMessage() == null) { + return emptyList(); } + Message message = carrier.getMessage(); - @Override - public String get(REQUEST carrier, String key) { - if (carrier == null || carrier.getMessage() == null) { - return null; - } - Message message = carrier.getMessage(); - - // the system property should always take precedence over the user property - MessageSystemPropertyName systemPropertyName = convert2SystemPropertyName(key); - if (systemPropertyName != null) { - MessageSystemPropertyValue value = message.getSystemProperty(systemPropertyName); - if (value != null) { - return value.getStringValueByType(); - } - } + Map systemProperties = message.getSystemProperties(); + if (systemProperties == null) { + systemProperties = Collections.emptyMap(); + } + Map userProperties = message.getUserProperties(); + if (userProperties == null) { + userProperties = Collections.emptyMap(); + } + List keys = new ArrayList<>(systemProperties.size() + userProperties.size()); + keys.addAll(systemProperties.keySet()); + keys.addAll(userProperties.keySet()); + return keys; + } - Map userProperties = message.getUserProperties(); - if (userProperties == null || userProperties.isEmpty()) { - return null; - } - MessagePropertyValue value = userProperties.getOrDefault(key, null); - if (value != null) { - return value.getStringValueByType(); - } - return null; + @Override + public String get(@Nullable REQUEST carrier, String key) { + if (carrier == null || carrier.getMessage() == null) { + return null; } + Message message = carrier.getMessage(); - /** - * see {@link MessageSystemPropertyName} - * */ - private MessageSystemPropertyName convert2SystemPropertyName(String name) { - if (name == null) { - return null; - } else if (name.equals(MessageSystemPropertyName.TRACE_PARENT.getValue())) { - return MessageSystemPropertyName.TRACE_PARENT; - } else if (name.equals(MessageSystemPropertyName.BAGGAGE.getValue())) { - return MessageSystemPropertyName.BAGGAGE; - } else if (name.equals(MessageSystemPropertyName.TRACE_STATE.getValue())) { - return MessageSystemPropertyName.TRACE_STATE; - } - return null; + // the system property should always take precedence over the user property + MessageSystemPropertyName systemPropertyName = MNSHelper.convert2SystemPropertyName(key); + if (systemPropertyName != null) { + MessageSystemPropertyValue value = message.getSystemProperty(systemPropertyName); + if (value != null) { + return value.getStringValueByType(); + } } - private MNSTextMapGetter() { + Map userProperties = message.getUserProperties(); + if (userProperties == null || userProperties.isEmpty()) { + return null; } + MessagePropertyValue value = userProperties.getOrDefault(key, null); + if (value != null) { + return value.getStringValueByType(); + } + return null; + } + + private MNSTextMapGetter() { + } } diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/example/MNSConsumer.java b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/example/MNSConsumer.java index a2b0aa613..45032cae2 100644 --- a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/example/MNSConsumer.java +++ b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/example/MNSConsumer.java @@ -7,10 +7,9 @@ import com.aliyun.mns.common.ServiceException; import com.aliyun.mns.model.Message; import com.aliyun.mns.model.MessageSystemPropertyValue; -import io.opentelemetry.contrib.messaging.wrappers.mns.MNSProcessWrapper; -import io.opentelemetry.contrib.messaging.wrappers.mns.MNSProcessWrapperBuilder; +import io.opentelemetry.contrib.messaging.wrappers.MessagingProcessWrapper; +import io.opentelemetry.contrib.messaging.wrappers.mns.MNSHelper; import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessRequest; -import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessResponse; import static com.aliyun.mns.model.MessageSystemPropertyName.TRACE_PARENT; @@ -18,7 +17,7 @@ public class MNSConsumer { public static void main(String[] args) { // 1. create wrapper by default - MNSProcessWrapper> wrapper = MNSProcessWrapperBuilder.create().build(); + MessagingProcessWrapper wrapper = MNSHelper.processWrapperBuilder().build(); CloudAccount account = new CloudAccount("my-ak", "my-sk", "endpoint"); MNSClient client = account.getMNSClient(); diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java index 798a7be5f..7a3d5b61f 100644 --- a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java +++ b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java @@ -9,87 +9,87 @@ public class MNSProcessRequest implements MessagingProcessRequest { - private final Message message; - - private final String destination; - - public static MNSProcessRequest of(Message message) { - return of(message, null); - } - - public static MNSProcessRequest of(Message message, String destination) { - return new MNSProcessRequest(message, destination); - } - - @Override - public String getSystem() { - return "mns"; - } - - @Override - public String getDestination() { - return this.destination; - } - - @Override - public String getDestinationTemplate() { - return null; - } - - @Override - public boolean isTemporaryDestination() { - return false; - } - - @Override - public boolean isAnonymousDestination() { - return false; + private final Message message; + + private final String destination; + + public static MNSProcessRequest of(Message message) { + return of(message, null); + } + + public static MNSProcessRequest of(Message message, String destination) { + return new MNSProcessRequest(message, destination); + } + + @Override + public String getSystem() { + return "mns"; + } + + @Override + public String getDestination() { + return this.destination; + } + + @Override + public String getDestinationTemplate() { + return null; + } + + @Override + public boolean isTemporaryDestination() { + return false; + } + + @Override + public boolean isAnonymousDestination() { + return false; + } + + @Override + public String getConversationId() { + return null; + } + + @Override + public Long getMessageBodySize() { + if (message == null) { + return null; } - - @Override - public String getConversationId() { - return null; + return (long) message.getMessageBodyAsBytes().length; + } + + @Override + public Long getMessageEnvelopeSize() { + return null; + } + + @Override + public String getMessageId() { + if (message == null) { + return null; } + return message.getMessageId(); + } - @Override - public Long getMessageBodySize() { - if (message == null) { - return null; - } - return (long) message.getMessageBodyAsBytes().length; + @Override + public List getMessageHeader(String name) { + if (message == null) { + return Collections.emptyList(); } - - @Override - public Long getMessageEnvelopeSize() { - return null; + String header = MNSHelper.getMessageHeader(message, name); + if (header == null) { + return Collections.emptyList(); } + return Collections.singletonList(header); + } - @Override - public String getMessageId() { - if (message == null) { - return null; - } - return message.getMessageId(); - } + public Message getMessage() { + return message; + } - @Override - public List getMessageHeader(String name) { - if (message == null) { - return Collections.emptyList(); - } - String header = MNSHelper.getMessageHeader(message, name); - if (header == null) { - return Collections.emptyList(); - } - return Collections.singletonList(header); - } - - public Message getMessage() { - return message; - } - - private MNSProcessRequest(Message message, String destination) { - this.message = message; - this.destination = destination; - } + private MNSProcessRequest(Message message, String destination) { + this.message = message; + this.destination = destination; + } } diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessResponse.java b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessResponse.java deleted file mode 100644 index cfefc72c8..000000000 --- a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessResponse.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.opentelemetry.contrib.messaging.wrappers.mns.semconv; - -import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessResponse; - -public class MNSProcessResponse implements MessagingProcessResponse { - - private final T originalResponse; - - public static MNSProcessResponse of(T originalResponse) { - return new MNSProcessResponse<>(originalResponse); - } - - @Override - public T getOriginalResponse() { - return originalResponse; - } - - public MNSProcessResponse(T originalResponse) { - this.originalResponse = originalResponse; - } -} diff --git a/messaging-wrappers/mns/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider b/messaging-wrappers/mns/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider deleted file mode 100644 index 553d222bc..000000000 --- a/messaging-wrappers/mns/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider +++ /dev/null @@ -1,2 +0,0 @@ -internal.io.opentelemetry.contrib.messaging.wrappers.AwsConfigurablePropagator -internal.io.opentelemetry.contrib.messaging.wrappers.AwsXrayLambdaConfigurablePropagator diff --git a/messaging-wrappers/mns/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider b/messaging-wrappers/mns/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider deleted file mode 100644 index a7f8dd670..000000000 --- a/messaging-wrappers/mns/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider +++ /dev/null @@ -1,2 +0,0 @@ -internal.io.opentelemetry.contrib.messaging.wrappers.AwsXrayComponentProvider -internal.io.opentelemetry.contrib.messaging.wrappers.AwsXrayLambdaComponentProvider diff --git a/settings.gradle.kts b/settings.gradle.kts index 385baf346..7b7d8f986 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -54,6 +54,7 @@ include(":jmx-scraper:test-app") include(":jmx-scraper:test-webapp") include(":maven-extension") include(":messaging-wrappers:api") +include(":messaging-wrappers:kafka-clients") include(":messaging-wrappers:mns") include(":micrometer-meter-provider") include(":noop-api") From 6f3587be0abfe4553e3f240d8d88f02c93f44106 Mon Sep 17 00:00:00 2001 From: cirilla-zmh Date: Wed, 2 Apr 2025 13:49:08 +0800 Subject: [PATCH 03/17] add test for kafka clients --- ...DefaultMessagingProcessWrapperBuilder.java | 18 +- .../wrappers/MessagingProcessWrapper.java | 6 +- .../messaging/wrappers/NoopTextMapGetter.java | 26 ++ .../semconv/MessagingProcessRequest.java | 11 + .../kafka-clients/build.gradle.kts | 18 +- .../kafka/semconv/KafkaProcessRequest.java | 19 +- .../wrappers/kafka/KafkaClientBaseTest.java | 253 ++++++++++++++++++ .../wrappers/kafka/KafkaClientTest.java | 180 +++++++++++++ .../internal/AutoConfigTestProperties.java | 42 +++ .../internal/AutoConfiguredDataCapture.java | 45 ++++ messaging-wrappers/mns/build.gradle.kts | 3 - .../mns/semconv/MNSProcessRequest.java | 21 +- 12 files changed, 606 insertions(+), 36 deletions(-) create mode 100644 messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/NoopTextMapGetter.java create mode 100644 messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java create mode 100644 messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java create mode 100644 messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfigTestProperties.java create mode 100644 messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfiguredDataCapture.java diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java index b2eb03d01..3c8b7e99d 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java @@ -1,5 +1,6 @@ package io.opentelemetry.contrib.messaging.wrappers; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.context.propagation.TextMapGetter; @@ -9,23 +10,28 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.List; public class DefaultMessagingProcessWrapperBuilder { + @Nullable private OpenTelemetry openTelemetry; + @Nullable protected TextMapGetter textMapGetter; - protected List> attributesExtractors; - + @CanIgnoreReturnValue public DefaultMessagingProcessWrapperBuilder openTelemetry(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; return this; } + protected List> attributesExtractors; + + @CanIgnoreReturnValue public DefaultMessagingProcessWrapperBuilder textMapGetter(TextMapGetter textMapGetter) { this.textMapGetter = textMapGetter; return this; @@ -35,6 +41,7 @@ public DefaultMessagingProcessWrapperBuilder textMapGetter(TextMapGette * This method overrides the original items. *

See {@link DefaultMessagingProcessWrapperBuilder#addAttributesExtractor} if you just want to append one.

* */ + @CanIgnoreReturnValue public DefaultMessagingProcessWrapperBuilder attributesExtractors( Collection> attributesExtractors) { this.attributesExtractors = new ArrayList<>(); @@ -42,6 +49,7 @@ public DefaultMessagingProcessWrapperBuilder attributesExtractors( return this; } + @CanIgnoreReturnValue public DefaultMessagingProcessWrapperBuilder addAttributesExtractor( AttributesExtractor attributesExtractor) { this.attributesExtractors.add(attributesExtractor); @@ -49,8 +57,10 @@ public DefaultMessagingProcessWrapperBuilder addAttributesExtractor( } public MessagingProcessWrapper build() { - return new MessagingProcessWrapper<>(this.openTelemetry == null ? GlobalOpenTelemetry.get() : this.openTelemetry, - this.textMapGetter, this.attributesExtractors); + return new MessagingProcessWrapper<>( + this.openTelemetry == null ? GlobalOpenTelemetry.get() : this.openTelemetry, + this.textMapGetter == null ? NoopTextMapGetter.create() : this.textMapGetter, + this.attributesExtractors); } protected DefaultMessagingProcessWrapperBuilder() { diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java index 8b6111d64..807a2717e 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java @@ -77,7 +77,7 @@ protected Span handleStart(REQUEST request) { return spanBuilder.setAllAttributes(builder.build()).startSpan(); } - protected void handleEnd(Span span, REQUEST request, Throwable t) { + protected void handleEnd(Span span, REQUEST request, @Nullable Throwable t) { AttributesBuilder builder = Attributes.builder(); for (AttributesExtractor extractor : this.attributesExtractors) { extractor.onEnd(builder, Context.current(), request, null, t); @@ -85,7 +85,7 @@ protected void handleEnd(Span span, REQUEST request, Throwable t) { span.end(); } - protected String getDefaultSpanName(String destination) { + protected String getDefaultSpanName(@Nullable String destination) { if (destination == null) { destination = "unknown"; } @@ -93,7 +93,7 @@ protected String getDefaultSpanName(String destination) { } protected MessagingProcessWrapper(OpenTelemetry openTelemetry, - @Nullable TextMapGetter textMapGetter, + TextMapGetter textMapGetter, List> attributesExtractors) { this.textMapPropagator = openTelemetry.getPropagators().getTextMapPropagator(); this.tracer = openTelemetry.getTracer(INSTRUMENTATION_SCOPE + "-" + INSTRUMENTATION_VERSION); diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/NoopTextMapGetter.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/NoopTextMapGetter.java new file mode 100644 index 000000000..5432c3e4d --- /dev/null +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/NoopTextMapGetter.java @@ -0,0 +1,26 @@ +package io.opentelemetry.contrib.messaging.wrappers; + +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; +import javax.annotation.Nullable; +import java.util.Collections; + +public class NoopTextMapGetter implements TextMapGetter { + + public static TextMapGetter create() { + return new NoopTextMapGetter<>(); + } + + @Override + public Iterable keys(REQUEST request) { + return Collections.emptyList(); + } + + @Nullable + @Override + public String get(@Nullable REQUEST request, String s) { + return null; + } + + private NoopTextMapGetter() {} +} diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java index fcc4958c5..ab5e50480 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java @@ -1,5 +1,6 @@ package io.opentelemetry.contrib.messaging.wrappers.semconv; +import javax.annotation.Nullable; import java.util.List; import static java.util.Collections.emptyList; @@ -13,29 +14,39 @@ public interface MessagingProcessRequest { String getSystem(); + @Nullable String getDestination(); + @Nullable String getDestinationTemplate(); boolean isTemporaryDestination(); boolean isAnonymousDestination(); + @Nullable String getConversationId(); + @Nullable Long getMessageBodySize(); + @Nullable Long getMessageEnvelopeSize(); + @Nullable String getMessageId(); + @Nullable default String getClientId() { return null; } + + @Nullable default Long getBatchMessageCount() { return null; } + @Nullable default String getDestinationPartitionId() { return null; } diff --git a/messaging-wrappers/kafka-clients/build.gradle.kts b/messaging-wrappers/kafka-clients/build.gradle.kts index 6f89a96f4..8828cb2e3 100644 --- a/messaging-wrappers/kafka-clients/build.gradle.kts +++ b/messaging-wrappers/kafka-clients/build.gradle.kts @@ -16,10 +16,22 @@ dependencies { compileOnly("org.apache.kafka:kafka-clients:0.11.0.0") + testImplementation("org.apache.kafka:kafka-clients:0.11.0.0") + testImplementation("io.opentelemetry.instrumentation:opentelemetry-kafka-clients-2.6") + + testAnnotationProcessor("com.google.auto.service:auto-service") + testCompileOnly("com.google.auto.service:auto-service-annotations") + testImplementation("org.junit.jupiter:junit-jupiter-api") + testImplementation("org.junit.jupiter:junit-jupiter-params") + testImplementation("org.testcontainers:kafka") + testImplementation("org.testcontainers:junit-jupiter") + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") - testImplementation("io.opentelemetry:opentelemetry-sdk-trace") + testImplementation("io.opentelemetry:opentelemetry-sdk") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") - testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") - testImplementation("uk.org.webcompere:system-stubs-jupiter:2.0.3") + testImplementation("io.opentelemetry.semconv:opentelemetry-semconv") + testImplementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating") + testImplementation("io.opentelemetry:opentelemetry-exporter-logging") + testImplementation("io.opentelemetry.javaagent:opentelemetry-testing-common") } diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaProcessRequest.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaProcessRequest.java index ae81d7072..4c801a23b 100644 --- a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaProcessRequest.java +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaProcessRequest.java @@ -5,7 +5,6 @@ import javax.annotation.Nullable; import java.nio.charset.StandardCharsets; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -14,15 +13,18 @@ public class KafkaProcessRequest implements MessagingProcessRequest { private final ConsumerRecord consumerRecord; + @Nullable private final String clientId; + @Nullable private final String consumerGroup; public static KafkaProcessRequest of(ConsumerRecord consumerRecord) { return of(consumerRecord, null, null); } - public static KafkaProcessRequest of(ConsumerRecord consumerRecord, String consumerGroup, String clientId) { + public static KafkaProcessRequest of(ConsumerRecord consumerRecord, + @Nullable String consumerGroup, @Nullable String clientId) { return new KafkaProcessRequest(consumerRecord, consumerGroup, clientId); } @@ -31,11 +33,9 @@ public String getSystem() { return "kafka"; } + @Nullable @Override public String getDestination() { - if (this.consumerRecord == null) { - return null; - } return this.consumerRecord.topic(); } @@ -64,9 +64,6 @@ public String getConversationId() { @Nullable @Override public Long getMessageBodySize() { - if (this.consumerRecord == null) { - return null; - } long size = this.consumerRecord.serializedValueSize(); return size >= 0 ? size : null; } @@ -97,9 +94,6 @@ public Long getBatchMessageCount() { @Override public List getMessageHeader(String name) { - if (this.consumerRecord == null) { - return Collections.emptyList(); - } return StreamSupport.stream(this.consumerRecord.headers().headers(name).spliterator(), false) .map(header -> new String(header.value(), StandardCharsets.UTF_8)) .collect(Collectors.toList()); @@ -114,7 +108,8 @@ public String getConsumerGroup() { return consumerRecord; } - private KafkaProcessRequest(ConsumerRecord consumerRecord, String consumerGroup, String clientId) { + private KafkaProcessRequest(ConsumerRecord consumerRecord, + @Nullable String consumerGroup, @Nullable String clientId) { this.consumerRecord = consumerRecord; this.consumerGroup = consumerGroup; this.clientId = clientId; diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java new file mode 100644 index 000000000..ba93103b7 --- /dev/null +++ b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java @@ -0,0 +1,253 @@ +package io.opentelemetry.contrib.messaging.wrappers.kafka; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_BATCH_MESSAGE_COUNT; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_PARTITION_ID; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_CONSUMER_GROUP; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_KEY; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_TOMBSTONE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.apache.kafka.clients.admin.AdminClient; +import org.apache.kafka.clients.admin.NewTopic; +import org.apache.kafka.clients.consumer.Consumer; +import org.apache.kafka.clients.consumer.ConsumerRebalanceListener; +import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.clients.producer.Producer; +import org.apache.kafka.common.TopicPartition; +import org.apache.kafka.common.serialization.IntegerDeserializer; +import org.apache.kafka.common.serialization.IntegerSerializer; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.common.serialization.StringSerializer; +import org.assertj.core.api.AbstractLongAssert; +import org.assertj.core.api.AbstractStringAssert; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.TestInstance; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.kafka.KafkaContainer; +import org.testcontainers.utility.DockerImageName; + +/** + * Copied from KafkaClientBaseTest. + * */ +@SuppressWarnings("OtelInternalJavadoc") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public abstract class KafkaClientBaseTest { + + private static final Logger logger = LoggerFactory.getLogger(KafkaClientBaseTest.class); + + protected static final String SHARED_TOPIC = "shared.topic"; + protected static final AttributeKey MESSAGING_CLIENT_ID = + AttributeKey.stringKey("messaging.client_id"); + + private KafkaContainer kafka; + protected Producer producer; + protected Consumer consumer; + private final CountDownLatch consumerReady = new CountDownLatch(1); + + public static final int partition = 0; + public static final TopicPartition topicPartition = new TopicPartition(SHARED_TOPIC, partition); + + @BeforeAll + void setupClass() throws ExecutionException, InterruptedException, TimeoutException { + kafka = + new KafkaContainer(DockerImageName.parse("apache/kafka:3.8.0")) + .withEnv("KAFKA_HEAP_OPTS", "-Xmx256m") + .withLogConsumer(new Slf4jLogConsumer(logger)) + .waitingFor(Wait.forLogMessage(".*started \\(kafka.server.Kafka.*Server\\).*", 1)) + .withStartupTimeout(Duration.ofMinutes(1)); + kafka.start(); + + // create test topic + HashMap adminProps = new HashMap<>(); + adminProps.put("bootstrap.servers", kafka.getBootstrapServers()); + + try (AdminClient admin = AdminClient.create(adminProps)) { + admin + .createTopics(Collections.singletonList(new NewTopic(SHARED_TOPIC, 1, (short) 1))) + .all() + .get(30, TimeUnit.SECONDS); + } + + producer = new KafkaProducer<>(producerProps()); + + consumer = new KafkaConsumer<>(consumerProps()); + + consumer.subscribe( + Collections.singletonList(SHARED_TOPIC), + new ConsumerRebalanceListener() { + @Override + public void onPartitionsRevoked(Collection collection) {} + + @Override + public void onPartitionsAssigned(Collection collection) { + consumerReady.countDown(); + } + }); + } + + public Map consumerProps() { + HashMap props = new HashMap<>(); + props.put("bootstrap.servers", kafka.getBootstrapServers()); + props.put("enable.auto.commit", "true"); + props.put("auto.commit.interval.ms", 10); + props.put("session.timeout.ms", "30000"); + props.put("key.deserializer", IntegerDeserializer.class.getName()); + props.put("value.deserializer", StringDeserializer.class.getName()); + return props; + } + + public Map producerProps() { + HashMap props = new HashMap<>(); + props.put("bootstrap.servers", kafka.getBootstrapServers()); + props.put("retries", 0); + props.put("batch.size", "16384"); + props.put("linger.ms", 1); + props.put("buffer.memory", "33554432"); + props.put("key.serializer", IntegerSerializer.class.getName()); + props.put("value.serializer", StringSerializer.class.getName()); + return props; + } + + @AfterAll + void cleanupClass() { + if (producer != null) { + producer.close(); + } + if (consumer != null) { + consumer.close(); + } + kafka.stop(); + } + + @SuppressWarnings("PreferJavaTimeOverload") + public void awaitUntilConsumerIsReady() throws InterruptedException { + if (consumerReady.await(0, TimeUnit.SECONDS)) { + return; + } + for (int i = 0; i < 60; i++) { + consumer.poll(Duration.ZERO); + if (consumerReady.await(3, TimeUnit.SECONDS)) { + break; + } + logger.info("Consumer has not been ready for {} time(s).", i); + } + if (consumerReady.getCount() != 0) { + throw new AssertionError("Consumer wasn't assigned any partitions!"); + } + consumer.seekToBeginning(Collections.emptyList()); + } + + @SuppressWarnings("deprecation") // using deprecated semconv + protected static List sendAttributes( + String messageKey, String messageValue, boolean testHeaders) { + List assertions = + new ArrayList<>( + Arrays.asList( + equalTo(MESSAGING_SYSTEM, "kafka"), + equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), + equalTo(MESSAGING_OPERATION, "publish"), + satisfies(MESSAGING_CLIENT_ID, stringAssert -> stringAssert.startsWith("producer")), + satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty), + satisfies(MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative))); + if (messageKey != null) { + assertions.add(equalTo(MESSAGING_KAFKA_MESSAGE_KEY, messageKey)); + } + if (messageValue == null) { + assertions.add(equalTo(MESSAGING_KAFKA_MESSAGE_TOMBSTONE, true)); + } + if (testHeaders) { + assertions.add( + equalTo( + AttributeKey.stringArrayKey("messaging.header.test_message_header"), + Collections.singletonList("test"))); + } + return assertions; + } + + @SuppressWarnings("deprecation") // using deprecated semconv + protected static List receiveAttributes(boolean testHeaders) { + List assertions = + new ArrayList<>( + Arrays.asList( + equalTo(MESSAGING_SYSTEM, "kafka"), + equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), + equalTo(MESSAGING_OPERATION, "receive"), + satisfies(MESSAGING_CLIENT_ID, stringAssert -> stringAssert.startsWith("consumer")), + satisfies(MESSAGING_BATCH_MESSAGE_COUNT, AbstractLongAssert::isPositive))); + // consumer group is not available in version 0.11 + if (Boolean.getBoolean("testLatestDeps")) { + assertions.add(equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test")); + } + if (testHeaders) { + assertions.add( + equalTo( + AttributeKey.stringArrayKey("messaging.header.test_message_header"), + Collections.singletonList("test"))); + } + return assertions; + } + + @SuppressWarnings("deprecation") // using deprecated semconv + protected static List processAttributes( + String messageKey, String messageValue, boolean testHeaders) { + List assertions = + new ArrayList<>( + Arrays.asList( + equalTo(MESSAGING_SYSTEM, "kafka"), + equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), + equalTo(MESSAGING_OPERATION, "process"), + satisfies(MESSAGING_CLIENT_ID, stringAssert -> stringAssert.startsWith("consumer")), + satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty), + satisfies(MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative), + satisfies( + AttributeKey.longKey("kafka.record.queue_time_ms"), + AbstractLongAssert::isNotNegative))); + // consumer group is not available in version 0.11 + if (Boolean.getBoolean("testLatestDeps")) { + assertions.add(equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test")); + } + if (messageKey != null) { + assertions.add(equalTo(MESSAGING_KAFKA_MESSAGE_KEY, messageKey)); + } + if (messageValue == null) { + assertions.add(equalTo(MESSAGING_KAFKA_MESSAGE_TOMBSTONE, true)); + } else { + assertions.add( + equalTo( + MESSAGING_MESSAGE_BODY_SIZE, messageValue.getBytes(StandardCharsets.UTF_8).length)); + } + if (testHeaders) { + assertions.add( + equalTo( + AttributeKey.stringArrayKey("messaging.header.test_message_header"), + Collections.singletonList("test"))); + } + return assertions; + } +} diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java new file mode 100644 index 000000000..c359be4dc --- /dev/null +++ b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java @@ -0,0 +1,180 @@ +package io.opentelemetry.contrib.messaging.wrappers.kafka; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Scope; +import io.opentelemetry.contrib.messaging.wrappers.MessagingProcessWrapper; +import io.opentelemetry.contrib.messaging.wrappers.kafka.internal.AutoConfigTestProperties; +import io.opentelemetry.contrib.messaging.wrappers.kafka.internal.AutoConfiguredDataCapture; +import io.opentelemetry.contrib.messaging.wrappers.kafka.semconv.KafkaProcessRequest; +import io.opentelemetry.instrumentation.kafkaclients.v2_6.TracingConsumerInterceptor; +import io.opentelemetry.instrumentation.kafkaclients.v2_6.TracingProducerInterceptor; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.junit.jupiter.api.Test; +import java.time.Duration; +import java.util.Map; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class KafkaClientTest extends KafkaClientBaseTest { + + static final String greeting = "Hello Kafka!"; + + static final String clientId = "test-consumer-1"; + + static final String groupId = "test"; + + @Override + public Map producerProps() { + Map props = super.producerProps(); + props.put( + ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); + return props; + } + + @Override + public Map consumerProps() { + Map props = super.consumerProps(); + props.put( + ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); + props.put(ConsumerConfig.CLIENT_ID_CONFIG, clientId); + props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); + return props; + } + + @Test + void testInterceptors() throws InterruptedException { + try (AutoConfigTestProperties props = new AutoConfigTestProperties() + .put("otel.instrumentation.messaging.experimental.receive-telemetry.enabled", "true")) { + OpenTelemetry otel = GlobalOpenTelemetry.get(); + Tracer tracer = otel.getTracer("test"); + MessagingProcessWrapper wrapper = KafkaHelper.processWrapperBuilder() + .openTelemetry(otel) + .build(); + + sendWithParent(tracer); + + awaitUntilConsumerIsReady(); + + consumeWithChild(tracer, wrapper); + + assertTraces(); + } + } + + @SuppressWarnings("FutureReturnValueIgnored") + public void sendWithParent(Tracer tracer) { + Span parent = tracer.spanBuilder("parent").startSpan(); + try (Scope scope = parent.makeCurrent()) { + producer.send(new ProducerRecord<>(SHARED_TOPIC, greeting), + (meta, ex) -> { + if (ex == null) { + tracer.spanBuilder("producer callback").startSpan().end(); + } else { + tracer.spanBuilder("producer exception: " + ex).startSpan().end(); + } + }); + } + } + + public void consumeWithChild(Tracer tracer, MessagingProcessWrapper wrapper) { + // check that the message was received + ConsumerRecords records = consumer.poll(Duration.ofSeconds(5)); + assertThat(records.count()).isEqualTo(1); + ConsumerRecord record = records.iterator().next(); + assertThat(record.value()).isEqualTo(greeting); + assertThat(record.key()).isNull(); + + wrapper.doProcess(KafkaProcessRequest.of(record, groupId, clientId), () -> { + tracer.spanBuilder("process child").startSpan().end(); + }); + } + + public void assertTraces() { + await() + .untilAsserted( + () -> { + assertThat(AutoConfiguredDataCapture.getSpans()) + .hasSize(5); + } + ); +// testing.waitAndAssertSortedTraces( +// orderByRootSpanName("parent", SHARED_TOPIC + " receive", "producer callback"), +// trace -> { +// trace.hasSpansSatisfyingExactly( +// span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), +// span -> +// span.hasName(SHARED_TOPIC + " publish") +// .hasKind(SpanKind.PRODUCER) +// .hasParent(trace.getSpan(0)) +// .hasAttributesSatisfyingExactly( +// equalTo(MESSAGING_SYSTEM, "kafka"), +// equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), +// equalTo(MESSAGING_OPERATION, "publish"), +// satisfies( +// MESSAGING_CLIENT_ID, +// stringAssert -> stringAssert.startsWith("producer")))); +// SpanContext spanContext = trace.getSpan(1).getSpanContext(); +// producerSpanContext.set( +// SpanContext.createFromRemoteParent( +// spanContext.getTraceId(), +// spanContext.getSpanId(), +// spanContext.getTraceFlags(), +// spanContext.getTraceState())); +// }, +// trace -> +// trace.hasSpansSatisfyingExactly( +// span -> +// span.hasName(SHARED_TOPIC + " receive") +// .hasKind(SpanKind.CONSUMER) +// .hasNoParent() +// .hasLinksSatisfying(links -> assertThat(links).isEmpty()) +// .hasAttributesSatisfyingExactly( +// equalTo(MESSAGING_SYSTEM, "kafka"), +// equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), +// equalTo(MESSAGING_OPERATION, "receive"), +// equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test"), +// satisfies( +// MESSAGING_CLIENT_ID, +// stringAssert -> stringAssert.startsWith("consumer")), +// equalTo(MESSAGING_BATCH_MESSAGE_COUNT, 1)), +// span -> +// span.hasName(SHARED_TOPIC + " process") +// .hasKind(SpanKind.CONSUMER) +// .hasParent(trace.getSpan(0)) +// .hasLinks(LinkData.create(producerSpanContext.get())) +// .hasAttributesSatisfyingExactly( +// equalTo(MESSAGING_SYSTEM, "kafka"), +// equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), +// equalTo(MESSAGING_OPERATION, "process"), +// equalTo( +// MESSAGING_MESSAGE_BODY_SIZE, +// greeting.getBytes(StandardCharsets.UTF_8).length), +// satisfies( +// MESSAGING_DESTINATION_PARTITION_ID, +// AbstractStringAssert::isNotEmpty), +// satisfies( +// MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative), +// equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test"), +// satisfies( +// MESSAGING_CLIENT_ID, +// stringAssert -> stringAssert.startsWith("consumer"))), +// span -> +// span.hasName("process child") +// .hasKind(SpanKind.INTERNAL) +// .hasParent(trace.getSpan(1))), +// // ideally we'd want producer callback to be part of the main trace, we just aren't able to +// // instrument that +// trace -> +// trace.hasSpansSatisfyingExactly( +// span -> +// span.hasName("producer callback").hasKind(SpanKind.INTERNAL).hasNoParent())); + } +} diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfigTestProperties.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfigTestProperties.java new file mode 100644 index 000000000..60bebaae8 --- /dev/null +++ b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfigTestProperties.java @@ -0,0 +1,42 @@ +package io.opentelemetry.contrib.messaging.wrappers.kafka.internal; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.HashMap; +import java.util.Map; + +public class AutoConfigTestProperties implements AutoCloseable { + + public AutoConfigTestProperties() { + put("otel.java.global-autoconfigure.enabled", "true"); + put("otel.traces.exporter", "logging"); + put("otel.metrics.exporter", "logging"); + put("otel.logs.exporter", "logging"); + } + + private final Map originalValues = new HashMap<>(); + + @CanIgnoreReturnValue + public AutoConfigTestProperties put(String key, String value) { + if (!originalValues.containsKey(key)) { + originalValues.put(key, System.getProperty(key)); + } + if (value == null) { + System.clearProperty(key); + } else { + System.setProperty(key, value); + } + return this; + } + + @Override + public void close() { + for (String key : originalValues.keySet()) { + String value = originalValues.get(key); + if (value == null) { + System.clearProperty(key); + } else { + System.setProperty(key, value); + } + } + } +} diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfiguredDataCapture.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfiguredDataCapture.java new file mode 100644 index 000000000..a3b7299f9 --- /dev/null +++ b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfiguredDataCapture.java @@ -0,0 +1,45 @@ +package io.opentelemetry.contrib.messaging.wrappers.kafka.internal; + +import com.google.auto.service.AutoService; +import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer; +import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; +import io.opentelemetry.exporter.logging.LoggingSpanExporter; +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.util.List; + +@AutoService(AutoConfigurationCustomizerProvider.class) +public class AutoConfiguredDataCapture implements AutoConfigurationCustomizerProvider { + + private static final InMemorySpanExporter inMemorySpanExporter = InMemorySpanExporter.create(); + + /* + Returns the spans which have been exported by the autoconfigured global OpenTelemetry SDK. + */ + public static List getSpans() { + return inMemorySpanExporter.getFinishedSpanItems(); + } + + @Override + public void customize(AutoConfigurationCustomizer autoConfiguration) { + autoConfiguration.addSpanExporterCustomizer( + (spanExporter, config) -> { + // we piggy-back onto the autoconfigured logging exporter for now, + // because that one uses a SimpleSpanProcessor which does not impose a batching delay + if (spanExporter instanceof LoggingSpanExporter) { + inMemorySpanExporter.reset(); + return SpanExporter.composite(inMemorySpanExporter, spanExporter); + } + return spanExporter; + }); + } + + @Override + public int order() { + // There might be other autoconfigurations wrapping SpanExporters, + // which can result in us failing to detect it + // We avoid this by ensuring that we run first + return Integer.MIN_VALUE; + } +} diff --git a/messaging-wrappers/mns/build.gradle.kts b/messaging-wrappers/mns/build.gradle.kts index 6fb17688c..9b5748311 100644 --- a/messaging-wrappers/mns/build.gradle.kts +++ b/messaging-wrappers/mns/build.gradle.kts @@ -9,9 +9,6 @@ otelJava.moduleName.set("io.opentelemetry.contrib.messaging.wrappers.mns") repositories { // FIXME: publish mns-client to maven central repository - maven { - url = uri("https://mvnrepo.alibaba-inc.com/mvn/repository") - } } dependencies { diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java index 7a3d5b61f..09d634c83 100644 --- a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java +++ b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java @@ -4,6 +4,7 @@ import io.opentelemetry.contrib.messaging.wrappers.mns.MNSHelper; import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; +import javax.annotation.Nullable; import java.util.Collections; import java.util.List; @@ -11,13 +12,14 @@ public class MNSProcessRequest implements MessagingProcessRequest { private final Message message; + @Nullable private final String destination; public static MNSProcessRequest of(Message message) { return of(message, null); } - public static MNSProcessRequest of(Message message, String destination) { + public static MNSProcessRequest of(Message message, @Nullable String destination) { return new MNSProcessRequest(message, destination); } @@ -26,11 +28,13 @@ public String getSystem() { return "mns"; } + @Nullable @Override public String getDestination() { return this.destination; } + @Nullable @Override public String getDestinationTemplate() { return null; @@ -46,37 +50,32 @@ public boolean isAnonymousDestination() { return false; } + @Nullable @Override public String getConversationId() { return null; } + @Nullable @Override public Long getMessageBodySize() { - if (message == null) { - return null; - } return (long) message.getMessageBodyAsBytes().length; } + @Nullable @Override public Long getMessageEnvelopeSize() { return null; } + @Nullable @Override public String getMessageId() { - if (message == null) { - return null; - } return message.getMessageId(); } @Override public List getMessageHeader(String name) { - if (message == null) { - return Collections.emptyList(); - } String header = MNSHelper.getMessageHeader(message, name); if (header == null) { return Collections.emptyList(); @@ -88,7 +87,7 @@ public Message getMessage() { return message; } - private MNSProcessRequest(Message message, String destination) { + private MNSProcessRequest(Message message, @Nullable String destination) { this.message = message; this.destination = destination; } From b6ed6c692ea3f8f98c2d5ab03882606fb4da5a42 Mon Sep 17 00:00:00 2001 From: cirilla-zmh Date: Wed, 2 Apr 2025 14:55:37 +0800 Subject: [PATCH 04/17] add init params by jvm args --- .../kafka-clients/build.gradle.kts | 10 +++++++++ .../wrappers/kafka/KafkaClientTest.java | 21 ++++++++----------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/messaging-wrappers/kafka-clients/build.gradle.kts b/messaging-wrappers/kafka-clients/build.gradle.kts index 8828cb2e3..ef1142274 100644 --- a/messaging-wrappers/kafka-clients/build.gradle.kts +++ b/messaging-wrappers/kafka-clients/build.gradle.kts @@ -35,3 +35,13 @@ dependencies { testImplementation("io.opentelemetry:opentelemetry-exporter-logging") testImplementation("io.opentelemetry.javaagent:opentelemetry-testing-common") } + +tasks { + withType().configureEach { + jvmArgs("-Dotel.java.global-autoconfigure.enabled=true") + jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=true") + jvmArgs("-Dotel.traces.exporter=logging") + jvmArgs("-Dotel.metrics.exporter=logging") + jvmArgs("-Dotel.logs.exporter=logging") + } +} \ No newline at end of file diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java index c359be4dc..f757d5e28 100644 --- a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java +++ b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java @@ -51,22 +51,19 @@ public Map consumerProps() { @Test void testInterceptors() throws InterruptedException { - try (AutoConfigTestProperties props = new AutoConfigTestProperties() - .put("otel.instrumentation.messaging.experimental.receive-telemetry.enabled", "true")) { - OpenTelemetry otel = GlobalOpenTelemetry.get(); - Tracer tracer = otel.getTracer("test"); - MessagingProcessWrapper wrapper = KafkaHelper.processWrapperBuilder() - .openTelemetry(otel) - .build(); + OpenTelemetry otel = GlobalOpenTelemetry.get(); + Tracer tracer = otel.getTracer("test"); + MessagingProcessWrapper wrapper = KafkaHelper.processWrapperBuilder() + .openTelemetry(otel) + .build(); - sendWithParent(tracer); + sendWithParent(tracer); - awaitUntilConsumerIsReady(); + awaitUntilConsumerIsReady(); - consumeWithChild(tracer, wrapper); + consumeWithChild(tracer, wrapper); - assertTraces(); - } + assertTraces(); } @SuppressWarnings("FutureReturnValueIgnored") From 77e30018f686f1a139cb4e875c1941ba4ed707b3 Mon Sep 17 00:00:00 2001 From: cirilla-zmh Date: Wed, 2 Apr 2025 20:28:17 +0800 Subject: [PATCH 05/17] improve the integration test of kafka-clients messaging wrapper --- messaging-wrappers/README.md | 96 ++++++++- ...DefaultMessagingProcessWrapperBuilder.java | 7 + .../wrappers/MessagingProcessWrapper.java | 6 +- .../kafka-clients/build.gradle.kts | 5 +- .../KafkaConsumerAttributesExtractor.java | 12 +- .../wrappers/kafka/KafkaClientTest.java | 200 +++++++++++------- .../internal/AutoConfigTestProperties.java | 42 ---- 7 files changed, 234 insertions(+), 134 deletions(-) delete mode 100644 messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfigTestProperties.java diff --git a/messaging-wrappers/README.md b/messaging-wrappers/README.md index ba9b7042d..2b3411a4b 100644 --- a/messaging-wrappers/README.md +++ b/messaging-wrappers/README.md @@ -15,9 +15,99 @@ this tool aims to streamline the tracing and monitoring process. ## Predefined Implementations -| Messaging system | Version | Wrapper type | -|-------------------|----------------|--------------| -| Aliyun mns-client | 1.3.0-SNAPSHOT | process | +| Messaging system | Version Scope | Wrapper type | +|-------------------|---------------------|--------------| +| kafka-clients | `[0.11.0.0,)` | process | +| aliyun mns-client | `[1.3.0-SNAPSHOT,)` | process | + +## Quickstart + +### Step 1 Add dependencies + +To use OpenTelemetry in your project, you need to add the necessary dependencies. Below are the configurations for both +Gradle and Maven. + +#### Gradle + +```kotlin +dependencies { + implementation("io.opentelemetry.contrib:opentelemetry-messaging-wrappers-api") +} +``` + +#### Maven + +```xml + + io.opentelemetry.contrib + opentelemetry-messaging-wrappers-api + +``` + +### Step 2 Initializing MessagingWrappers + +Below is an example of how to initialize a messaging wrapper. + +```java +public class Demo { + + public static MessagingProcessWrapper createWrapper( + OpenTelemetry openTelemetry, + MyTextMapGetter textMapGetter, + List> additionalExtractor) { + + return MessagingProcessWrapper.defaultBuilder() + .openTelemetry(openTelemetry) + .textMapGetter(textMapGetter) + .addAttributesExtractors(additionalExtractor) + .build(); + } +} + +public class MyMessagingProcessRequest implements MessagingProcessRequest { + // your implementation here +} + +public class MyTextMapGetter implements TextMapGetter { + // your implementation here +} +``` + +For arbitrary messaging systems, you need to manually define `MessagingProcessRequest` and the corresponding `TextMapGetter`. +You can also customize your messaging spans by adding an AttributesExtractor. + +For popular messaging systems, we provide pre-implemented wrappers that allow for out-of-the-box integration. We provide +an implementation based on the OpenTelemetry semantic convention by default. + +```java +public class KafkaDemo { + + public static MessagingProcessWrapper createWrapper() { + return KafkaHelper.processWrapperBuilder().build(); + } +} +``` + +### Step 3 Wrapping your process + +Once the MessagingWrapper are initialized, you can wrap your message processing logic to ensure that tracing spans are +properly created and propagated. + +**P.S.** Some instrumentations may also [generate process spans](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/supported-libraries.md). +If both are enabled, it might result in duplicate nested process spans. It is recommended to disable one of them. + +```java +public class Demo { + + private static final MessagingProcessWrapper WRAPPER = createWrapper(); + + public String consume(Message message) { + WRAPPER.doProcess(new MyMessagingProcessRequest(message), () -> { + // your processing logic + }); + } +} +``` ## Component owners diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java index 3c8b7e99d..c9d414cfd 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java @@ -56,6 +56,13 @@ public DefaultMessagingProcessWrapperBuilder addAttributesExtractor( return this; } + @CanIgnoreReturnValue + public DefaultMessagingProcessWrapperBuilder addAttributesExtractors( + Collection> attributesExtractor) { + this.attributesExtractors.addAll(attributesExtractor); + return this; + } + public MessagingProcessWrapper build() { return new MessagingProcessWrapper<>( this.openTelemetry == null ? GlobalOpenTelemetry.get() : this.openTelemetry, diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java index 807a2717e..a60d9d391 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java @@ -16,6 +16,8 @@ import javax.annotation.Nullable; import java.util.List; +import static io.opentelemetry.api.trace.SpanKind.CONSUMER; + public class MessagingProcessWrapper { private static final String INSTRUMENTATION_SCOPE = "messaging-process-wrapper"; @@ -68,7 +70,7 @@ public R doProcess(REQUEST request, ThrowingSupplier extractor : this.attributesExtractors) { @@ -96,7 +98,7 @@ protected MessagingProcessWrapper(OpenTelemetry openTelemetry, TextMapGetter textMapGetter, List> attributesExtractors) { this.textMapPropagator = openTelemetry.getPropagators().getTextMapPropagator(); - this.tracer = openTelemetry.getTracer(INSTRUMENTATION_SCOPE + "-" + INSTRUMENTATION_VERSION); + this.tracer = openTelemetry.getTracer(INSTRUMENTATION_SCOPE, INSTRUMENTATION_VERSION); this.textMapGetter = textMapGetter; this.attributesExtractors = attributesExtractors; } diff --git a/messaging-wrappers/kafka-clients/build.gradle.kts b/messaging-wrappers/kafka-clients/build.gradle.kts index ef1142274..6afc1ed7f 100644 --- a/messaging-wrappers/kafka-clients/build.gradle.kts +++ b/messaging-wrappers/kafka-clients/build.gradle.kts @@ -39,7 +39,10 @@ dependencies { tasks { withType().configureEach { jvmArgs("-Dotel.java.global-autoconfigure.enabled=true") - jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=true") + // TODO: According to https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/#message-creation-context-as-parent-of-process-span, + // process span should be the child of receive span. However, we couldn't access the trace context with receive span + // in wrappers, unless we add a generic accessor for that. + jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=false") jvmArgs("-Dotel.traces.exporter=logging") jvmArgs("-Dotel.metrics.exporter=logging") jvmArgs("-Dotel.logs.exporter=logging") diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesExtractor.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesExtractor.java index 2950e7f7f..c4eec9261 100644 --- a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesExtractor.java +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesExtractor.java @@ -18,12 +18,12 @@ public final class KafkaConsumerAttributesExtractor MESSAGING_DESTINATION_PARTITION_ID = AttributeKey.stringKey("messaging.destination.partition.id"); - private static final AttributeKey MESSAGING_KAFKA_CONSUMER_GROUP = - AttributeKey.stringKey("messaging.kafka.consumer.group"); + private static final AttributeKey MESSAGING_CONSUMER_GROUP_NAME = + AttributeKey.stringKey("messaging.consumer.group.name"); + private static final AttributeKey MESSAGING_KAFKA_OFFSET = + AttributeKey.longKey("messaging.kafka.offset"); private static final AttributeKey MESSAGING_KAFKA_MESSAGE_KEY = AttributeKey.stringKey("messaging.kafka.message.key"); - private static final AttributeKey MESSAGING_KAFKA_MESSAGE_OFFSET = - AttributeKey.longKey("messaging.kafka.message.offset"); private static final AttributeKey MESSAGING_KAFKA_MESSAGE_TOMBSTONE = AttributeKey.booleanKey("messaging.kafka.message.tombstone"); @@ -38,7 +38,7 @@ public void onStart( ConsumerRecord record = request.getRecord(); attributes.put(MESSAGING_DESTINATION_PARTITION_ID, String.valueOf(record.partition())); - attributes.put(MESSAGING_KAFKA_MESSAGE_OFFSET, record.offset()); + attributes.put(MESSAGING_KAFKA_OFFSET, record.offset()); Object key = record.key(); if (key != null && canSerialize(key.getClass())) { @@ -50,7 +50,7 @@ public void onStart( String consumerGroup = request.getConsumerGroup(); if (consumerGroup != null) { - attributes.put(MESSAGING_KAFKA_CONSUMER_GROUP, consumerGroup); + attributes.put(MESSAGING_CONSUMER_GROUP_NAME, consumerGroup); } } diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java index f757d5e28..2bba4d63d 100644 --- a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java +++ b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java @@ -3,24 +3,52 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Scope; import io.opentelemetry.contrib.messaging.wrappers.MessagingProcessWrapper; -import io.opentelemetry.contrib.messaging.wrappers.kafka.internal.AutoConfigTestProperties; import io.opentelemetry.contrib.messaging.wrappers.kafka.internal.AutoConfiguredDataCapture; +import io.opentelemetry.contrib.messaging.wrappers.kafka.semconv.KafkaConsumerAttributesExtractor; import io.opentelemetry.contrib.messaging.wrappers.kafka.semconv.KafkaProcessRequest; import io.opentelemetry.instrumentation.kafkaclients.v2_6.TracingConsumerInterceptor; import io.opentelemetry.instrumentation.kafkaclients.v2_6.TracingProducerInterceptor; +import io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil; +import io.opentelemetry.sdk.testing.assertj.TraceAssert; +import io.opentelemetry.sdk.testing.assertj.TracesAssert; +import io.opentelemetry.sdk.trace.data.SpanData; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerRecord; +import org.assertj.core.api.AbstractAssert; +import org.awaitility.core.ConditionTimeoutException; import org.junit.jupiter.api.Test; + +import javax.annotation.Nullable; +import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import java.util.Map; +import java.util.Arrays; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; +import java.util.function.Supplier; +import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanName; +import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.waitForTraces; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_CONSUMER_GROUP_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_PARTITION_ID; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_OFFSET; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; import static org.awaitility.Awaitility.await; public class KafkaClientTest extends KafkaClientBaseTest { @@ -52,9 +80,10 @@ public Map consumerProps() { @Test void testInterceptors() throws InterruptedException { OpenTelemetry otel = GlobalOpenTelemetry.get(); - Tracer tracer = otel.getTracer("test"); + Tracer tracer = otel.getTracer("test-tracer", "1.0.0"); MessagingProcessWrapper wrapper = KafkaHelper.processWrapperBuilder() .openTelemetry(otel) + .addAttributesExtractor(KafkaConsumerAttributesExtractor.create()) .build(); sendWithParent(tracer); @@ -79,6 +108,7 @@ public void sendWithParent(Tracer tracer) { } }); } + parent.end(); } public void consumeWithChild(Tracer tracer, MessagingProcessWrapper wrapper) { @@ -94,84 +124,94 @@ public void consumeWithChild(Tracer tracer, MessagingProcessWrappertesting-common. + * */ + @SuppressWarnings("deprecation") // using deprecated semconv public void assertTraces() { - await() - .untilAsserted( - () -> { - assertThat(AutoConfiguredDataCapture.getSpans()) - .hasSize(5); - } - ); -// testing.waitAndAssertSortedTraces( -// orderByRootSpanName("parent", SHARED_TOPIC + " receive", "producer callback"), -// trace -> { -// trace.hasSpansSatisfyingExactly( -// span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), -// span -> -// span.hasName(SHARED_TOPIC + " publish") -// .hasKind(SpanKind.PRODUCER) -// .hasParent(trace.getSpan(0)) -// .hasAttributesSatisfyingExactly( -// equalTo(MESSAGING_SYSTEM, "kafka"), -// equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), -// equalTo(MESSAGING_OPERATION, "publish"), -// satisfies( -// MESSAGING_CLIENT_ID, -// stringAssert -> stringAssert.startsWith("producer")))); -// SpanContext spanContext = trace.getSpan(1).getSpanContext(); -// producerSpanContext.set( -// SpanContext.createFromRemoteParent( -// spanContext.getTraceId(), -// spanContext.getSpanId(), -// spanContext.getTraceFlags(), -// spanContext.getTraceState())); -// }, -// trace -> -// trace.hasSpansSatisfyingExactly( -// span -> -// span.hasName(SHARED_TOPIC + " receive") -// .hasKind(SpanKind.CONSUMER) -// .hasNoParent() -// .hasLinksSatisfying(links -> assertThat(links).isEmpty()) -// .hasAttributesSatisfyingExactly( -// equalTo(MESSAGING_SYSTEM, "kafka"), -// equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), -// equalTo(MESSAGING_OPERATION, "receive"), -// equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test"), -// satisfies( -// MESSAGING_CLIENT_ID, -// stringAssert -> stringAssert.startsWith("consumer")), -// equalTo(MESSAGING_BATCH_MESSAGE_COUNT, 1)), -// span -> -// span.hasName(SHARED_TOPIC + " process") -// .hasKind(SpanKind.CONSUMER) -// .hasParent(trace.getSpan(0)) -// .hasLinks(LinkData.create(producerSpanContext.get())) -// .hasAttributesSatisfyingExactly( -// equalTo(MESSAGING_SYSTEM, "kafka"), -// equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), -// equalTo(MESSAGING_OPERATION, "process"), -// equalTo( -// MESSAGING_MESSAGE_BODY_SIZE, -// greeting.getBytes(StandardCharsets.UTF_8).length), -// satisfies( -// MESSAGING_DESTINATION_PARTITION_ID, -// AbstractStringAssert::isNotEmpty), -// satisfies( -// MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative), -// equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test"), -// satisfies( -// MESSAGING_CLIENT_ID, -// stringAssert -> stringAssert.startsWith("consumer"))), -// span -> -// span.hasName("process child") -// .hasKind(SpanKind.INTERNAL) -// .hasParent(trace.getSpan(1))), -// // ideally we'd want producer callback to be part of the main trace, we just aren't able to -// // instrument that -// trace -> -// trace.hasSpansSatisfyingExactly( -// span -> -// span.hasName("producer callback").hasKind(SpanKind.INTERNAL).hasNoParent())); + waitAndAssertTraces( + orderByRootSpanName("parent", "producer callback"), + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + // No need to verify the attribute here because it is generated by instrumentation library. + span.hasName(SHARED_TOPIC + " publish") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)), + span -> + span.hasName("process " + SHARED_TOPIC) + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_SYSTEM, "kafka"), + equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), + equalTo( + MESSAGING_MESSAGE_BODY_SIZE, + greeting.getBytes(StandardCharsets.UTF_8).length), + satisfies( + MESSAGING_DESTINATION_PARTITION_ID, + org.assertj.core.api.AbstractStringAssert::isNotEmpty), + satisfies( + MESSAGING_CLIENT_ID, + stringAssert -> stringAssert.isEqualTo("test-consumer-1")), + satisfies( + MESSAGING_KAFKA_OFFSET, + AbstractAssert::isNotNull), + equalTo(MESSAGING_CONSUMER_GROUP_NAME, "test"), + equalTo(MESSAGING_OPERATION, "process")), + span -> + span.hasName("process child") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(2))), + // ideally we'd want producer callback to be part of the main trace, we just aren't able to + // instrument that + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("producer callback").hasKind(SpanKind.INTERNAL).hasNoParent())); + } + + @SafeVarargs + @SuppressWarnings("varargs") + private static void waitAndAssertTraces( + @Nullable Comparator> traceComparator, + Consumer... assertions) { + List> assertionsList = new ArrayList<>(Arrays.asList(assertions)); + try { + await() + .untilAsserted(() -> doAssertTraces(traceComparator, AutoConfiguredDataCapture::getSpans, assertionsList)); + } catch (Throwable t) { + // awaitility is doing a jmx call that is not implemented in GraalVM: + // call: + // https://github.com/awaitility/awaitility/blob/fbe16add874b4260dd240108304d5c0be84eabc8/awaitility/src/main/java/org/awaitility/core/ConditionAwaiter.java#L157 + // see https://github.com/oracle/graal/issues/6101 (spring boot graal native image) + if (t.getClass().getName().equals("com.oracle.svm.core.jdk.UnsupportedFeatureError") + || t instanceof ConditionTimeoutException) { + // Don't throw this failure since the stack is the awaitility thread, causing confusion. + // Instead, just assert one more time on the test thread, which will fail with a better + // stack trace. + // TODO(anuraaga): There is probably a better way to do this. + doAssertTraces(traceComparator, AutoConfiguredDataCapture::getSpans, assertionsList); + } else { + throw t; + } + } + } + + private static void doAssertTraces( + @Nullable Comparator> traceComparator, + Supplier> supplier, + List> assertionsList) { + try { + List> traces = waitForTraces(supplier, assertionsList.size()); + TelemetryDataUtil.assertScopeVersion(traces); + if (traceComparator != null) { + traces.sort(traceComparator); + } + TracesAssert.assertThat(traces).hasTracesSatisfyingExactly(assertionsList); + } catch (InterruptedException | TimeoutException e) { + throw new AssertionError("Error waiting for " + assertionsList.size() + " traces", e); + } } } diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfigTestProperties.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfigTestProperties.java deleted file mode 100644 index 60bebaae8..000000000 --- a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfigTestProperties.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.opentelemetry.contrib.messaging.wrappers.kafka.internal; - -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import java.util.HashMap; -import java.util.Map; - -public class AutoConfigTestProperties implements AutoCloseable { - - public AutoConfigTestProperties() { - put("otel.java.global-autoconfigure.enabled", "true"); - put("otel.traces.exporter", "logging"); - put("otel.metrics.exporter", "logging"); - put("otel.logs.exporter", "logging"); - } - - private final Map originalValues = new HashMap<>(); - - @CanIgnoreReturnValue - public AutoConfigTestProperties put(String key, String value) { - if (!originalValues.containsKey(key)) { - originalValues.put(key, System.getProperty(key)); - } - if (value == null) { - System.clearProperty(key); - } else { - System.setProperty(key, value); - } - return this; - } - - @Override - public void close() { - for (String key : originalValues.keySet()) { - String value = originalValues.get(key); - if (value == null) { - System.clearProperty(key); - } else { - System.setProperty(key, value); - } - } - } -} From 2180b1a15b698dc223f353166a1c2871309e1eeb Mon Sep 17 00:00:00 2001 From: cirilla-zmh Date: Wed, 16 Apr 2025 18:15:08 +0800 Subject: [PATCH 06/17] change company names of code owners --- messaging-wrappers/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messaging-wrappers/README.md b/messaging-wrappers/README.md index 2b3411a4b..4e26e2496 100644 --- a/messaging-wrappers/README.md +++ b/messaging-wrappers/README.md @@ -111,7 +111,7 @@ public class Demo { ## Component owners -- [Minghui Zhang](https://github.com/Cirilla-zmh), Alibaba Cloud -- [Steve Rao](https://github.com/steverao), Alibaba Cloud +- [Minghui Zhang](https://github.com/Cirilla-zmh), Alibaba +- [Steve Rao](https://github.com/steverao), Alibaba Learn more about component owners in [component_owners.yml](../.github/component_owners.yml). From 568619906fbc559dc1ad2c395705052ebf8a6148 Mon Sep 17 00:00:00 2001 From: cirilla-zmh Date: Wed, 16 Apr 2025 21:55:10 +0800 Subject: [PATCH 07/17] add test for api module --- messaging-wrappers/README.md | 8 +- .../{mns => aliyun-mns-sdk}/build.gradle.kts | 10 +- .../{mns => aliyun-mns-sdk}/gradle.properties | 0 .../messaging/wrappers/mns/MNSHelper.java | 0 .../mns/MNSProcessWrapperBuilder.java | 0 .../wrappers/mns/MNSTextMapGetter.java | 0 .../wrappers/mns/example/MNSConsumer.java | 0 .../messaging/wrappers/mns/package-info.java | 2 + .../mns/semconv/MNSProcessRequest.java | 2 +- messaging-wrappers/api/build.gradle.kts | 17 ++- .../messaging/wrappers/TestConstants.java | 14 +++ .../UserDefinedMessageSystemTest.java | 113 ++++++++++++++++++ .../wrappers/impl/MessageRequest.java | 101 ++++++++++++++++ .../wrappers/impl/MessageTextMapGetter.java | 30 +++++ .../messaging/wrappers/model/Message.java | 46 +++++++ .../wrappers/model/MessageListener.java | 39 ++++++ .../wrappers/model/MessageTextMapSetter.java | 20 ++++ .../messaging/wrappers/package-info.java | 2 + .../wrappers/kafka/package-info.java | 2 +- .../wrappers/kafka/KafkaClientBaseTest.java | 108 ----------------- .../messaging/wrappers/mns/package-info.java | 2 - messaging-wrappers/testing/build.gradle.kts | 20 ++++ .../wrappers/testing/AbstractBaseTest.java | 71 +++++++++++ .../internal/AutoConfiguredDataCapture.java | 46 +++++++ settings.gradle.kts | 3 +- 25 files changed, 526 insertions(+), 130 deletions(-) rename messaging-wrappers/{mns => aliyun-mns-sdk}/build.gradle.kts (71%) rename messaging-wrappers/{mns => aliyun-mns-sdk}/gradle.properties (100%) rename messaging-wrappers/{mns => aliyun-mns-sdk}/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSHelper.java (100%) rename messaging-wrappers/{mns => aliyun-mns-sdk}/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapperBuilder.java (100%) rename messaging-wrappers/{mns => aliyun-mns-sdk}/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSTextMapGetter.java (100%) rename messaging-wrappers/{mns => aliyun-mns-sdk}/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/example/MNSConsumer.java (100%) create mode 100644 messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/package-info.java rename messaging-wrappers/{mns => aliyun-mns-sdk}/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java (99%) create mode 100644 messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/TestConstants.java create mode 100644 messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java create mode 100644 messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageRequest.java create mode 100644 messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageTextMapGetter.java create mode 100644 messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/Message.java create mode 100644 messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageListener.java create mode 100644 messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageTextMapSetter.java create mode 100644 messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/package-info.java delete mode 100644 messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/package-info.java create mode 100644 messaging-wrappers/testing/build.gradle.kts create mode 100644 messaging-wrappers/testing/src/main/java/io/opentelemetry/contrib/messaging/wrappers/testing/AbstractBaseTest.java create mode 100644 messaging-wrappers/testing/src/main/java/io/opentelemetry/contrib/messaging/wrappers/testing/internal/AutoConfiguredDataCapture.java diff --git a/messaging-wrappers/README.md b/messaging-wrappers/README.md index 4e26e2496..49067df29 100644 --- a/messaging-wrappers/README.md +++ b/messaging-wrappers/README.md @@ -15,10 +15,10 @@ this tool aims to streamline the tracing and monitoring process. ## Predefined Implementations -| Messaging system | Version Scope | Wrapper type | -|-------------------|---------------------|--------------| -| kafka-clients | `[0.11.0.0,)` | process | -| aliyun mns-client | `[1.3.0-SNAPSHOT,)` | process | +| Messaging system | Version Scope | Wrapper type | +|-------------------|---------------|--------------| +| kafka-clients | `[0.11.0.0,)` | process | +| aliyun mns-client | `[1.3.0,)` | process | ## Quickstart diff --git a/messaging-wrappers/mns/build.gradle.kts b/messaging-wrappers/aliyun-mns-sdk/build.gradle.kts similarity index 71% rename from messaging-wrappers/mns/build.gradle.kts rename to messaging-wrappers/aliyun-mns-sdk/build.gradle.kts index 9b5748311..19a728420 100644 --- a/messaging-wrappers/mns/build.gradle.kts +++ b/messaging-wrappers/aliyun-mns-sdk/build.gradle.kts @@ -4,17 +4,13 @@ plugins { id("otel.publish-conventions") } -description = "OpenTelemetry Messaging Wrappers - mns-clients implementation" -otelJava.moduleName.set("io.opentelemetry.contrib.messaging.wrappers.mns") - -repositories { - // FIXME: publish mns-client to maven central repository -} +description = "OpenTelemetry Messaging Wrappers - aliyun-mns-sdk implementation" +otelJava.moduleName.set("io.opentelemetry.contrib.messaging.wrappers.aliyun-mns-sdk") dependencies { api(project(":messaging-wrappers:api")) - compileOnly("com.aliyun.mns:aliyun-sdk-mns:1.3.0-SNAPSHOT") + compileOnly("com.aliyun.mns:aliyun-sdk-mns:1.3.0") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") testImplementation("io.opentelemetry:opentelemetry-sdk-trace") diff --git a/messaging-wrappers/mns/gradle.properties b/messaging-wrappers/aliyun-mns-sdk/gradle.properties similarity index 100% rename from messaging-wrappers/mns/gradle.properties rename to messaging-wrappers/aliyun-mns-sdk/gradle.properties diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSHelper.java b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSHelper.java similarity index 100% rename from messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSHelper.java rename to messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSHelper.java diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapperBuilder.java b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapperBuilder.java similarity index 100% rename from messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapperBuilder.java rename to messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapperBuilder.java diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSTextMapGetter.java b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSTextMapGetter.java similarity index 100% rename from messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSTextMapGetter.java rename to messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSTextMapGetter.java diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/example/MNSConsumer.java b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/example/MNSConsumer.java similarity index 100% rename from messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/example/MNSConsumer.java rename to messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/example/MNSConsumer.java diff --git a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/package-info.java b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/package-info.java new file mode 100644 index 000000000..1530bdbd7 --- /dev/null +++ b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/package-info.java @@ -0,0 +1,2 @@ +/** OpenTelemetry messaging wrappers extension - mns implementation. */ +package io.opentelemetry.contrib.messaging.wrappers.mns; diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java similarity index 99% rename from messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java rename to messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java index 09d634c83..ea675da38 100644 --- a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java +++ b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java @@ -25,7 +25,7 @@ public static MNSProcessRequest of(Message message, @Nullable String destination @Override public String getSystem() { - return "mns"; + return "smq"; } @Nullable diff --git a/messaging-wrappers/api/build.gradle.kts b/messaging-wrappers/api/build.gradle.kts index dcf2a00b0..14e930cfc 100644 --- a/messaging-wrappers/api/build.gradle.kts +++ b/messaging-wrappers/api/build.gradle.kts @@ -16,10 +16,15 @@ dependencies { implementation("io.opentelemetry.semconv:opentelemetry-semconv") implementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating") - testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") - testImplementation("io.opentelemetry:opentelemetry-sdk-trace") - testImplementation("io.opentelemetry:opentelemetry-sdk-testing") - - testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") - testImplementation("uk.org.webcompere:system-stubs-jupiter:2.0.3") + testImplementation("com.google.guava:guava:33.4.8-jre") + testImplementation(project(":messaging-wrappers:testing")) } + +tasks { + withType().configureEach { + jvmArgs("-Dotel.java.global-autoconfigure.enabled=true") + jvmArgs("-Dotel.traces.exporter=logging") + jvmArgs("-Dotel.metrics.exporter=logging") + jvmArgs("-Dotel.logs.exporter=logging") + } +} \ No newline at end of file diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/TestConstants.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/TestConstants.java new file mode 100644 index 000000000..49d07ab8f --- /dev/null +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/TestConstants.java @@ -0,0 +1,14 @@ +package io.opentelemetry.contrib.messaging.wrappers; + +public final class TestConstants { + + public static final String MESSAGE_ID = "42"; + + public static final String MESSAGE_BODY = "Hello messaging wrapper!"; + + public static final String EVENTBUS_NAME = "test-eb"; + + public static final String CLIENT_ID = "eventbus-client-0"; + + private TestConstants() {} +} diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java new file mode 100644 index 000000000..5d8360213 --- /dev/null +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java @@ -0,0 +1,113 @@ +package io.opentelemetry.contrib.messaging.wrappers; + +import com.google.common.eventbus.EventBus; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.contrib.messaging.wrappers.impl.MessageRequest; +import io.opentelemetry.contrib.messaging.wrappers.impl.MessageTextMapGetter; +import io.opentelemetry.contrib.messaging.wrappers.model.Message; +import io.opentelemetry.contrib.messaging.wrappers.model.MessageListener; +import io.opentelemetry.contrib.messaging.wrappers.model.MessageTextMapSetter; +import io.opentelemetry.contrib.messaging.wrappers.testing.AbstractBaseTest; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; + +import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.CLIENT_ID; +import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.EVENTBUS_NAME; +import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.MESSAGE_BODY; +import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.MESSAGE_ID; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_CLIENT_ID; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; + +@SuppressWarnings("OtelInternalJavadoc") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class UserDefinedMessageSystemTest extends AbstractBaseTest { + + private OpenTelemetry otel; + + private Tracer tracer; + + private EventBus eventBus; + + @BeforeAll + void setupClass() { + otel = GlobalOpenTelemetry.get(); + tracer = otel.getTracer("test-tracer", "1.0.0"); + MessagingProcessWrapper wrapper = MessagingProcessWrapper.defaultBuilder() + .openTelemetry(otel) + .textMapGetter(MessageTextMapGetter.create()) + .build(); + + eventBus = new EventBus(); + + eventBus.register(MessageListener.create(tracer, wrapper)); + } + + @Test + void testSendAndConsume() { + sendWithParent(tracer); + + assertTraces(); + } + + public void sendWithParent(Tracer tracer) { + // mock a send span + Span parent = tracer.spanBuilder("publish " + EVENTBUS_NAME) + .setSpanKind(SpanKind.PRODUCER) + .startSpan(); + + try (Scope scope = parent.makeCurrent()) { + Message message = Message.create(new HashMap<>(), MESSAGE_ID, MESSAGE_BODY); + otel.getPropagators().getTextMapPropagator().inject(Context.current(), message, MessageTextMapSetter.create()); + eventBus.post(message); + } + + parent.end(); + } + + /** + * Copied from testing-common. + * */ + @SuppressWarnings("deprecation") // using deprecated semconv + public void assertTraces() { + waitAndAssertTraces( + sortByRootSpanName("parent", "producer callback"), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + // No need to verify the attribute here because it is generated by instrumentation library. + span.hasName("publish " + EVENTBUS_NAME) + .hasKind(SpanKind.PRODUCER) + .hasNoParent(), + span -> + span.hasName("process " + EVENTBUS_NAME) + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_SYSTEM, "guava-eventbus"), + equalTo(MESSAGING_DESTINATION_NAME, EVENTBUS_NAME), + equalTo( + MESSAGING_MESSAGE_BODY_SIZE, + MESSAGE_BODY.getBytes(StandardCharsets.UTF_8).length), + equalTo(MESSAGING_CLIENT_ID, CLIENT_ID), + equalTo(MESSAGING_OPERATION, "process")), + span -> + span.hasName("process child") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(1)))); + } + +} diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageRequest.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageRequest.java new file mode 100644 index 000000000..152cec839 --- /dev/null +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageRequest.java @@ -0,0 +1,101 @@ +package io.opentelemetry.contrib.messaging.wrappers.impl; + +import io.opentelemetry.contrib.messaging.wrappers.model.Message; +import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; + +import javax.annotation.Nullable; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; + +public class MessageRequest implements MessagingProcessRequest { + + private final Message message; + + @Nullable + private final String clientId; + + @Nullable + private final String eventBusName; + + public static MessageRequest of(Message message) { + return of(message, null, null); + } + + public static MessageRequest of(Message message, + @Nullable String clientId, @Nullable String eventBusName) { + return new MessageRequest(message, clientId, eventBusName); + } + + @Override + public String getSystem() { + return "guava-eventbus"; + } + + @Nullable + @Override + public String getDestination() { + return eventBusName; + } + + @Nullable + @Override + public String getDestinationTemplate() { + return null; + } + + @Override + public boolean isTemporaryDestination() { + return false; + } + + @Override + public boolean isAnonymousDestination() { + return false; + } + + @Nullable + @Override + public String getConversationId() { + return null; + } + + @Nullable + @Override + public Long getMessageBodySize() { + return (long) message.getBody().getBytes(StandardCharsets.UTF_8).length; + } + + @Nullable + @Override + public Long getMessageEnvelopeSize() { + return null; + } + + @Nullable + @Override + public String getMessageId() { + return message.getId(); + } + + @Nullable + @Override + public String getClientId() { + return clientId; + } + + @Override + public List getMessageHeader(String name) { + return Collections.singletonList(message.getHeaders().get(name)); + } + + public Message getMessage() { + return message; + } + + private MessageRequest(Message message, @Nullable String clientId, @Nullable String eventBusName) { + this.message = message; + this.clientId = clientId; + this.eventBusName = eventBusName; + } +} diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageTextMapGetter.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageTextMapGetter.java new file mode 100644 index 000000000..501e14bd4 --- /dev/null +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageTextMapGetter.java @@ -0,0 +1,30 @@ +package io.opentelemetry.contrib.messaging.wrappers.impl; + +import io.opentelemetry.context.propagation.TextMapGetter; + +import javax.annotation.Nullable; +import java.util.Collections; + +public class MessageTextMapGetter implements TextMapGetter { + + public static TextMapGetter create() { + return new MessageTextMapGetter(); + } + + @Override + public Iterable keys(MessageRequest carrier) { + if (carrier == null || carrier.getMessage() == null) { + return Collections.emptyList(); + } + return carrier.getMessage().getHeaders().keySet(); + } + + @Nullable + @Override + public String get(@Nullable MessageRequest carrier, String key) { + if (carrier == null || carrier.getMessage() == null) { + return null; + } + return carrier.getMessage().getHeaders().get(key); + } +} diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/Message.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/Message.java new file mode 100644 index 000000000..2839094ec --- /dev/null +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/Message.java @@ -0,0 +1,46 @@ +package io.opentelemetry.contrib.messaging.wrappers.model; + +import java.util.Map; + +public class Message { + + private Map headers; + + private String id; + + private String body; + + public static Message create(Map headers, String id, String body) { + return new Message(headers, id, body); + } + + private Message(Map headers, String id, String body) { + this.headers = headers; + this.id = id; + this.body = body; + } + + public Map getHeaders() { + return headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } +} diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageListener.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageListener.java new file mode 100644 index 000000000..27e17b398 --- /dev/null +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageListener.java @@ -0,0 +1,39 @@ +package io.opentelemetry.contrib.messaging.wrappers.model; + +import com.google.common.eventbus.Subscribe; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.contrib.messaging.wrappers.impl.MessageRequest; +import io.opentelemetry.contrib.messaging.wrappers.MessagingProcessWrapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.CLIENT_ID; +import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.EVENTBUS_NAME; + +public class MessageListener { + + private static final Logger logger = LoggerFactory.getLogger(MessageListener.class); + + private final Tracer tracer; + + private final MessagingProcessWrapper wrapper; + + public static MessageListener create(Tracer tracer, MessagingProcessWrapper wrapper) { + return new MessageListener(tracer, wrapper); + } + + @Subscribe + public void handleEvent(Message event) { + wrapper.doProcess(MessageRequest.of(event, CLIENT_ID, EVENTBUS_NAME), () -> { + Span span = tracer.spanBuilder("process child").startSpan(); + logger.info("Received event from <" + EVENTBUS_NAME + ">: " + event.getId()); + span.end(); + }); + } + + private MessageListener(Tracer tracer, MessagingProcessWrapper wrapper) { + this.tracer = tracer; + this.wrapper = wrapper; + } +} diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageTextMapSetter.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageTextMapSetter.java new file mode 100644 index 000000000..83e68ac11 --- /dev/null +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageTextMapSetter.java @@ -0,0 +1,20 @@ +package io.opentelemetry.contrib.messaging.wrappers.model; + +import io.opentelemetry.context.propagation.TextMapSetter; + +import javax.annotation.Nullable; + +public class MessageTextMapSetter implements TextMapSetter { + + public static TextMapSetter create() { + return new MessageTextMapSetter(); + } + + @Override + public void set(@Nullable Message carrier, String key, String value) { + if (carrier == null) { + return; + } + carrier.getHeaders().put(key, value); + } +} diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/package-info.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/package-info.java new file mode 100644 index 000000000..502b96ca6 --- /dev/null +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/package-info.java @@ -0,0 +1,2 @@ +/** OpenTelemetry messaging wrappers extension. */ +package io.opentelemetry.contrib.messaging.wrappers; diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/package-info.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/package-info.java index 31364e41f..7e988a5b6 100644 --- a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/package-info.java +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/package-info.java @@ -1,2 +1,2 @@ -/** OpenTelemetry messaging wrappers extension. */ +/** OpenTelemetry messaging wrappers extension - kafka implementation. */ package io.opentelemetry.contrib.messaging.wrappers.kafka; diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java index ba93103b7..9c4629a71 100644 --- a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java +++ b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java @@ -1,28 +1,10 @@ package io.opentelemetry.contrib.messaging.wrappers.kafka; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_BATCH_MESSAGE_COUNT; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_PARTITION_ID; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_CONSUMER_GROUP; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_KEY; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_TOMBSTONE; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; - import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; -import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -40,8 +22,6 @@ import org.apache.kafka.common.serialization.IntegerSerializer; import org.apache.kafka.common.serialization.StringDeserializer; import org.apache.kafka.common.serialization.StringSerializer; -import org.assertj.core.api.AbstractLongAssert; -import org.assertj.core.api.AbstractStringAssert; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInstance; @@ -162,92 +142,4 @@ public void awaitUntilConsumerIsReady() throws InterruptedException { } consumer.seekToBeginning(Collections.emptyList()); } - - @SuppressWarnings("deprecation") // using deprecated semconv - protected static List sendAttributes( - String messageKey, String messageValue, boolean testHeaders) { - List assertions = - new ArrayList<>( - Arrays.asList( - equalTo(MESSAGING_SYSTEM, "kafka"), - equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(MESSAGING_OPERATION, "publish"), - satisfies(MESSAGING_CLIENT_ID, stringAssert -> stringAssert.startsWith("producer")), - satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty), - satisfies(MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative))); - if (messageKey != null) { - assertions.add(equalTo(MESSAGING_KAFKA_MESSAGE_KEY, messageKey)); - } - if (messageValue == null) { - assertions.add(equalTo(MESSAGING_KAFKA_MESSAGE_TOMBSTONE, true)); - } - if (testHeaders) { - assertions.add( - equalTo( - AttributeKey.stringArrayKey("messaging.header.test_message_header"), - Collections.singletonList("test"))); - } - return assertions; - } - - @SuppressWarnings("deprecation") // using deprecated semconv - protected static List receiveAttributes(boolean testHeaders) { - List assertions = - new ArrayList<>( - Arrays.asList( - equalTo(MESSAGING_SYSTEM, "kafka"), - equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(MESSAGING_OPERATION, "receive"), - satisfies(MESSAGING_CLIENT_ID, stringAssert -> stringAssert.startsWith("consumer")), - satisfies(MESSAGING_BATCH_MESSAGE_COUNT, AbstractLongAssert::isPositive))); - // consumer group is not available in version 0.11 - if (Boolean.getBoolean("testLatestDeps")) { - assertions.add(equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test")); - } - if (testHeaders) { - assertions.add( - equalTo( - AttributeKey.stringArrayKey("messaging.header.test_message_header"), - Collections.singletonList("test"))); - } - return assertions; - } - - @SuppressWarnings("deprecation") // using deprecated semconv - protected static List processAttributes( - String messageKey, String messageValue, boolean testHeaders) { - List assertions = - new ArrayList<>( - Arrays.asList( - equalTo(MESSAGING_SYSTEM, "kafka"), - equalTo(MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(MESSAGING_OPERATION, "process"), - satisfies(MESSAGING_CLIENT_ID, stringAssert -> stringAssert.startsWith("consumer")), - satisfies(MESSAGING_DESTINATION_PARTITION_ID, AbstractStringAssert::isNotEmpty), - satisfies(MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("kafka.record.queue_time_ms"), - AbstractLongAssert::isNotNegative))); - // consumer group is not available in version 0.11 - if (Boolean.getBoolean("testLatestDeps")) { - assertions.add(equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test")); - } - if (messageKey != null) { - assertions.add(equalTo(MESSAGING_KAFKA_MESSAGE_KEY, messageKey)); - } - if (messageValue == null) { - assertions.add(equalTo(MESSAGING_KAFKA_MESSAGE_TOMBSTONE, true)); - } else { - assertions.add( - equalTo( - MESSAGING_MESSAGE_BODY_SIZE, messageValue.getBytes(StandardCharsets.UTF_8).length)); - } - if (testHeaders) { - assertions.add( - equalTo( - AttributeKey.stringArrayKey("messaging.header.test_message_header"), - Collections.singletonList("test"))); - } - return assertions; - } } diff --git a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/package-info.java b/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/package-info.java deleted file mode 100644 index 0fb38eb18..000000000 --- a/messaging-wrappers/mns/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** OpenTelemetry messaging wrappers extension. */ -package io.opentelemetry.contrib.messaging.wrappers.mns; diff --git a/messaging-wrappers/testing/build.gradle.kts b/messaging-wrappers/testing/build.gradle.kts new file mode 100644 index 000000000..7385d24ee --- /dev/null +++ b/messaging-wrappers/testing/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + id("otel.java-conventions") +} + +description = "OpenTelemetry Messaging Wrappers testing" + +dependencies { + annotationProcessor("com.google.auto.service:auto-service") + compileOnly("com.google.auto.service:auto-service-annotations") + + api("org.junit.jupiter:junit-jupiter-api") + api("org.junit.jupiter:junit-jupiter-params") + api("io.opentelemetry:opentelemetry-sdk-testing") + + implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + implementation("io.opentelemetry:opentelemetry-sdk-trace") + implementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") + implementation("io.opentelemetry:opentelemetry-exporter-logging") + implementation("io.opentelemetry.javaagent:opentelemetry-testing-common") +} \ No newline at end of file diff --git a/messaging-wrappers/testing/src/main/java/io/opentelemetry/contrib/messaging/wrappers/testing/AbstractBaseTest.java b/messaging-wrappers/testing/src/main/java/io/opentelemetry/contrib/messaging/wrappers/testing/AbstractBaseTest.java new file mode 100644 index 000000000..3eedc9846 --- /dev/null +++ b/messaging-wrappers/testing/src/main/java/io/opentelemetry/contrib/messaging/wrappers/testing/AbstractBaseTest.java @@ -0,0 +1,71 @@ +package io.opentelemetry.contrib.messaging.wrappers.testing; + +import io.opentelemetry.contrib.messaging.wrappers.testing.internal.AutoConfiguredDataCapture; +import io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil; +import io.opentelemetry.sdk.testing.assertj.TraceAssert; +import io.opentelemetry.sdk.testing.assertj.TracesAssert; +import io.opentelemetry.sdk.trace.data.SpanData; +import org.awaitility.core.ConditionTimeoutException; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanName; +import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.waitForTraces; +import static org.awaitility.Awaitility.await; + +public abstract class AbstractBaseTest { + + public static Comparator> sortByRootSpanName(String... names) { + return orderByRootSpanName(names); + } + + @SafeVarargs + @SuppressWarnings("varargs") + public static void waitAndAssertTraces( + @Nullable Comparator> traceComparator, + Consumer... assertions) { + List> assertionsList = new ArrayList<>(Arrays.asList(assertions)); + try { + await() + .untilAsserted(() -> doAssertTraces(traceComparator, AutoConfiguredDataCapture::getSpans, assertionsList)); + } catch (Throwable t) { + // awaitility is doing a jmx call that is not implemented in GraalVM: + // call: + // https://github.com/awaitility/awaitility/blob/fbe16add874b4260dd240108304d5c0be84eabc8/awaitility/src/main/java/org/awaitility/core/ConditionAwaiter.java#L157 + // see https://github.com/oracle/graal/issues/6101 (spring boot graal native image) + if (t.getClass().getName().equals("com.oracle.svm.core.jdk.UnsupportedFeatureError") + || t instanceof ConditionTimeoutException) { + // Don't throw this failure since the stack is the awaitility thread, causing confusion. + // Instead, just assert one more time on the test thread, which will fail with a better + // stack trace. + // TODO(anuraaga): There is probably a better way to do this. + doAssertTraces(traceComparator, AutoConfiguredDataCapture::getSpans, assertionsList); + } else { + throw t; + } + } + } + + public static void doAssertTraces( + @Nullable Comparator> traceComparator, + Supplier> supplier, + List> assertionsList) { + try { + List> traces = waitForTraces(supplier, assertionsList.size()); + TelemetryDataUtil.assertScopeVersion(traces); + if (traceComparator != null) { + traces.sort(traceComparator); + } + TracesAssert.assertThat(traces).hasTracesSatisfyingExactly(assertionsList); + } catch (InterruptedException | TimeoutException e) { + throw new AssertionError("Error waiting for " + assertionsList.size() + " traces", e); + } + } +} diff --git a/messaging-wrappers/testing/src/main/java/io/opentelemetry/contrib/messaging/wrappers/testing/internal/AutoConfiguredDataCapture.java b/messaging-wrappers/testing/src/main/java/io/opentelemetry/contrib/messaging/wrappers/testing/internal/AutoConfiguredDataCapture.java new file mode 100644 index 000000000..45f96ecb3 --- /dev/null +++ b/messaging-wrappers/testing/src/main/java/io/opentelemetry/contrib/messaging/wrappers/testing/internal/AutoConfiguredDataCapture.java @@ -0,0 +1,46 @@ +package io.opentelemetry.contrib.messaging.wrappers.testing.internal; + +import com.google.auto.service.AutoService; +import io.opentelemetry.exporter.logging.LoggingSpanExporter; +import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer; +import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SpanExporter; + +import java.util.List; + +@AutoService(AutoConfigurationCustomizerProvider.class) +public class AutoConfiguredDataCapture implements AutoConfigurationCustomizerProvider { + + private static final InMemorySpanExporter inMemorySpanExporter = InMemorySpanExporter.create(); + + /* + Returns the spans which have been exported by the autoconfigured global OpenTelemetry SDK. + */ + public static List getSpans() { + return inMemorySpanExporter.getFinishedSpanItems(); + } + + @Override + public void customize(AutoConfigurationCustomizer autoConfiguration) { + autoConfiguration.addSpanExporterCustomizer( + (spanExporter, config) -> { + // we piggy-back onto the autoconfigured logging exporter for now, + // because that one uses a SimpleSpanProcessor which does not impose a batching delay + if (spanExporter instanceof LoggingSpanExporter) { + inMemorySpanExporter.reset(); + return SpanExporter.composite(inMemorySpanExporter, spanExporter); + } + return spanExporter; + }); + } + + @Override + public int order() { + // There might be other autoconfigurations wrapping SpanExporters, + // which can result in us failing to detect it + // We avoid this by ensuring that we run first + return Integer.MIN_VALUE; + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index da357d742..db03bb591 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -53,9 +53,10 @@ include(":jmx-scraper") include(":jmx-scraper:test-app") include(":jmx-scraper:test-webapp") include(":maven-extension") +include(":messaging-wrappers:aliyun-mns-sdk") include(":messaging-wrappers:api") include(":messaging-wrappers:kafka-clients") -include(":messaging-wrappers:mns") +include(":messaging-wrappers:testing") include(":micrometer-meter-provider") include(":noop-api") include(":processors") From e588d1e0ab0486c95673322880a23d79a4429bc6 Mon Sep 17 00:00:00 2001 From: cirilla-zmh Date: Wed, 16 Apr 2025 22:18:09 +0800 Subject: [PATCH 08/17] fix test for api and kafka-clients --- messaging-wrappers/api/build.gradle.kts | 5 +- .../UserDefinedMessageSystemTest.java | 6 +- .../kafka-clients/build.gradle.kts | 12 +--- .../wrappers/kafka/KafkaClientBaseTest.java | 7 +- .../wrappers/kafka/KafkaClientTest.java | 69 ++----------------- 5 files changed, 15 insertions(+), 84 deletions(-) diff --git a/messaging-wrappers/api/build.gradle.kts b/messaging-wrappers/api/build.gradle.kts index 14e930cfc..2289e1b32 100644 --- a/messaging-wrappers/api/build.gradle.kts +++ b/messaging-wrappers/api/build.gradle.kts @@ -9,13 +9,12 @@ otelJava.moduleName.set("io.opentelemetry.contrib.messaging.wrappers") dependencies { api("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator") + api("io.opentelemetry.semconv:opentelemetry-semconv") + api("io.opentelemetry.semconv:opentelemetry-semconv-incubating") compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") compileOnly("io.opentelemetry:opentelemetry-api-incubator") - implementation("io.opentelemetry.semconv:opentelemetry-semconv") - implementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating") - testImplementation("com.google.guava:guava:33.4.8-jre") testImplementation(project(":messaging-wrappers:testing")) } diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java index 5d8360213..54a409a07 100644 --- a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java @@ -3,6 +3,7 @@ import com.google.common.eventbus.EventBus; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.Tracer; @@ -26,7 +27,6 @@ import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.MESSAGE_BODY; import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.MESSAGE_ID; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_CLIENT_ID; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; @@ -102,7 +102,9 @@ public void assertTraces() { equalTo( MESSAGING_MESSAGE_BODY_SIZE, MESSAGE_BODY.getBytes(StandardCharsets.UTF_8).length), - equalTo(MESSAGING_CLIENT_ID, CLIENT_ID), + // FIXME: We do have "messaging.client_id" in instrumentation but "messaging.client.id" in + // semconv library right now. It should be replaced after semconv release. + equalTo(AttributeKey.stringKey("messaging.client_id"), CLIENT_ID), equalTo(MESSAGING_OPERATION, "process")), span -> span.hasName("process child") diff --git a/messaging-wrappers/kafka-clients/build.gradle.kts b/messaging-wrappers/kafka-clients/build.gradle.kts index 6afc1ed7f..94f5a591b 100644 --- a/messaging-wrappers/kafka-clients/build.gradle.kts +++ b/messaging-wrappers/kafka-clients/build.gradle.kts @@ -18,22 +18,12 @@ dependencies { testImplementation("org.apache.kafka:kafka-clients:0.11.0.0") testImplementation("io.opentelemetry.instrumentation:opentelemetry-kafka-clients-2.6") + testImplementation(project(":messaging-wrappers:testing")) testAnnotationProcessor("com.google.auto.service:auto-service") testCompileOnly("com.google.auto.service:auto-service-annotations") - testImplementation("org.junit.jupiter:junit-jupiter-api") - testImplementation("org.junit.jupiter:junit-jupiter-params") testImplementation("org.testcontainers:kafka") testImplementation("org.testcontainers:junit-jupiter") - - testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") - testImplementation("io.opentelemetry:opentelemetry-sdk") - testImplementation("io.opentelemetry:opentelemetry-sdk-testing") - testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") - testImplementation("io.opentelemetry.semconv:opentelemetry-semconv") - testImplementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating") - testImplementation("io.opentelemetry:opentelemetry-exporter-logging") - testImplementation("io.opentelemetry.javaagent:opentelemetry-testing-common") } tasks { diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java index 9c4629a71..7026c3ad4 100644 --- a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java +++ b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java @@ -1,6 +1,5 @@ package io.opentelemetry.contrib.messaging.wrappers.kafka; -import io.opentelemetry.api.common.AttributeKey; import java.time.Duration; import java.util.Collection; import java.util.Collections; @@ -10,6 +9,8 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; + +import io.opentelemetry.contrib.messaging.wrappers.testing.AbstractBaseTest; import org.apache.kafka.clients.admin.AdminClient; import org.apache.kafka.clients.admin.NewTopic; import org.apache.kafka.clients.consumer.Consumer; @@ -37,13 +38,11 @@ * */ @SuppressWarnings("OtelInternalJavadoc") @TestInstance(TestInstance.Lifecycle.PER_CLASS) -public abstract class KafkaClientBaseTest { +public abstract class KafkaClientBaseTest extends AbstractBaseTest { private static final Logger logger = LoggerFactory.getLogger(KafkaClientBaseTest.class); protected static final String SHARED_TOPIC = "shared.topic"; - protected static final AttributeKey MESSAGING_CLIENT_ID = - AttributeKey.stringKey("messaging.client_id"); private KafkaContainer kafka; protected Producer producer; diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java index 2bba4d63d..83be8f633 100644 --- a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java +++ b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java @@ -2,43 +2,28 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Scope; import io.opentelemetry.contrib.messaging.wrappers.MessagingProcessWrapper; -import io.opentelemetry.contrib.messaging.wrappers.kafka.internal.AutoConfiguredDataCapture; import io.opentelemetry.contrib.messaging.wrappers.kafka.semconv.KafkaConsumerAttributesExtractor; import io.opentelemetry.contrib.messaging.wrappers.kafka.semconv.KafkaProcessRequest; import io.opentelemetry.instrumentation.kafkaclients.v2_6.TracingConsumerInterceptor; import io.opentelemetry.instrumentation.kafkaclients.v2_6.TracingProducerInterceptor; -import io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil; -import io.opentelemetry.sdk.testing.assertj.TraceAssert; -import io.opentelemetry.sdk.testing.assertj.TracesAssert; -import io.opentelemetry.sdk.trace.data.SpanData; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerRecord; import org.assertj.core.api.AbstractAssert; -import org.awaitility.core.ConditionTimeoutException; import org.junit.jupiter.api.Test; -import javax.annotation.Nullable; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; import java.util.Map; -import java.util.Arrays; -import java.util.concurrent.TimeoutException; -import java.util.function.Consumer; -import java.util.function.Supplier; -import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanName; -import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.waitForTraces; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; @@ -49,7 +34,6 @@ import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; -import static org.awaitility.Awaitility.await; public class KafkaClientTest extends KafkaClientBaseTest { @@ -130,7 +114,7 @@ public void consumeWithChild(Tracer tracer, MessagingProcessWrapper trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), @@ -152,9 +136,9 @@ public void assertTraces() { satisfies( MESSAGING_DESTINATION_PARTITION_ID, org.assertj.core.api.AbstractStringAssert::isNotEmpty), - satisfies( - MESSAGING_CLIENT_ID, - stringAssert -> stringAssert.isEqualTo("test-consumer-1")), + // FIXME: We do have "messaging.client_id" in instrumentation but "messaging.client.id" in + // semconv library right now. It should be replaced after semconv release. + equalTo(AttributeKey.stringKey("messaging.client_id"), "test-consumer-1"), satisfies( MESSAGING_KAFKA_OFFSET, AbstractAssert::isNotNull), @@ -171,47 +155,4 @@ public void assertTraces() { span -> span.hasName("producer callback").hasKind(SpanKind.INTERNAL).hasNoParent())); } - - @SafeVarargs - @SuppressWarnings("varargs") - private static void waitAndAssertTraces( - @Nullable Comparator> traceComparator, - Consumer... assertions) { - List> assertionsList = new ArrayList<>(Arrays.asList(assertions)); - try { - await() - .untilAsserted(() -> doAssertTraces(traceComparator, AutoConfiguredDataCapture::getSpans, assertionsList)); - } catch (Throwable t) { - // awaitility is doing a jmx call that is not implemented in GraalVM: - // call: - // https://github.com/awaitility/awaitility/blob/fbe16add874b4260dd240108304d5c0be84eabc8/awaitility/src/main/java/org/awaitility/core/ConditionAwaiter.java#L157 - // see https://github.com/oracle/graal/issues/6101 (spring boot graal native image) - if (t.getClass().getName().equals("com.oracle.svm.core.jdk.UnsupportedFeatureError") - || t instanceof ConditionTimeoutException) { - // Don't throw this failure since the stack is the awaitility thread, causing confusion. - // Instead, just assert one more time on the test thread, which will fail with a better - // stack trace. - // TODO(anuraaga): There is probably a better way to do this. - doAssertTraces(traceComparator, AutoConfiguredDataCapture::getSpans, assertionsList); - } else { - throw t; - } - } - } - - private static void doAssertTraces( - @Nullable Comparator> traceComparator, - Supplier> supplier, - List> assertionsList) { - try { - List> traces = waitForTraces(supplier, assertionsList.size()); - TelemetryDataUtil.assertScopeVersion(traces); - if (traceComparator != null) { - traces.sort(traceComparator); - } - TracesAssert.assertThat(traces).hasTracesSatisfyingExactly(assertionsList); - } catch (InterruptedException | TimeoutException e) { - throw new AssertionError("Error waiting for " + assertionsList.size() + " traces", e); - } - } } From c73a7ec5b22571d78f88729cf619cbdeca3f8d74 Mon Sep 17 00:00:00 2001 From: cirilla-zmh Date: Wed, 16 Apr 2025 22:35:57 +0800 Subject: [PATCH 09/17] fix build and format test --- messaging-wrappers/README.md | 1 + .../mns/{MNSHelper.java => MnsHelper.java} | 25 ++- ...der.java => MnsProcessWrapperBuilder.java} | 13 +- ...xtMapGetter.java => MnsTextMapGetter.java} | 27 +-- .../wrappers/mns/example/MNSConsumer.java | 72 -------- ...essRequest.java => MnsProcessRequest.java} | 25 +-- messaging-wrappers/api/build.gradle.kts | 2 +- ...DefaultMessagingProcessWrapperBuilder.java | 26 +-- .../wrappers/MessagingProcessWrapper.java | 31 ++-- .../messaging/wrappers/NoopTextMapGetter.java | 10 +- .../messaging/wrappers/ThrowingRunnable.java | 8 +- .../messaging/wrappers/ThrowingSupplier.java | 8 +- .../DefaultMessagingAttributesGetter.java | 169 +++++++++--------- .../semconv/MessagingProcessRequest.java | 14 +- .../messaging/wrappers/TestConstants.java | 5 + .../UserDefinedMessageSystemTest.java | 63 ++++--- .../wrappers/impl/MessageRequest.java | 21 ++- .../wrappers/impl/MessageTextMapGetter.java | 8 +- .../messaging/wrappers/model/Message.java | 5 + .../wrappers/model/MessageListener.java | 28 +-- .../wrappers/model/MessageTextMapSetter.java | 6 +- .../kafka-clients/build.gradle.kts | 2 +- .../messaging/wrappers/kafka/KafkaHelper.java | 11 +- .../kafka/KafkaProcessWrapperBuilder.java | 5 + .../wrappers/kafka/KafkaTextMapGetter.java | 20 ++- .../KafkaConsumerAttributesExtractor.java | 20 ++- .../kafka/semconv/KafkaProcessRequest.java | 28 +-- .../wrappers/kafka/KafkaClientBaseTest.java | 13 +- .../wrappers/kafka/KafkaClientTest.java | 78 ++++---- .../internal/AutoConfiguredDataCapture.java | 7 +- messaging-wrappers/testing/build.gradle.kts | 24 +-- .../wrappers/testing/AbstractBaseTest.java | 26 +-- .../internal/AutoConfiguredDataCapture.java | 6 +- 33 files changed, 449 insertions(+), 358 deletions(-) rename messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/{MNSHelper.java => MnsHelper.java} (77%) rename messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/{MNSProcessWrapperBuilder.java => MnsProcessWrapperBuilder.java} (54%) rename messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/{MNSTextMapGetter.java => MnsTextMapGetter.java} (78%) delete mode 100644 messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/example/MNSConsumer.java rename messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/{MNSProcessRequest.java => MnsProcessRequest.java} (72%) diff --git a/messaging-wrappers/README.md b/messaging-wrappers/README.md index 49067df29..8209105f1 100644 --- a/messaging-wrappers/README.md +++ b/messaging-wrappers/README.md @@ -115,3 +115,4 @@ public class Demo { - [Steve Rao](https://github.com/steverao), Alibaba Learn more about component owners in [component_owners.yml](../.github/component_owners.yml). + diff --git a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSHelper.java b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsHelper.java similarity index 77% rename from messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSHelper.java rename to messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsHelper.java index 99214e861..23b7e3f97 100644 --- a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSHelper.java +++ b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsHelper.java @@ -1,17 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.mns; import com.aliyun.mns.model.BaseMessage; import com.aliyun.mns.model.MessagePropertyValue; import com.aliyun.mns.model.MessageSystemPropertyName; import com.aliyun.mns.model.MessageSystemPropertyValue; -import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessRequest; +import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MnsProcessRequest; + +import javax.annotation.Nullable; -public final class MNSHelper { +public final class MnsHelper { - public static MNSProcessWrapperBuilder processWrapperBuilder() { - return new MNSProcessWrapperBuilder<>(); + public static + MnsProcessWrapperBuilder processWrapperBuilder() { + return new MnsProcessWrapperBuilder<>(); } + @Nullable public static String getMessageHeader(BaseMessage message, String name) { MessageSystemPropertyName key = convert2SystemPropertyName(name); if (key != null) { @@ -27,9 +36,8 @@ public static String getMessageHeader(BaseMessage message, String name) { return null; } - /** - * see {@link MessageSystemPropertyName} - * */ + /** see {@link MessageSystemPropertyName} */ + @Nullable public static MessageSystemPropertyName convert2SystemPropertyName(String name) { if (name == null) { return null; @@ -43,6 +51,5 @@ public static MessageSystemPropertyName convert2SystemPropertyName(String name) return null; } - private MNSHelper() { - } + private MnsHelper() {} } diff --git a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapperBuilder.java b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsProcessWrapperBuilder.java similarity index 54% rename from messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapperBuilder.java rename to messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsProcessWrapperBuilder.java index 5a302d851..493ff93c1 100644 --- a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSProcessWrapperBuilder.java +++ b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsProcessWrapperBuilder.java @@ -1,13 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.mns; import io.opentelemetry.contrib.messaging.wrappers.DefaultMessagingProcessWrapperBuilder; -import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessRequest; +import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MnsProcessRequest; -public class MNSProcessWrapperBuilder +public class MnsProcessWrapperBuilder extends DefaultMessagingProcessWrapperBuilder { - MNSProcessWrapperBuilder() { + MnsProcessWrapperBuilder() { super(); - super.textMapGetter = MNSTextMapGetter.create(); + super.textMapGetter = MnsTextMapGetter.create(); } } diff --git a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSTextMapGetter.java b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsTextMapGetter.java similarity index 78% rename from messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSTextMapGetter.java rename to messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsTextMapGetter.java index e56f1c1f2..4eb854149 100644 --- a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MNSTextMapGetter.java +++ b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsTextMapGetter.java @@ -1,21 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.mns; import static java.util.Collections.emptyList; -import com.aliyun.mns.model.*; +import com.aliyun.mns.model.Message; +import com.aliyun.mns.model.MessagePropertyValue; +import com.aliyun.mns.model.MessageSystemPropertyName; +import com.aliyun.mns.model.MessageSystemPropertyValue; import io.opentelemetry.context.propagation.TextMapGetter; -import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessRequest; - -import javax.annotation.Nullable; +import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MnsProcessRequest; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; -public class MNSTextMapGetter implements TextMapGetter { +public class MnsTextMapGetter implements TextMapGetter { - public static TextMapGetter create() { - return new MNSTextMapGetter<>(); + public static TextMapGetter create() { + return new MnsTextMapGetter<>(); } @Override @@ -39,6 +46,7 @@ public Iterable keys(@Nullable REQUEST carrier) { return keys; } + @Nullable @Override public String get(@Nullable REQUEST carrier, String key) { if (carrier == null || carrier.getMessage() == null) { @@ -47,7 +55,7 @@ public String get(@Nullable REQUEST carrier, String key) { Message message = carrier.getMessage(); // the system property should always take precedence over the user property - MessageSystemPropertyName systemPropertyName = MNSHelper.convert2SystemPropertyName(key); + MessageSystemPropertyName systemPropertyName = MnsHelper.convert2SystemPropertyName(key); if (systemPropertyName != null) { MessageSystemPropertyValue value = message.getSystemProperty(systemPropertyName); if (value != null) { @@ -66,6 +74,5 @@ public String get(@Nullable REQUEST carrier, String key) { return null; } - private MNSTextMapGetter() { - } + private MnsTextMapGetter() {} } diff --git a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/example/MNSConsumer.java b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/example/MNSConsumer.java deleted file mode 100644 index 45032cae2..000000000 --- a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/example/MNSConsumer.java +++ /dev/null @@ -1,72 +0,0 @@ -package io.opentelemetry.contrib.messaging.wrappers.mns.example; - -import com.aliyun.mns.client.CloudAccount; -import com.aliyun.mns.client.CloudQueue; -import com.aliyun.mns.client.MNSClient; -import com.aliyun.mns.common.ClientException; -import com.aliyun.mns.common.ServiceException; -import com.aliyun.mns.model.Message; -import com.aliyun.mns.model.MessageSystemPropertyValue; -import io.opentelemetry.contrib.messaging.wrappers.MessagingProcessWrapper; -import io.opentelemetry.contrib.messaging.wrappers.mns.MNSHelper; -import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MNSProcessRequest; - -import static com.aliyun.mns.model.MessageSystemPropertyName.TRACE_PARENT; - -public class MNSConsumer { - - public static void main(String[] args) { - // 1. create wrapper by default - MessagingProcessWrapper wrapper = MNSHelper.processWrapperBuilder().build(); - CloudAccount account = new CloudAccount("my-ak", "my-sk", "endpoint"); - MNSClient client = account.getMNSClient(); - - final String queueName = "test-queue"; - try { - CloudQueue queue = client.getQueueRef(queueName); - while (true) { - Message popMsg = queue.popMessage(5); - if (popMsg != null) { - // 2. wrap your consume block - String result = wrapper.doProcess(MNSProcessRequest.of(popMsg, queueName), () -> { - System.out.println("message handle: " + popMsg.getReceiptHandle()); - System.out.println("message body: " + popMsg.getMessageBodyAsString()); - System.out.println("message id: " + popMsg.getMessageId()); - System.out.println("message dequeue count:" + popMsg.getDequeueCount()); - MessageSystemPropertyValue systemProperty = popMsg.getSystemProperty(TRACE_PARENT); - if (systemProperty != null) { - System.out.println("message trace parent: " + systemProperty.getStringValueByType()); - } else { - System.out.println("empty system property"); - } - //<> - - queue.deleteMessage(popMsg.getReceiptHandle()); - System.out.println("delete message successfully\n"); - return "success"; - }); - } - } - } catch (ClientException ce) { - System.out.println("Something wrong with the network connection between client and MNS service." - + "Please check your network and DNS availablity."); - ce.printStackTrace(); - } catch (ServiceException se) { - if (se.getErrorCode().equals("QueueNotExist")) { - System.out.println("Queue is not exist.Please create queue before use"); - } else if (se.getErrorCode().equals("TimeExpired")) { - System.out.println("The request is time expired. Please check your local machine timeclock"); - } - /* - you can get more MNS service error code in following link. - https://help.aliyun.com/document_detail/mns/api_reference/error_code/error_code.html?spm=5176.docmns/api_reference/error_code/error_response - */ - se.printStackTrace(); - } catch (Exception e) { - System.out.println("Unknown exception happened!"); - e.printStackTrace(); - } - - client.close(); - } -} diff --git a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MnsProcessRequest.java similarity index 72% rename from messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java rename to messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MnsProcessRequest.java index ea675da38..a07ae80f0 100644 --- a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MNSProcessRequest.java +++ b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MnsProcessRequest.java @@ -1,26 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.mns.semconv; import com.aliyun.mns.model.Message; -import io.opentelemetry.contrib.messaging.wrappers.mns.MNSHelper; +import io.opentelemetry.contrib.messaging.wrappers.mns.MnsHelper; import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; - -import javax.annotation.Nullable; import java.util.Collections; import java.util.List; +import javax.annotation.Nullable; -public class MNSProcessRequest implements MessagingProcessRequest { +public class MnsProcessRequest implements MessagingProcessRequest { private final Message message; - @Nullable - private final String destination; + @Nullable private final String destination; - public static MNSProcessRequest of(Message message) { + public static MnsProcessRequest of(Message message) { return of(message, null); } - public static MNSProcessRequest of(Message message, @Nullable String destination) { - return new MNSProcessRequest(message, destination); + public static MnsProcessRequest of(Message message, @Nullable String destination) { + return new MnsProcessRequest(message, destination); } @Override @@ -76,7 +79,7 @@ public String getMessageId() { @Override public List getMessageHeader(String name) { - String header = MNSHelper.getMessageHeader(message, name); + String header = MnsHelper.getMessageHeader(message, name); if (header == null) { return Collections.emptyList(); } @@ -87,7 +90,7 @@ public Message getMessage() { return message; } - private MNSProcessRequest(Message message, @Nullable String destination) { + private MnsProcessRequest(Message message, @Nullable String destination) { this.message = message; this.destination = destination; } diff --git a/messaging-wrappers/api/build.gradle.kts b/messaging-wrappers/api/build.gradle.kts index 2289e1b32..cc27cc6e0 100644 --- a/messaging-wrappers/api/build.gradle.kts +++ b/messaging-wrappers/api/build.gradle.kts @@ -26,4 +26,4 @@ tasks { jvmArgs("-Dotel.metrics.exporter=logging") jvmArgs("-Dotel.logs.exporter=logging") } -} \ No newline at end of file +} diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java index c9d414cfd..f8b0c8cf3 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java @@ -1,3 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -9,19 +14,16 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; - -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import javax.annotation.Nullable; public class DefaultMessagingProcessWrapperBuilder { - @Nullable - private OpenTelemetry openTelemetry; + @Nullable private OpenTelemetry openTelemetry; - @Nullable - protected TextMapGetter textMapGetter; + @Nullable protected TextMapGetter textMapGetter; @CanIgnoreReturnValue public DefaultMessagingProcessWrapperBuilder openTelemetry(OpenTelemetry openTelemetry) { @@ -32,15 +34,18 @@ public DefaultMessagingProcessWrapperBuilder openTelemetry(OpenTelemetr protected List> attributesExtractors; @CanIgnoreReturnValue - public DefaultMessagingProcessWrapperBuilder textMapGetter(TextMapGetter textMapGetter) { + public DefaultMessagingProcessWrapperBuilder textMapGetter( + TextMapGetter textMapGetter) { this.textMapGetter = textMapGetter; return this; } /** * This method overrides the original items. - *

See {@link DefaultMessagingProcessWrapperBuilder#addAttributesExtractor} if you just want to append one.

- * */ + * + *

See {@link DefaultMessagingProcessWrapperBuilder#addAttributesExtractor} if you just want to + * append one. + */ @CanIgnoreReturnValue public DefaultMessagingProcessWrapperBuilder attributesExtractors( Collection> attributesExtractors) { @@ -73,7 +78,8 @@ public MessagingProcessWrapper build() { protected DefaultMessagingProcessWrapperBuilder() { // init attributes extractors by default this.attributesExtractors = new ArrayList<>(); - this.attributesExtractors.add(MessagingAttributesExtractor.create( + this.attributesExtractors.add( + MessagingAttributesExtractor.create( DefaultMessagingAttributesGetter.create(), MessageOperation.PROCESS)); } } diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java index a60d9d391..89a8f099b 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java @@ -1,5 +1,12 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers; +import static io.opentelemetry.api.trace.SpanKind.CONSUMER; + import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; @@ -12,11 +19,8 @@ import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; - -import javax.annotation.Nullable; import java.util.List; - -import static io.opentelemetry.api.trace.SpanKind.CONSUMER; +import javax.annotation.Nullable; public class MessagingProcessWrapper { @@ -35,11 +39,13 @@ public class MessagingProcessWrapper { // no attributes need to be extracted from responses in process operations private final List> attributesExtractors; - public static DefaultMessagingProcessWrapperBuilder defaultBuilder() { + public static + DefaultMessagingProcessWrapperBuilder defaultBuilder() { return new DefaultMessagingProcessWrapperBuilder<>(); } - public void doProcess(REQUEST request, ThrowingRunnable runnable) throws E { + public void doProcess(REQUEST request, ThrowingRunnable runnable) + throws E { Span span = handleStart(request); try (Scope scope = span.makeCurrent()) { @@ -52,7 +58,8 @@ public void doProcess(REQUEST request, ThrowingRunnable handleEnd(span, request, null); } - public R doProcess(REQUEST request, ThrowingSupplier supplier) throws E { + public R doProcess(REQUEST request, ThrowingSupplier supplier) + throws E { Span span = handleStart(request); R result = null; @@ -68,7 +75,8 @@ public R doProcess(REQUEST request, ThrowingSupplier textMapGetter, - List> attributesExtractors) { + protected MessagingProcessWrapper( + OpenTelemetry openTelemetry, + TextMapGetter textMapGetter, + List> attributesExtractors) { this.textMapPropagator = openTelemetry.getPropagators().getTextMapPropagator(); this.tracer = openTelemetry.getTracer(INSTRUMENTATION_SCOPE, INSTRUMENTATION_VERSION); this.textMapGetter = textMapGetter; diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/NoopTextMapGetter.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/NoopTextMapGetter.java index 5432c3e4d..213fb4a6a 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/NoopTextMapGetter.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/NoopTextMapGetter.java @@ -1,11 +1,17 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers; import io.opentelemetry.context.propagation.TextMapGetter; import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; -import javax.annotation.Nullable; import java.util.Collections; +import javax.annotation.Nullable; -public class NoopTextMapGetter implements TextMapGetter { +public class NoopTextMapGetter + implements TextMapGetter { public static TextMapGetter create() { return new NoopTextMapGetter<>(); diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/ThrowingRunnable.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/ThrowingRunnable.java index 6e5279632..e1bbd05b0 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/ThrowingRunnable.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/ThrowingRunnable.java @@ -1,9 +1,15 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers; /** * A utility interface representing a {@link Runnable} that may throw. * - *

Inspired from ThrowingRunnable. + *

Inspired from ThrowingRunnable. * * @param Thrown exception type. */ diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/ThrowingSupplier.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/ThrowingSupplier.java index 1f1f1c974..9bec00d4b 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/ThrowingSupplier.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/ThrowingSupplier.java @@ -1,3 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers; import java.util.function.Supplier; @@ -5,7 +10,8 @@ /** * A utility interface representing a {@link Supplier} that may throw. * - *

Inspired from ThrowingSupplier. + *

Inspired from ThrowingSupplier. * * @param Thrown exception type. */ diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingAttributesGetter.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingAttributesGetter.java index 88e7531b0..89c37c703 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingAttributesGetter.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingAttributesGetter.java @@ -1,89 +1,94 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.semconv; import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; - -import javax.annotation.Nullable; import java.util.List; +import javax.annotation.Nullable; public class DefaultMessagingAttributesGetter - implements MessagingAttributesGetter { - - public static MessagingAttributesGetter create() { - return new DefaultMessagingAttributesGetter<>(); - } - - @Nullable - @Override - public String getDestinationPartitionId(REQUEST request) { - return request.getDestinationPartitionId(); - } - - @Override - public List getMessageHeader(REQUEST request, String name) { - return request.getMessageHeader(name); - } - - @Nullable - @Override - public String getSystem(REQUEST request) { - return request.getSystem(); - } - - @Nullable - @Override - public String getDestination(REQUEST request) { - return request.getDestination(); - } - - @Nullable - @Override - public String getDestinationTemplate(REQUEST request) { - return request.getDestinationTemplate(); - } - - @Override - public boolean isTemporaryDestination(REQUEST request) { - return request.isTemporaryDestination(); - } - - @Override - public boolean isAnonymousDestination(REQUEST request) { - return request.isAnonymousDestination(); - } - - @Nullable - @Override - public String getConversationId(REQUEST request) { - return request.getConversationId(); - } - - @Nullable - @Override - public Long getMessageBodySize(REQUEST request) { - return request.getMessageBodySize(); - } - - @Nullable - @Override - public Long getMessageEnvelopeSize(REQUEST request) { - return request.getMessageEnvelopeSize(); - } - - @Nullable - @Override - public String getMessageId(REQUEST request, @Nullable Void unused) { - return request.getMessageId(); - } - - @Nullable - @Override - public String getClientId(REQUEST request) { - return request.getClientId(); - } - - @Nullable - @Override - public Long getBatchMessageCount(REQUEST request, @Nullable Void unused) { - return request.getBatchMessageCount(); - } + implements MessagingAttributesGetter { + + public static + MessagingAttributesGetter create() { + return new DefaultMessagingAttributesGetter<>(); + } + + @Nullable + @Override + public String getDestinationPartitionId(REQUEST request) { + return request.getDestinationPartitionId(); + } + + @Override + public List getMessageHeader(REQUEST request, String name) { + return request.getMessageHeader(name); + } + + @Nullable + @Override + public String getSystem(REQUEST request) { + return request.getSystem(); + } + + @Nullable + @Override + public String getDestination(REQUEST request) { + return request.getDestination(); + } + + @Nullable + @Override + public String getDestinationTemplate(REQUEST request) { + return request.getDestinationTemplate(); + } + + @Override + public boolean isTemporaryDestination(REQUEST request) { + return request.isTemporaryDestination(); + } + + @Override + public boolean isAnonymousDestination(REQUEST request) { + return request.isAnonymousDestination(); + } + + @Nullable + @Override + public String getConversationId(REQUEST request) { + return request.getConversationId(); + } + + @Nullable + @Override + public Long getMessageBodySize(REQUEST request) { + return request.getMessageBodySize(); + } + + @Nullable + @Override + public Long getMessageEnvelopeSize(REQUEST request) { + return request.getMessageEnvelopeSize(); + } + + @Nullable + @Override + public String getMessageId(REQUEST request, @Nullable Void unused) { + return request.getMessageId(); + } + + @Nullable + @Override + public String getClientId(REQUEST request) { + return request.getClientId(); + } + + @Nullable + @Override + public Long getBatchMessageCount(REQUEST request, @Nullable Void unused) { + return request.getBatchMessageCount(); + } } diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java index ab5e50480..42d342d79 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java @@ -1,14 +1,20 @@ -package io.opentelemetry.contrib.messaging.wrappers.semconv; +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ -import javax.annotation.Nullable; -import java.util.List; +package io.opentelemetry.contrib.messaging.wrappers.semconv; import static java.util.Collections.emptyList; +import java.util.List; +import javax.annotation.Nullable; + /** * An interface to expose messaging properties for the pre-defined process wrapper. * - *

Inspired from MessagingAttributesGetter. + *

Inspired from MessagingAttributesGetter. */ public interface MessagingProcessRequest { diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/TestConstants.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/TestConstants.java index 49d07ab8f..a826c2393 100644 --- a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/TestConstants.java +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/TestConstants.java @@ -1,3 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers; public final class TestConstants { diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java index 54a409a07..43fca9167 100644 --- a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java @@ -1,5 +1,20 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers; +import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.CLIENT_ID; +import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.EVENTBUS_NAME; +import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.MESSAGE_BODY; +import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.MESSAGE_ID; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; + import com.google.common.eventbus.EventBus; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; @@ -15,23 +30,12 @@ import io.opentelemetry.contrib.messaging.wrappers.model.MessageListener; import io.opentelemetry.contrib.messaging.wrappers.model.MessageTextMapSetter; import io.opentelemetry.contrib.messaging.wrappers.testing.AbstractBaseTest; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; - -import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.CLIENT_ID; -import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.EVENTBUS_NAME; -import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.MESSAGE_BODY; -import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.MESSAGE_ID; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; - @SuppressWarnings("OtelInternalJavadoc") @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class UserDefinedMessageSystemTest extends AbstractBaseTest { @@ -46,10 +50,11 @@ public class UserDefinedMessageSystemTest extends AbstractBaseTest { void setupClass() { otel = GlobalOpenTelemetry.get(); tracer = otel.getTracer("test-tracer", "1.0.0"); - MessagingProcessWrapper wrapper = MessagingProcessWrapper.defaultBuilder() - .openTelemetry(otel) - .textMapGetter(MessageTextMapGetter.create()) - .build(); + MessagingProcessWrapper wrapper = + MessagingProcessWrapper.defaultBuilder() + .openTelemetry(otel) + .textMapGetter(MessageTextMapGetter.create()) + .build(); eventBus = new EventBus(); @@ -65,13 +70,14 @@ void testSendAndConsume() { public void sendWithParent(Tracer tracer) { // mock a send span - Span parent = tracer.spanBuilder("publish " + EVENTBUS_NAME) - .setSpanKind(SpanKind.PRODUCER) - .startSpan(); + Span parent = + tracer.spanBuilder("publish " + EVENTBUS_NAME).setSpanKind(SpanKind.PRODUCER).startSpan(); try (Scope scope = parent.makeCurrent()) { Message message = Message.create(new HashMap<>(), MESSAGE_ID, MESSAGE_BODY); - otel.getPropagators().getTextMapPropagator().inject(Context.current(), message, MessageTextMapSetter.create()); + otel.getPropagators() + .getTextMapPropagator() + .inject(Context.current(), message, MessageTextMapSetter.create()); eventBus.post(message); } @@ -79,8 +85,9 @@ public void sendWithParent(Tracer tracer) { } /** - * Copied from testing-common. - * */ + * Copied from testing-common. + */ @SuppressWarnings("deprecation") // using deprecated semconv public void assertTraces() { waitAndAssertTraces( @@ -88,7 +95,8 @@ public void assertTraces() { trace -> trace.hasSpansSatisfyingExactly( span -> - // No need to verify the attribute here because it is generated by instrumentation library. + // No need to verify the attribute here because it is generated by + // instrumentation library. span.hasName("publish " + EVENTBUS_NAME) .hasKind(SpanKind.PRODUCER) .hasNoParent(), @@ -102,8 +110,10 @@ public void assertTraces() { equalTo( MESSAGING_MESSAGE_BODY_SIZE, MESSAGE_BODY.getBytes(StandardCharsets.UTF_8).length), - // FIXME: We do have "messaging.client_id" in instrumentation but "messaging.client.id" in - // semconv library right now. It should be replaced after semconv release. + // FIXME: We do have "messaging.client_id" in instrumentation but + // "messaging.client.id" in + // semconv library right now. It should be replaced after semconv + // release. equalTo(AttributeKey.stringKey("messaging.client_id"), CLIENT_ID), equalTo(MESSAGING_OPERATION, "process")), span -> @@ -111,5 +121,4 @@ public void assertTraces() { .hasKind(SpanKind.INTERNAL) .hasParent(trace.getSpan(1)))); } - } diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageRequest.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageRequest.java index 152cec839..c70523c4b 100644 --- a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageRequest.java +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageRequest.java @@ -1,29 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.impl; import io.opentelemetry.contrib.messaging.wrappers.model.Message; import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; - -import javax.annotation.Nullable; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; +import javax.annotation.Nullable; public class MessageRequest implements MessagingProcessRequest { private final Message message; - @Nullable - private final String clientId; + @Nullable private final String clientId; - @Nullable - private final String eventBusName; + @Nullable private final String eventBusName; public static MessageRequest of(Message message) { return of(message, null, null); } - public static MessageRequest of(Message message, - @Nullable String clientId, @Nullable String eventBusName) { + public static MessageRequest of( + Message message, @Nullable String clientId, @Nullable String eventBusName) { return new MessageRequest(message, clientId, eventBusName); } @@ -93,7 +95,8 @@ public Message getMessage() { return message; } - private MessageRequest(Message message, @Nullable String clientId, @Nullable String eventBusName) { + private MessageRequest( + Message message, @Nullable String clientId, @Nullable String eventBusName) { this.message = message; this.clientId = clientId; this.eventBusName = eventBusName; diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageTextMapGetter.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageTextMapGetter.java index 501e14bd4..4fa167fcb 100644 --- a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageTextMapGetter.java +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageTextMapGetter.java @@ -1,9 +1,13 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.impl; import io.opentelemetry.context.propagation.TextMapGetter; - -import javax.annotation.Nullable; import java.util.Collections; +import javax.annotation.Nullable; public class MessageTextMapGetter implements TextMapGetter { diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/Message.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/Message.java index 2839094ec..e606e214e 100644 --- a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/Message.java +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/Message.java @@ -1,3 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.model; import java.util.Map; diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageListener.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageListener.java index 27e17b398..cc22a8958 100644 --- a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageListener.java +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageListener.java @@ -1,16 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.model; +import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.CLIENT_ID; +import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.EVENTBUS_NAME; + import com.google.common.eventbus.Subscribe; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.contrib.messaging.wrappers.impl.MessageRequest; import io.opentelemetry.contrib.messaging.wrappers.MessagingProcessWrapper; +import io.opentelemetry.contrib.messaging.wrappers.impl.MessageRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.CLIENT_ID; -import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.EVENTBUS_NAME; - public class MessageListener { private static final Logger logger = LoggerFactory.getLogger(MessageListener.class); @@ -19,17 +24,20 @@ public class MessageListener { private final MessagingProcessWrapper wrapper; - public static MessageListener create(Tracer tracer, MessagingProcessWrapper wrapper) { + public static MessageListener create( + Tracer tracer, MessagingProcessWrapper wrapper) { return new MessageListener(tracer, wrapper); } @Subscribe public void handleEvent(Message event) { - wrapper.doProcess(MessageRequest.of(event, CLIENT_ID, EVENTBUS_NAME), () -> { - Span span = tracer.spanBuilder("process child").startSpan(); - logger.info("Received event from <" + EVENTBUS_NAME + ">: " + event.getId()); - span.end(); - }); + wrapper.doProcess( + MessageRequest.of(event, CLIENT_ID, EVENTBUS_NAME), + () -> { + Span span = tracer.spanBuilder("process child").startSpan(); + logger.info("Received event from <" + EVENTBUS_NAME + ">: " + event.getId()); + span.end(); + }); } private MessageListener(Tracer tracer, MessagingProcessWrapper wrapper) { diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageTextMapSetter.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageTextMapSetter.java index 83e68ac11..77e5a56f4 100644 --- a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageTextMapSetter.java +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageTextMapSetter.java @@ -1,7 +1,11 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.model; import io.opentelemetry.context.propagation.TextMapSetter; - import javax.annotation.Nullable; public class MessageTextMapSetter implements TextMapSetter { diff --git a/messaging-wrappers/kafka-clients/build.gradle.kts b/messaging-wrappers/kafka-clients/build.gradle.kts index 94f5a591b..56ad60c53 100644 --- a/messaging-wrappers/kafka-clients/build.gradle.kts +++ b/messaging-wrappers/kafka-clients/build.gradle.kts @@ -37,4 +37,4 @@ tasks { jvmArgs("-Dotel.metrics.exporter=logging") jvmArgs("-Dotel.logs.exporter=logging") } -} \ No newline at end of file +} diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaHelper.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaHelper.java index 88d32cc50..fee425043 100644 --- a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaHelper.java +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaHelper.java @@ -1,13 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.kafka; import io.opentelemetry.contrib.messaging.wrappers.kafka.semconv.KafkaProcessRequest; public final class KafkaHelper { - public static KafkaProcessWrapperBuilder processWrapperBuilder() { + public static + KafkaProcessWrapperBuilder processWrapperBuilder() { return new KafkaProcessWrapperBuilder<>(); } - private KafkaHelper() { - } + private KafkaHelper() {} } diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaProcessWrapperBuilder.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaProcessWrapperBuilder.java index 80630b7f8..75716640c 100644 --- a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaProcessWrapperBuilder.java +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaProcessWrapperBuilder.java @@ -1,3 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.kafka; import io.opentelemetry.contrib.messaging.wrappers.DefaultMessagingProcessWrapperBuilder; diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaTextMapGetter.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaTextMapGetter.java index efcb7718b..483561525 100644 --- a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaTextMapGetter.java +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaTextMapGetter.java @@ -1,19 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.kafka; import io.opentelemetry.context.propagation.TextMapGetter; import io.opentelemetry.contrib.messaging.wrappers.kafka.semconv.KafkaProcessRequest; -import org.apache.kafka.common.header.Header; - -import javax.annotation.Nullable; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.stream.Collectors; import java.util.stream.StreamSupport; +import javax.annotation.Nullable; +import org.apache.kafka.common.header.Header; /** - * Copied from KafkaConsumerRecordGetter. - * */ -public class KafkaTextMapGetter implements TextMapGetter { + * Copied from KafkaConsumerRecordGetter. + */ +public class KafkaTextMapGetter + implements TextMapGetter { public static TextMapGetter create() { return new KafkaTextMapGetter<>(); @@ -45,4 +51,4 @@ public String get(@Nullable REQUEST carrier, String key) { } return new String(value, StandardCharsets.UTF_8); } -} \ No newline at end of file +} diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesExtractor.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesExtractor.java index c4eec9261..e65df97d8 100644 --- a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesExtractor.java +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesExtractor.java @@ -1,17 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.kafka.semconv; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; -import org.apache.kafka.clients.consumer.ConsumerRecord; - -import javax.annotation.Nullable; import java.nio.ByteBuffer; +import javax.annotation.Nullable; +import org.apache.kafka.clients.consumer.ConsumerRecord; /** - * Copied from KafkaConsumerAttributesExtractor. - * */ + * Copied from KafkaConsumerAttributesExtractor. + */ public final class KafkaConsumerAttributesExtractor implements AttributesExtractor { @@ -32,8 +37,7 @@ public static AttributesExtractor record = request.getRecord(); @@ -69,4 +73,4 @@ public void onEnd( @Nullable Throwable error) {} private KafkaConsumerAttributesExtractor() {} -} \ No newline at end of file +} diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaProcessRequest.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaProcessRequest.java index 4c801a23b..ef9149b2b 100644 --- a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaProcessRequest.java +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaProcessRequest.java @@ -1,30 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.kafka.semconv; import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; -import org.apache.kafka.clients.consumer.ConsumerRecord; - -import javax.annotation.Nullable; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.stream.Collectors; import java.util.stream.StreamSupport; +import javax.annotation.Nullable; +import org.apache.kafka.clients.consumer.ConsumerRecord; public class KafkaProcessRequest implements MessagingProcessRequest { private final ConsumerRecord consumerRecord; - @Nullable - private final String clientId; + @Nullable private final String clientId; - @Nullable - private final String consumerGroup; + @Nullable private final String consumerGroup; public static KafkaProcessRequest of(ConsumerRecord consumerRecord) { return of(consumerRecord, null, null); } - public static KafkaProcessRequest of(ConsumerRecord consumerRecord, - @Nullable String consumerGroup, @Nullable String clientId) { + public static KafkaProcessRequest of( + ConsumerRecord consumerRecord, + @Nullable String consumerGroup, + @Nullable String clientId) { return new KafkaProcessRequest(consumerRecord, consumerGroup, clientId); } @@ -108,8 +112,10 @@ public String getConsumerGroup() { return consumerRecord; } - private KafkaProcessRequest(ConsumerRecord consumerRecord, - @Nullable String consumerGroup, @Nullable String clientId) { + private KafkaProcessRequest( + ConsumerRecord consumerRecord, + @Nullable String consumerGroup, + @Nullable String clientId) { this.consumerRecord = consumerRecord; this.consumerGroup = consumerGroup; this.clientId = clientId; diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java index 7026c3ad4..b2c55397d 100644 --- a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java +++ b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java @@ -1,5 +1,11 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.kafka; +import io.opentelemetry.contrib.messaging.wrappers.testing.AbstractBaseTest; import java.time.Duration; import java.util.Collection; import java.util.Collections; @@ -9,8 +15,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; - -import io.opentelemetry.contrib.messaging.wrappers.testing.AbstractBaseTest; import org.apache.kafka.clients.admin.AdminClient; import org.apache.kafka.clients.admin.NewTopic; import org.apache.kafka.clients.consumer.Consumer; @@ -34,8 +38,9 @@ import org.testcontainers.utility.DockerImageName; /** - * Copied from KafkaClientBaseTest. - * */ + * Copied from KafkaClientBaseTest. + */ @SuppressWarnings("OtelInternalJavadoc") @TestInstance(TestInstance.Lifecycle.PER_CLASS) public abstract class KafkaClientBaseTest extends AbstractBaseTest { diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java index 83be8f633..cc61e0965 100644 --- a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java +++ b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java @@ -1,5 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.kafka; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_CONSUMER_GROUP_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_PARTITION_ID; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_OFFSET; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; + import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributeKey; @@ -12,6 +28,9 @@ import io.opentelemetry.contrib.messaging.wrappers.kafka.semconv.KafkaProcessRequest; import io.opentelemetry.instrumentation.kafkaclients.v2_6.TracingConsumerInterceptor; import io.opentelemetry.instrumentation.kafkaclients.v2_6.TracingProducerInterceptor; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Map; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; @@ -20,21 +39,6 @@ import org.assertj.core.api.AbstractAssert; import org.junit.jupiter.api.Test; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.Map; - -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_CONSUMER_GROUP_NAME; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_PARTITION_ID; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_OFFSET; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; - public class KafkaClientTest extends KafkaClientBaseTest { static final String greeting = "Hello Kafka!"; @@ -65,10 +69,11 @@ public Map consumerProps() { void testInterceptors() throws InterruptedException { OpenTelemetry otel = GlobalOpenTelemetry.get(); Tracer tracer = otel.getTracer("test-tracer", "1.0.0"); - MessagingProcessWrapper wrapper = KafkaHelper.processWrapperBuilder() - .openTelemetry(otel) - .addAttributesExtractor(KafkaConsumerAttributesExtractor.create()) - .build(); + MessagingProcessWrapper wrapper = + KafkaHelper.processWrapperBuilder() + .openTelemetry(otel) + .addAttributesExtractor(KafkaConsumerAttributesExtractor.create()) + .build(); sendWithParent(tracer); @@ -83,7 +88,8 @@ void testInterceptors() throws InterruptedException { public void sendWithParent(Tracer tracer) { Span parent = tracer.spanBuilder("parent").startSpan(); try (Scope scope = parent.makeCurrent()) { - producer.send(new ProducerRecord<>(SHARED_TOPIC, greeting), + producer.send( + new ProducerRecord<>(SHARED_TOPIC, greeting), (meta, ex) -> { if (ex == null) { tracer.spanBuilder("producer callback").startSpan().end(); @@ -95,7 +101,8 @@ public void sendWithParent(Tracer tracer) { parent.end(); } - public void consumeWithChild(Tracer tracer, MessagingProcessWrapper wrapper) { + public void consumeWithChild( + Tracer tracer, MessagingProcessWrapper wrapper) { // check that the message was received ConsumerRecords records = consumer.poll(Duration.ofSeconds(5)); assertThat(records.count()).isEqualTo(1); @@ -103,14 +110,17 @@ public void consumeWithChild(Tracer tracer, MessagingProcessWrapper { - tracer.spanBuilder("process child").startSpan().end(); - }); + wrapper.doProcess( + KafkaProcessRequest.of(record, groupId, clientId), + () -> { + tracer.spanBuilder("process child").startSpan().end(); + }); } /** - * Copied from testing-common. - * */ + * Copied from testing-common. + */ @SuppressWarnings("deprecation") // using deprecated semconv public void assertTraces() { waitAndAssertTraces( @@ -119,7 +129,8 @@ public void assertTraces() { trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), span -> - // No need to verify the attribute here because it is generated by instrumentation library. + // No need to verify the attribute here because it is generated by + // instrumentation library. span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)), @@ -136,12 +147,13 @@ public void assertTraces() { satisfies( MESSAGING_DESTINATION_PARTITION_ID, org.assertj.core.api.AbstractStringAssert::isNotEmpty), - // FIXME: We do have "messaging.client_id" in instrumentation but "messaging.client.id" in - // semconv library right now. It should be replaced after semconv release. - equalTo(AttributeKey.stringKey("messaging.client_id"), "test-consumer-1"), - satisfies( - MESSAGING_KAFKA_OFFSET, - AbstractAssert::isNotNull), + // FIXME: We do have "messaging.client_id" in instrumentation but + // "messaging.client.id" in + // semconv library right now. It should be replaced after semconv + // release. + equalTo( + AttributeKey.stringKey("messaging.client_id"), "test-consumer-1"), + satisfies(MESSAGING_KAFKA_OFFSET, AbstractAssert::isNotNull), equalTo(MESSAGING_CONSUMER_GROUP_NAME, "test"), equalTo(MESSAGING_OPERATION, "process")), span -> diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfiguredDataCapture.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfiguredDataCapture.java index a3b7299f9..177cbf06b 100644 --- a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfiguredDataCapture.java +++ b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfiguredDataCapture.java @@ -1,9 +1,14 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.kafka.internal; import com.google.auto.service.AutoService; +import io.opentelemetry.exporter.logging.LoggingSpanExporter; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; -import io.opentelemetry.exporter.logging.LoggingSpanExporter; import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SpanExporter; diff --git a/messaging-wrappers/testing/build.gradle.kts b/messaging-wrappers/testing/build.gradle.kts index 7385d24ee..6c0983923 100644 --- a/messaging-wrappers/testing/build.gradle.kts +++ b/messaging-wrappers/testing/build.gradle.kts @@ -1,20 +1,20 @@ plugins { - id("otel.java-conventions") + id("otel.java-conventions") } description = "OpenTelemetry Messaging Wrappers testing" dependencies { - annotationProcessor("com.google.auto.service:auto-service") - compileOnly("com.google.auto.service:auto-service-annotations") + annotationProcessor("com.google.auto.service:auto-service") + compileOnly("com.google.auto.service:auto-service-annotations") - api("org.junit.jupiter:junit-jupiter-api") - api("org.junit.jupiter:junit-jupiter-params") - api("io.opentelemetry:opentelemetry-sdk-testing") + api("org.junit.jupiter:junit-jupiter-api") + api("org.junit.jupiter:junit-jupiter-params") + api("io.opentelemetry:opentelemetry-sdk-testing") - implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") - implementation("io.opentelemetry:opentelemetry-sdk-trace") - implementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") - implementation("io.opentelemetry:opentelemetry-exporter-logging") - implementation("io.opentelemetry.javaagent:opentelemetry-testing-common") -} \ No newline at end of file + implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + implementation("io.opentelemetry:opentelemetry-sdk-trace") + implementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") + implementation("io.opentelemetry:opentelemetry-exporter-logging") + implementation("io.opentelemetry.javaagent:opentelemetry-testing-common") +} diff --git a/messaging-wrappers/testing/src/main/java/io/opentelemetry/contrib/messaging/wrappers/testing/AbstractBaseTest.java b/messaging-wrappers/testing/src/main/java/io/opentelemetry/contrib/messaging/wrappers/testing/AbstractBaseTest.java index 3eedc9846..9ed951bce 100644 --- a/messaging-wrappers/testing/src/main/java/io/opentelemetry/contrib/messaging/wrappers/testing/AbstractBaseTest.java +++ b/messaging-wrappers/testing/src/main/java/io/opentelemetry/contrib/messaging/wrappers/testing/AbstractBaseTest.java @@ -1,13 +1,19 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.testing; +import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanName; +import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.waitForTraces; +import static org.awaitility.Awaitility.await; + import io.opentelemetry.contrib.messaging.wrappers.testing.internal.AutoConfiguredDataCapture; import io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil; import io.opentelemetry.sdk.testing.assertj.TraceAssert; import io.opentelemetry.sdk.testing.assertj.TracesAssert; import io.opentelemetry.sdk.trace.data.SpanData; -import org.awaitility.core.ConditionTimeoutException; - -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -15,10 +21,8 @@ import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import java.util.function.Supplier; - -import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanName; -import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.waitForTraces; -import static org.awaitility.Awaitility.await; +import javax.annotation.Nullable; +import org.awaitility.core.ConditionTimeoutException; public abstract class AbstractBaseTest { @@ -29,12 +33,14 @@ public static Comparator> sortByRootSpanName(String... names) { @SafeVarargs @SuppressWarnings("varargs") public static void waitAndAssertTraces( - @Nullable Comparator> traceComparator, - Consumer... assertions) { + @Nullable Comparator> traceComparator, Consumer... assertions) { List> assertionsList = new ArrayList<>(Arrays.asList(assertions)); try { await() - .untilAsserted(() -> doAssertTraces(traceComparator, AutoConfiguredDataCapture::getSpans, assertionsList)); + .untilAsserted( + () -> + doAssertTraces( + traceComparator, AutoConfiguredDataCapture::getSpans, assertionsList)); } catch (Throwable t) { // awaitility is doing a jmx call that is not implemented in GraalVM: // call: diff --git a/messaging-wrappers/testing/src/main/java/io/opentelemetry/contrib/messaging/wrappers/testing/internal/AutoConfiguredDataCapture.java b/messaging-wrappers/testing/src/main/java/io/opentelemetry/contrib/messaging/wrappers/testing/internal/AutoConfiguredDataCapture.java index 45f96ecb3..9d25fe83f 100644 --- a/messaging-wrappers/testing/src/main/java/io/opentelemetry/contrib/messaging/wrappers/testing/internal/AutoConfiguredDataCapture.java +++ b/messaging-wrappers/testing/src/main/java/io/opentelemetry/contrib/messaging/wrappers/testing/internal/AutoConfiguredDataCapture.java @@ -1,3 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.contrib.messaging.wrappers.testing.internal; import com.google.auto.service.AutoService; @@ -7,7 +12,6 @@ import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SpanExporter; - import java.util.List; @AutoService(AutoConfigurationCustomizerProvider.class) From b40d6c48b0802b5055159ee0f1194b652d6bc017 Mon Sep 17 00:00:00 2001 From: cirilla-zmh Date: Wed, 16 Apr 2025 22:57:53 +0800 Subject: [PATCH 10/17] fix tests --- messaging-wrappers/README.md | 19 +++---- .../messaging/wrappers/mns/MnsHelper.java | 1 - .../internal/AutoConfiguredDataCapture.java | 50 ------------------- 3 files changed, 10 insertions(+), 60 deletions(-) delete mode 100644 messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfiguredDataCapture.java diff --git a/messaging-wrappers/README.md b/messaging-wrappers/README.md index 8209105f1..dd13181f1 100644 --- a/messaging-wrappers/README.md +++ b/messaging-wrappers/README.md @@ -2,15 +2,15 @@ This is a lightweight messaging wrappers API designed to help you quickly add instrumentation to any type of messaging system client. To further ease the burden of instrumentation, we will also provide -predefined implementations for certain messaging systems, helping you seamlessly address the issue +predefined implementations for certain messaging systems, helping you seamlessly address the issue of broken traces. ## Overview -The primary goal of this API is to simplify the process of adding instrumentation to your messaging -systems, thereby enhancing observability without introducing significant overhead. Inspired by -[#13340](https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/13340) and -[opentelemetry-java-instrumentation](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractor.java), +The primary goal of this API is to simplify the process of adding instrumentation to your messaging +systems, thereby enhancing observability without introducing significant overhead. Inspired by +[#13340](https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/13340) and +[opentelemetry-java-instrumentation](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/messaging/MessagingAttributesExtractor.java), this tool aims to streamline the tracing and monitoring process. ## Predefined Implementations @@ -31,7 +31,7 @@ Gradle and Maven. ```kotlin dependencies { - implementation("io.opentelemetry.contrib:opentelemetry-messaging-wrappers-api") + implementation("io.opentelemetry.contrib:opentelemetry-messaging-wrappers-api:${latest_version}") } ``` @@ -41,6 +41,7 @@ dependencies { io.opentelemetry.contrib opentelemetry-messaging-wrappers-api + ${latest_version} ``` @@ -81,7 +82,7 @@ an implementation based on the OpenTelemetry semantic convention by default. ```java public class KafkaDemo { - + public static MessagingProcessWrapper createWrapper() { return KafkaHelper.processWrapperBuilder().build(); } @@ -98,9 +99,9 @@ If both are enabled, it might result in duplicate nested process spans. It is re ```java public class Demo { - + private static final MessagingProcessWrapper WRAPPER = createWrapper(); - + public String consume(Message message) { WRAPPER.doProcess(new MyMessagingProcessRequest(message), () -> { // your processing logic diff --git a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsHelper.java b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsHelper.java index 23b7e3f97..59c7fb931 100644 --- a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsHelper.java +++ b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsHelper.java @@ -10,7 +10,6 @@ import com.aliyun.mns.model.MessageSystemPropertyName; import com.aliyun.mns.model.MessageSystemPropertyValue; import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MnsProcessRequest; - import javax.annotation.Nullable; public final class MnsHelper { diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfiguredDataCapture.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfiguredDataCapture.java deleted file mode 100644 index 177cbf06b..000000000 --- a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/internal/AutoConfiguredDataCapture.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.messaging.wrappers.kafka.internal; - -import com.google.auto.service.AutoService; -import io.opentelemetry.exporter.logging.LoggingSpanExporter; -import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer; -import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; -import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import java.util.List; - -@AutoService(AutoConfigurationCustomizerProvider.class) -public class AutoConfiguredDataCapture implements AutoConfigurationCustomizerProvider { - - private static final InMemorySpanExporter inMemorySpanExporter = InMemorySpanExporter.create(); - - /* - Returns the spans which have been exported by the autoconfigured global OpenTelemetry SDK. - */ - public static List getSpans() { - return inMemorySpanExporter.getFinishedSpanItems(); - } - - @Override - public void customize(AutoConfigurationCustomizer autoConfiguration) { - autoConfiguration.addSpanExporterCustomizer( - (spanExporter, config) -> { - // we piggy-back onto the autoconfigured logging exporter for now, - // because that one uses a SimpleSpanProcessor which does not impose a batching delay - if (spanExporter instanceof LoggingSpanExporter) { - inMemorySpanExporter.reset(); - return SpanExporter.composite(inMemorySpanExporter, spanExporter); - } - return spanExporter; - }); - } - - @Override - public int order() { - // There might be other autoconfigurations wrapping SpanExporters, - // which can result in us failing to detect it - // We avoid this by ensuring that we run first - return Integer.MIN_VALUE; - } -} From eddb143439a2224c03d74831304d1ac83ac693c6 Mon Sep 17 00:00:00 2001 From: cirilla-zmh Date: Wed, 28 May 2025 18:58:06 +0800 Subject: [PATCH 11/17] refactor readme.md --- messaging-wrappers/README.md | 129 +++++++++++++++--- .../aliyun-mns-sdk/build.gradle.kts | 6 +- 2 files changed, 115 insertions(+), 20 deletions(-) diff --git a/messaging-wrappers/README.md b/messaging-wrappers/README.md index dd13181f1..19d6a8360 100644 --- a/messaging-wrappers/README.md +++ b/messaging-wrappers/README.md @@ -5,6 +5,23 @@ type of messaging system client. To further ease the burden of instrumentation, predefined implementations for certain messaging systems, helping you seamlessly address the issue of broken traces. +

+Table of Contents + +- [Overview](#overview) +- [Predefined Implementations](#predefined-implementations) +- [Quickstart For Given Implementations](#quickstart-for-given-implementations) + - [\[Given\] Step 1 Add dependencies](#given-step-1-add-dependencies) + - [\[Given\] Step 2 Initializing MessagingWrappers](#given-step-2-initializing-messagingwrappers) + - [\[Given\] Step 3 Wrapping the Process](#given-step-3-wrapping-the-process) +- [Manual Implementation](#manual-implementation) + - [\[Manual\] Step 1 Add dependencies](#manual-step-1-add-dependencies) + - [\[Manual\] Step 2 Initializing MessagingWrappers](#manual-step-2-initializing-messagingwrappers) + - [\[Manual\] Step 3 Wrapping the Process](#manual-step-3-wrapping-the-process) +- [Component Owners](#component-owners) + +
+ ## Overview The primary goal of this API is to simplify the process of adding instrumentation to your messaging @@ -20,15 +37,96 @@ this tool aims to streamline the tracing and monitoring process. | kafka-clients | `[0.11.0.0,)` | process | | aliyun mns-client | `[1.3.0,)` | process | -## Quickstart +## Quickstart For Given Implementations + +This example will demonstrate how to add automatic instrumentation to your Kafka consumer with process wrapper. For +detailed example, please check out [KafkaClientTest](./kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java). -### Step 1 Add dependencies +### [Given] Step 1 Add dependencies To use OpenTelemetry in your project, you need to add the necessary dependencies. Below are the configurations for both Gradle and Maven. #### Gradle +```kotlin +dependencies { + implementation("io.opentelemetry.contrib:opentelemetry-messaging-wrappers-kafka-clients:${latest_version}") +} +``` + +#### Maven + +```xml + + io.opentelemetry.contrib + opentelemetry-messaging-wrappers-kafka-clients + ${latest_version} + +``` + +### [Given] Step 2 Initializing MessagingWrappers + +For `kafka-clients`, we provide pre-implemented wrappers that allow for out-of-the-box integration. We provide +an implementation based on the OpenTelemetry semantic convention by default. + +```java +public class KafkaDemo { + + public static MessagingProcessWrapper createWrapper() { + return KafkaHelper.processWrapperBuilder().build(); + } +} +``` + +### [Given] Step 3 Wrapping the Process + +Once the MessagingWrapper are initialized, you can wrap your message processing logic to ensure that tracing spans are +properly created and propagated. + +**P.S.** Some instrumentations may also [generate process spans](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/supported-libraries.md). +If both are enabled, it might result in duplicate nested process spans. It is recommended to disable one of them. + +```java +public class Demo { + + private static final MessagingProcessWrapper WRAPPER = createWrapper(); + + // please initialize consumer + private Consumer consumer; + + public String consume() { + ConsumerRecords records = consumer.poll(Duration.ofSeconds(5)); + ConsumerRecord record = records.iterator().next(); + + return WRAPPER.doProcess( + KafkaProcessRequest.of(record, groupId, clientId), () -> { + // your processing logic + return "success"; + }); + } + + public void consumeWithoutResult() { + ConsumerRecords records = consumer.poll(Duration.ofSeconds(5)); + ConsumerRecord record = records.iterator().next(); + + WRAPPER.doProcess( + KafkaProcessRequest.of(record, groupId, clientId), () -> { + // your processing logic + }); + } +} +``` + +## Manual Implementation + +You can also build implementations based on the `messaging-wrappers-api` for any messaging system to accommodate your +custom message protocol. For detailed example, please check out [UserDefinedMessageSystemTest](./api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java). + +### [Manual] Step 1 Add dependencies + +#### Gradle + ```kotlin dependencies { implementation("io.opentelemetry.contrib:opentelemetry-messaging-wrappers-api:${latest_version}") @@ -45,7 +143,7 @@ dependencies { ``` -### Step 2 Initializing MessagingWrappers +### [Manual] Step 2 Initializing MessagingWrappers Below is an example of how to initialize a messaging wrapper. @@ -75,21 +173,9 @@ public class MyTextMapGetter implements TextMapGetter ``` For arbitrary messaging systems, you need to manually define `MessagingProcessRequest` and the corresponding `TextMapGetter`. -You can also customize your messaging spans by adding an AttributesExtractor. - -For popular messaging systems, we provide pre-implemented wrappers that allow for out-of-the-box integration. We provide -an implementation based on the OpenTelemetry semantic convention by default. - -```java -public class KafkaDemo { - - public static MessagingProcessWrapper createWrapper() { - return KafkaHelper.processWrapperBuilder().build(); - } -} -``` +You can also customize your messaging spans by adding an `AttributesExtractor`. -### Step 3 Wrapping your process +### [Manual] Step 3 Wrapping the Process Once the MessagingWrapper are initialized, you can wrap your message processing logic to ensure that tracing spans are properly created and propagated. @@ -103,6 +189,13 @@ public class Demo { private static final MessagingProcessWrapper WRAPPER = createWrapper(); public String consume(Message message) { + return WRAPPER.doProcess(new MyMessagingProcessRequest(message), () -> { + // your processing logic + return "success"; + }); + } + + public void consumeWithoutReturn(Message message) { WRAPPER.doProcess(new MyMessagingProcessRequest(message), () -> { // your processing logic }); @@ -110,7 +203,7 @@ public class Demo { } ``` -## Component owners +## Component Owners - [Minghui Zhang](https://github.com/Cirilla-zmh), Alibaba - [Steve Rao](https://github.com/steverao), Alibaba diff --git a/messaging-wrappers/aliyun-mns-sdk/build.gradle.kts b/messaging-wrappers/aliyun-mns-sdk/build.gradle.kts index 19a728420..506e5d03d 100644 --- a/messaging-wrappers/aliyun-mns-sdk/build.gradle.kts +++ b/messaging-wrappers/aliyun-mns-sdk/build.gradle.kts @@ -12,10 +12,12 @@ dependencies { compileOnly("com.aliyun.mns:aliyun-sdk-mns:1.3.0") + testImplementation(project(":messaging-wrappers:testing")) testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") testImplementation("io.opentelemetry:opentelemetry-sdk-trace") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") - testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") - testImplementation("uk.org.webcompere:system-stubs-jupiter:2.0.3") + + testImplementation("com.aliyun.mns:aliyun-sdk-mns:1.3.0") + testImplementation("org.springframework.boot:spring-boot-starter-web:3.0.0") } From 1a914b0ed4d921c73ea5c17b9caaaa0e5fba7f4e Mon Sep 17 00:00:00 2001 From: cirilla-zmh Date: Wed, 28 May 2025 22:57:38 +0800 Subject: [PATCH 12/17] add it for aliyun-mns-sdk --- .../aliyun-mns-sdk/build.gradle.kts | 22 +- .../mns/semconv/MnsProcessRequest.java | 2 +- .../wrappers/mns/AliyunMnsSdkTest.java | 154 ++++++++++++ .../wrappers/mns/MnsTextMapSetter.java | 36 +++ .../wrappers/mns/broker/SmqMockedBroker.java | 231 ++++++++++++++++++ .../wrappers/mns/broker/SmqUtils.java | 73 ++++++ .../broker/ser/ErrorMessageSerializer.java | 28 +++ .../mns/broker/ser/MessageDeserializer.java | 22 ++ .../broker/ser/MessageListDeserializer.java | 38 +++ .../mns/broker/ser/MessageListSerializer.java | 39 +++ .../mns/broker/ser/MessageSerializer.java | 32 +++ .../mns/broker/ser/XMLDeserializer.java | 201 +++++++++++++++ .../mns/broker/ser/XMLSerializer.java | 200 +++++++++++++++ 13 files changed, 1071 insertions(+), 7 deletions(-) create mode 100644 messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/AliyunMnsSdkTest.java create mode 100644 messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsTextMapSetter.java create mode 100644 messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/SmqMockedBroker.java create mode 100644 messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/SmqUtils.java create mode 100644 messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/ErrorMessageSerializer.java create mode 100644 messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/MessageDeserializer.java create mode 100644 messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/MessageListDeserializer.java create mode 100644 messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/MessageListSerializer.java create mode 100644 messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/MessageSerializer.java create mode 100644 messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/XMLDeserializer.java create mode 100644 messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/XMLSerializer.java diff --git a/messaging-wrappers/aliyun-mns-sdk/build.gradle.kts b/messaging-wrappers/aliyun-mns-sdk/build.gradle.kts index 506e5d03d..15396a0a8 100644 --- a/messaging-wrappers/aliyun-mns-sdk/build.gradle.kts +++ b/messaging-wrappers/aliyun-mns-sdk/build.gradle.kts @@ -12,12 +12,22 @@ dependencies { compileOnly("com.aliyun.mns:aliyun-sdk-mns:1.3.0") + testImplementation("com.aliyun.mns:aliyun-sdk-mns:1.3.0") testImplementation(project(":messaging-wrappers:testing")) - testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") - testImplementation("io.opentelemetry:opentelemetry-sdk-trace") - testImplementation("io.opentelemetry:opentelemetry-sdk-testing") - testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") - testImplementation("com.aliyun.mns:aliyun-sdk-mns:1.3.0") - testImplementation("org.springframework.boot:spring-boot-starter-web:3.0.0") + testImplementation("org.springframework.boot:spring-boot-starter-web:2.7.18") + testImplementation("org.springframework.boot:spring-boot-starter-test:2.7.18") +} + +tasks { + withType().configureEach { + jvmArgs("-Dotel.java.global-autoconfigure.enabled=true") + // TODO: According to https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/#message-creation-context-as-parent-of-process-span, + // process span should be the child of receive span. However, we couldn't access the trace context with receive span + // in wrappers, unless we add a generic accessor for that. + jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=false") + jvmArgs("-Dotel.traces.exporter=logging") + jvmArgs("-Dotel.metrics.exporter=logging") + jvmArgs("-Dotel.logs.exporter=logging") + } } diff --git a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MnsProcessRequest.java b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MnsProcessRequest.java index a07ae80f0..bd38c156d 100644 --- a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MnsProcessRequest.java +++ b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MnsProcessRequest.java @@ -68,7 +68,7 @@ public Long getMessageBodySize() { @Nullable @Override public Long getMessageEnvelopeSize() { - return null; + return (long) message.getMessageBodyAsRawBytes().length; } @Nullable diff --git a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/AliyunMnsSdkTest.java b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/AliyunMnsSdkTest.java new file mode 100644 index 000000000..8a5187a18 --- /dev/null +++ b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/AliyunMnsSdkTest.java @@ -0,0 +1,154 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.messaging.wrappers.mns; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; +import static org.assertj.core.api.Assertions.assertThat; + +import com.aliyun.mns.client.CloudAccount; +import com.aliyun.mns.client.CloudQueue; +import com.aliyun.mns.client.MNSClient; +import com.aliyun.mns.common.ServiceHandlingRequiredException; +import com.aliyun.mns.common.http.ClientConfiguration; +import com.aliyun.mns.model.Message; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.contrib.messaging.wrappers.MessagingProcessWrapper; +import io.opentelemetry.contrib.messaging.wrappers.mns.broker.SmqMockedBroker; +import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MnsProcessRequest; +import io.opentelemetry.contrib.messaging.wrappers.testing.AbstractBaseTest; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; + +@SuppressWarnings("OtelInternalJavadoc") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@SpringBootTest( + classes = {SmqMockedBroker.class}, + webEnvironment = WebEnvironment.RANDOM_PORT) +public class AliyunMnsSdkTest extends AbstractBaseTest { + + private static final String TEST_ENDPOINT = "http://test.mns.cn-hangzhou.aliyuncs.com"; + + private static final String QUEUE = "TEST_QUEUE"; + + private static final String MESSAGE_BODY = "Hello OpenTelemetry"; + + @LocalServerPort private int testApplicationPort; // port at which the spring app is running + + private MNSClient mnsClient; + + private CloudQueue queue; + + private OpenTelemetry otel; + + private Tracer tracer; + + private MessagingProcessWrapper wrapper; + + @BeforeAll + void setupClass() { + otel = GlobalOpenTelemetry.get(); + tracer = otel.getTracer("test-tracer", "1.0.0"); + wrapper = MnsHelper.processWrapperBuilder().openTelemetry(otel).build(); + + ClientConfiguration configuration = new ClientConfiguration(); + configuration.setProxyHost("127.0.0.1"); + configuration.setProxyPort(testApplicationPort); + + CloudAccount account = new CloudAccount("test-ak", "test-sk", TEST_ENDPOINT, configuration); + + mnsClient = account.getMNSClient(); + queue = mnsClient.getQueueRef(QUEUE); + } + + @Test + void testSendAndConsume() throws ServiceHandlingRequiredException { + sendWithParent(); + + consumeWithChild(); + + assertTraces(); + } + + public void sendWithParent() { + // mock a send span + Span parent = tracer.spanBuilder("publish " + QUEUE).setSpanKind(SpanKind.PRODUCER).startSpan(); + + try (Scope scope = parent.makeCurrent()) { + Message message = new Message(MESSAGE_BODY); + otel.getPropagators() + .getTextMapPropagator() + .inject(Context.current(), message, MnsTextMapSetter.INSTANCE); + queue.putMessage(message); + } + + parent.end(); + } + + public void consumeWithChild() throws ServiceHandlingRequiredException { + // check that the message was received + Message message = null; + for (int i = 0; i < 3; i++) { + message = queue.popMessage(3); + if (message != null) { + break; + } + } + + assertThat(message).isNotNull(); + + wrapper.doProcess( + MnsProcessRequest.of(message, QUEUE), + () -> { + tracer.spanBuilder("process child").startSpan().end(); + }); + } + + /** + * Copied from testing-common. + */ + @SuppressWarnings("deprecation") // using deprecated semconv + public void assertTraces() { + waitAndAssertTraces( + sortByRootSpanName("parent", "producer callback"), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + // No need to verify the attribute here because it is generated by + // instrumentation library. + span.hasName("publish " + QUEUE).hasKind(SpanKind.PRODUCER).hasNoParent(), + span -> + span.hasName("process " + QUEUE) + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(MESSAGING_SYSTEM, "guava-eventbus"), + equalTo(MESSAGING_DESTINATION_NAME, QUEUE), + equalTo( + MESSAGING_MESSAGE_BODY_SIZE, + MESSAGE_BODY.getBytes(StandardCharsets.UTF_8).length), + equalTo(MESSAGING_OPERATION, "process")), + span -> + span.hasName("process child") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(1)))); + } +} diff --git a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsTextMapSetter.java b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsTextMapSetter.java new file mode 100644 index 000000000..4f5ac86f1 --- /dev/null +++ b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsTextMapSetter.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.messaging.wrappers.mns; + +import com.aliyun.mns.model.Message; +import com.aliyun.mns.model.MessagePropertyValue; +import com.aliyun.mns.model.MessageSystemPropertyName; +import com.aliyun.mns.model.MessageSystemPropertyValue; +import com.aliyun.mns.model.SystemPropertyType; +import io.opentelemetry.context.propagation.TextMapSetter; +import javax.annotation.Nullable; + +public enum MnsTextMapSetter implements TextMapSetter { + INSTANCE; + + /** + * MNS message trails currently only support the W3C protocol; other protocol headers should be + * injected into userProperties. + */ + @Override + public void set(@Nullable Message message, String key, String value) { + if (message == null) { + return; + } + MessageSystemPropertyName systemPropertyName = MnsHelper.convert2SystemPropertyName(key); + if (systemPropertyName != null) { + message.putSystemProperty( + systemPropertyName, new MessageSystemPropertyValue(SystemPropertyType.STRING, value)); + } else { + message.getUserProperties().put(key, new MessagePropertyValue(value)); + } + } +} diff --git a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/SmqMockedBroker.java b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/SmqMockedBroker.java new file mode 100644 index 000000000..03197eca8 --- /dev/null +++ b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/SmqMockedBroker.java @@ -0,0 +1,231 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.messaging.wrappers.mns.broker; + +import static com.aliyun.mns.common.MNSConstants.DEFAULT_CHARSET; +import static com.aliyun.mns.common.MNSConstants.X_HEADER_MNS_REQUEST_ID; +import static com.aliyun.mns.common.utils.HttpHeaders.CONTENT_TYPE; +import static io.opentelemetry.contrib.messaging.wrappers.mns.broker.SmqUtils.calculateMessageBodyMD5; +import static io.opentelemetry.contrib.messaging.wrappers.mns.broker.SmqUtils.createErrorMessage; +import static io.opentelemetry.contrib.messaging.wrappers.mns.broker.SmqUtils.generateRandomBase64String; +import static io.opentelemetry.contrib.messaging.wrappers.mns.broker.SmqUtils.generateRandomMessageId; +import static io.opentelemetry.contrib.messaging.wrappers.mns.broker.SmqUtils.generateRandomRequestId; +import static io.opentelemetry.contrib.messaging.wrappers.mns.broker.SmqUtils.inputStreamToByteArray; + +import com.aliyun.mns.model.ErrorMessage; +import com.aliyun.mns.model.Message; +import io.opentelemetry.contrib.messaging.wrappers.mns.broker.ser.ErrorMessageSerializer; +import io.opentelemetry.contrib.messaging.wrappers.mns.broker.ser.MessageDeserializer; +import io.opentelemetry.contrib.messaging.wrappers.mns.broker.ser.MessageListDeserializer; +import io.opentelemetry.contrib.messaging.wrappers.mns.broker.ser.MessageListSerializer; +import io.opentelemetry.contrib.messaging.wrappers.mns.broker.ser.MessageSerializer; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Scope; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +@SpringBootApplication +@SuppressWarnings({"JavaUtilDate", "LockNotBeforeTry", "SystemOut"}) +public class SmqMockedBroker { + + public static void main(String[] args) { + System.out.println(System.getProperty("java.class.path")); + SpringApplication.run(SmqMockedBroker.class, args); + } + + @Controller + @Scope("singleton") + public static class BrokerController { + + Lock queuesLock = new ReentrantLock(); + + Map> queues = new HashMap<>(); + + @PostMapping( + value = "/queues/{queueName}/messages", + consumes = MediaType.TEXT_XML_VALUE, + produces = MediaType.TEXT_XML_VALUE) + public ResponseEntity serveSendMessage( + @PathVariable String queueName, @RequestBody byte[] requestBody) throws Exception { + String requestId = generateRandomRequestId(); + + boolean isBatch = true; + + MessageListDeserializer listDeserializer = new MessageListDeserializer(); + List msgList = listDeserializer.deserialize(new ByteArrayInputStream(requestBody)); + + if (msgList == null) { + isBatch = false; + MessageDeserializer deserializer = new MessageDeserializer(); + Message msg = deserializer.deserialize(new ByteArrayInputStream(requestBody)); + msgList = Collections.singletonList(msg); + } + + List responses = new ArrayList<>(msgList.size()); + + for (Message msg : msgList) { + String mid = generateRandomMessageId(); + msg.setMessageId(mid); + msg.setEnqueueTime(new Date()); + msg.setPriority(8); + queuesLock.lock(); + Deque queue = queues.computeIfAbsent(queueName, (k) -> new ArrayDeque<>()); + queue.offerFirst(msg); + queuesLock.unlock(); + + Message response = new Message(); + response.setMessageId(mid); + response.setMessageBodyMD5(calculateMessageBodyMD5(msg.getMessageBody())); + responses.add(response); + } + + if (isBatch) { + MessageListSerializer listSerializer = new MessageListSerializer(); + InputStream stream = listSerializer.serialize(responses, DEFAULT_CHARSET); + return ResponseEntity.status(HttpStatus.CREATED) + .header(CONTENT_TYPE, "text/xml;charset=UTF-8") + .header(X_HEADER_MNS_REQUEST_ID, requestId) + .body(inputStreamToByteArray(stream)); + } else { + MessageSerializer serializer = new MessageSerializer(); + InputStream stream = serializer.serialize(responses.get(0), DEFAULT_CHARSET); + return ResponseEntity.status(HttpStatus.CREATED) + .header(CONTENT_TYPE, "text/xml;charset=UTF-8") + .header(X_HEADER_MNS_REQUEST_ID, requestId) + .body(inputStreamToByteArray(stream)); + } + } + + @PostMapping( + value = "/topics/{topicName}/messages", + consumes = MediaType.TEXT_XML_VALUE, + produces = MediaType.TEXT_XML_VALUE) + public ResponseEntity servePublishMessage( + @PathVariable String topicName, @RequestBody byte[] requestBody) throws Exception { + return serveSendMessage(topicName.replaceAll("topic", "queue"), requestBody); + } + + @GetMapping( + value = "/queues/{queueName}/messages", + consumes = MediaType.TEXT_XML_VALUE, + produces = MediaType.TEXT_XML_VALUE) + public ResponseEntity serveReceiveMessage( + @PathVariable String queueName, + @RequestParam(value = "numOfMessages", required = false, defaultValue = "-1") + int numOfMessages, + @RequestParam(value = "waitseconds", defaultValue = "10") int waitSec) + throws Exception { + String requestId = generateRandomRequestId(); + long timeout = System.currentTimeMillis() + waitSec * 1000L; + + queuesLock.lock(); + Deque queue = queues.computeIfAbsent(queueName, (k) -> new ArrayDeque<>()); + queuesLock.unlock(); + + List msgList = null; + while (System.currentTimeMillis() < timeout) { + if (!queue.isEmpty()) { + queuesLock.lock(); + if (!queue.isEmpty()) { + if (numOfMessages == -1) { + // receive single message + Message msg = queue.pollLast(); + msg.setFirstDequeueTime(new Date()); + msg.setNextVisibleTime(new Date(System.currentTimeMillis() + 24 * 3600 * 1000L)); + msg.setDequeueCount((msg.getDequeueCount() == null ? 0 : msg.getDequeueCount()) + 1); + msg.setReceiptHandle(msg.getPriority() + "-" + generateRandomBase64String(32)); + msgList = Collections.singletonList(msg); + } else { + msgList = new ArrayList<>(); + for (int i = 0; i < numOfMessages; i++) { + if (queue.isEmpty()) { + break; + } + Message msg = queue.pollLast(); + msg.setFirstDequeueTime(new Date()); + msg.setNextVisibleTime(new Date(System.currentTimeMillis() + 24 * 3600 * 1000L)); + msg.setDequeueCount( + (msg.getDequeueCount() == null ? 0 : msg.getDequeueCount()) + 1); + msg.setReceiptHandle(msg.getPriority() + "-" + generateRandomBase64String(32)); + msgList.add(msg); + } + } + queuesLock.unlock(); + break; + } + queuesLock.unlock(); + } + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + break; + } + } + + if (msgList != null) { + if (numOfMessages == -1) { + // receive single message + Message msg = msgList.get(0); + MessageSerializer serializer = new MessageSerializer(); + return ResponseEntity.status(HttpStatus.OK) + .header(CONTENT_TYPE, "text/xml;charset=UTF-8") + .header(X_HEADER_MNS_REQUEST_ID, requestId) + .body(inputStreamToByteArray(serializer.serialize(msg, DEFAULT_CHARSET))); + } else { + MessageListSerializer listSerializer = new MessageListSerializer(); + return ResponseEntity.status(HttpStatus.OK) + .header(CONTENT_TYPE, "text/xml;charset=UTF-8") + .header(X_HEADER_MNS_REQUEST_ID, requestId) + .body(inputStreamToByteArray(listSerializer.serialize(msgList, DEFAULT_CHARSET))); + } + } else { + ErrorMessage errorMessage = createErrorMessage(requestId); + ErrorMessageSerializer serializer = new ErrorMessageSerializer(); + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .header(CONTENT_TYPE, "text/xml;charset=UTF-8") + .header(X_HEADER_MNS_REQUEST_ID, requestId) + .body(inputStreamToByteArray(serializer.serialize(errorMessage, DEFAULT_CHARSET))); + } + } + + @DeleteMapping(value = "/queues/{queueName}/messages", consumes = MediaType.TEXT_XML_VALUE) + public ResponseEntity serveDeleteMessage( + @PathVariable String queueName, + @RequestParam(value = "ReceiptHandle", defaultValue = "") String receiptHandle, + @RequestBody(required = false) byte[] requestBody) + throws Exception { + String requestId = generateRandomRequestId(); + return ResponseEntity.status(HttpStatus.NO_CONTENT) + .header(X_HEADER_MNS_REQUEST_ID, requestId) + .build(); + } + } + + private SmqMockedBroker() { + } +} diff --git a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/SmqUtils.java b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/SmqUtils.java new file mode 100644 index 000000000..95887db73 --- /dev/null +++ b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/SmqUtils.java @@ -0,0 +1,73 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.messaging.wrappers.mns.broker; + +import com.aliyun.mns.model.ErrorMessage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.Locale; +import java.util.Random; +import java.util.UUID; + +public final class SmqUtils { + + public static byte[] inputStreamToByteArray(InputStream inputStream) throws IOException { + try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + byteArrayOutputStream.write(buffer, 0, bytesRead); + } + return byteArrayOutputStream.toByteArray(); + } + } + + public static String calculateMessageBodyMD5(String messageBody) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] hashBytes = md.digest(messageBody.getBytes(StandardCharsets.UTF_8)); + return bytesToHex(hashBytes).toUpperCase(Locale.ROOT); + } + + public static String generateRandomMessageId() { + return UUID.randomUUID().toString().replaceAll("-", "").toUpperCase(Locale.ROOT); + } + + public static String generateRandomRequestId() { + return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 24).toUpperCase(Locale.ROOT); + } + + public static ErrorMessage createErrorMessage(String requestId) { + ErrorMessage errorMessage = new ErrorMessage(); + errorMessage.Code = "MessageNotExist"; + errorMessage.Message = "Message not exist."; + errorMessage.RequestId = requestId; + errorMessage.HostId = "http://test.mns.cn-hangzhou.aliyuncs.com"; + return errorMessage; + } + + public static String generateRandomBase64String(int length) { + byte[] randomBytes = new byte[length]; + new Random().nextBytes(randomBytes); + + return Base64.getEncoder().encodeToString(randomBytes); + } + + private static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format(Locale.ROOT, "%02X", b)); + } + return sb.toString(); + } + + private SmqUtils() { + } +} diff --git a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/ErrorMessageSerializer.java b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/ErrorMessageSerializer.java new file mode 100644 index 000000000..bfea33c2e --- /dev/null +++ b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/ErrorMessageSerializer.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.messaging.wrappers.mns.broker.ser; + +import com.aliyun.mns.model.ErrorMessage; +import com.aliyun.mns.model.serialize.XmlUtil; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class ErrorMessageSerializer extends XMLSerializer { + + @Override + public InputStream serialize(ErrorMessage msg, String encoding) throws Exception { + Document doc = getDocumentBuilder().newDocument(); + + Element root = serializeError(doc, msg); + doc.appendChild(root); + + String xml = XmlUtil.xmlNodeToString(doc, encoding); + + return new ByteArrayInputStream(xml.getBytes(encoding)); + } +} diff --git a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/MessageDeserializer.java b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/MessageDeserializer.java new file mode 100644 index 000000000..e3fba1bdc --- /dev/null +++ b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/MessageDeserializer.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.messaging.wrappers.mns.broker.ser; + +import com.aliyun.mns.model.Message; +import java.io.InputStream; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class MessageDeserializer extends XMLDeserializer { + + @Override + public Message deserialize(InputStream stream) throws Exception { + Document doc = getDocumentBuilder().parse(stream); + + Element root = doc.getDocumentElement(); + return (Message) parseMessage(root); + } +} diff --git a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/MessageListDeserializer.java b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/MessageListDeserializer.java new file mode 100644 index 000000000..be3f38b2f --- /dev/null +++ b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/MessageListDeserializer.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.messaging.wrappers.mns.broker.ser; + +import static com.aliyun.mns.common.MNSConstants.MESSAGE_TAG; + +import com.aliyun.mns.model.Message; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +public class MessageListDeserializer extends XMLDeserializer> { + @Override + public List deserialize(InputStream stream) throws Exception { + Document doc = getDocumentBuilder().parse(stream); + return deserialize(doc); + } + + public List deserialize(Document doc) { + NodeList list = doc.getElementsByTagName(MESSAGE_TAG); + if (list != null && list.getLength() > 0) { + List results = new ArrayList(); + + for (int i = 0; i < list.getLength(); i++) { + Message msg = (Message) parseMessage((Element) list.item(i)); + results.add(msg); + } + return results; + } + return new ArrayList<>(); + } +} diff --git a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/MessageListSerializer.java b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/MessageListSerializer.java new file mode 100644 index 000000000..8b08babb8 --- /dev/null +++ b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/MessageListSerializer.java @@ -0,0 +1,39 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.messaging.wrappers.mns.broker.ser; + +import static com.aliyun.mns.common.MNSConstants.DEFAULT_XML_NAMESPACE; +import static com.aliyun.mns.common.MNSConstants.MESSAGE_LIST_TAG; + +import com.aliyun.mns.model.Message; +import com.aliyun.mns.model.serialize.XmlUtil; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.List; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class MessageListSerializer extends XMLSerializer> { + + @Override + public InputStream serialize(List msgs, String encoding) throws Exception { + Document doc = getDocumentBuilder().newDocument(); + + Element messages = doc.createElementNS(DEFAULT_XML_NAMESPACE, MESSAGE_LIST_TAG); + + doc.appendChild(messages); + + if (msgs != null) { + for (Message msg : msgs) { + Element root = serializeMessage(doc, msg); + messages.appendChild(root); + } + } + String xml = XmlUtil.xmlNodeToString(doc, encoding); + + return new ByteArrayInputStream(xml.getBytes(encoding)); + } +} diff --git a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/MessageSerializer.java b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/MessageSerializer.java new file mode 100644 index 000000000..935618086 --- /dev/null +++ b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/MessageSerializer.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.messaging.wrappers.mns.broker.ser; + +import com.aliyun.mns.model.Message; +import com.aliyun.mns.model.serialize.XmlUtil; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class MessageSerializer extends XMLSerializer { + + public MessageSerializer() { + super(); + } + + @Override + public InputStream serialize(Message msg, String encoding) throws Exception { + Document doc = getDocumentBuilder().newDocument(); + + Element root = serializeMessage(doc, msg); + doc.appendChild(root); + + String xml = XmlUtil.xmlNodeToString(doc, encoding); + + return new ByteArrayInputStream(xml.getBytes(encoding)); + } +} diff --git a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/XMLDeserializer.java b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/XMLDeserializer.java new file mode 100644 index 000000000..72f4117de --- /dev/null +++ b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/XMLDeserializer.java @@ -0,0 +1,201 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.messaging.wrappers.mns.broker.ser; + +import static com.aliyun.mns.common.MNSConstants.DEQUEUE_COUNT_TAG; +import static com.aliyun.mns.common.MNSConstants.ENQUEUE_TIME_TAG; +import static com.aliyun.mns.common.MNSConstants.FIRST_DEQUEUE_TIME_TAG; +import static com.aliyun.mns.common.MNSConstants.MESSAGE_BODY_MD5_TAG; +import static com.aliyun.mns.common.MNSConstants.MESSAGE_BODY_TAG; +import static com.aliyun.mns.common.MNSConstants.MESSAGE_ERRORCODE_TAG; +import static com.aliyun.mns.common.MNSConstants.MESSAGE_ERRORMESSAGE_TAG; +import static com.aliyun.mns.common.MNSConstants.MESSAGE_ID_TAG; +import static com.aliyun.mns.common.MNSConstants.MESSAGE_PROPERTY_TAG; +import static com.aliyun.mns.common.MNSConstants.MESSAGE_SYSTEM_PROPERTY_TAG; +import static com.aliyun.mns.common.MNSConstants.NEXT_VISIBLE_TIME_TAG; +import static com.aliyun.mns.common.MNSConstants.PRIORITY_TAG; +import static com.aliyun.mns.common.MNSConstants.PROPERTY_NAME_TAG; +import static com.aliyun.mns.common.MNSConstants.PROPERTY_TYPE_TAG; +import static com.aliyun.mns.common.MNSConstants.PROPERTY_VALUE_TAG; +import static com.aliyun.mns.common.MNSConstants.RECEIPT_HANDLE_TAG; +import static com.aliyun.mns.common.MNSConstants.SYSTEM_PROPERTIES_TAG; +import static com.aliyun.mns.common.MNSConstants.USER_PROPERTIES_TAG; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.aliyun.mns.model.BaseMessage; +import com.aliyun.mns.model.ErrorMessageResult; +import com.aliyun.mns.model.Message; +import com.aliyun.mns.model.MessagePropertyValue; +import com.aliyun.mns.model.MessageSystemPropertyName; +import com.aliyun.mns.model.MessageSystemPropertyValue; +import com.aliyun.mns.model.PropertyType; +import com.aliyun.mns.model.SystemPropertyType; +import com.aliyun.mns.model.serialize.BaseXMLSerializer; +import com.aliyun.mns.model.serialize.Deserializer; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.codec.binary.Base64; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +@SuppressWarnings("JavaUtilDate") +public abstract class XMLDeserializer extends BaseXMLSerializer implements Deserializer { + + protected String safeGetElementContent(Element root, String tagName, String defaultValue) { + NodeList nodes = root.getElementsByTagName(tagName); + if (nodes != null) { + Node node = nodes.item(0); + if (node == null) { + return defaultValue; + } else { + return node.getTextContent(); + } + } + return defaultValue; + } + + protected Element safeGetElement(Element root, String tagName) { + NodeList nodes = root.getElementsByTagName(tagName); + if (nodes != null) { + Node node = nodes.item(0); + if (node == null) { + return null; + } else { + return (Element) node; + } + } + return null; + } + + protected List safeGetElements(Element parent, String tagName) { + NodeList nodeList = parent.getElementsByTagName(tagName); + List elements = new ArrayList(); + for (int i = 0; i < nodeList.getLength(); i++) { + Node node = nodeList.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + elements.add((Element) node); + } + } + return elements; + } + + protected ErrorMessageResult parseErrorMessageResult(Element root) { + ErrorMessageResult result = new ErrorMessageResult(); + String errorCode = safeGetElementContent(root, MESSAGE_ERRORCODE_TAG, null); + result.setErrorCode(errorCode); + + String errorMessage = safeGetElementContent(root, MESSAGE_ERRORMESSAGE_TAG, null); + result.setErrorMessage(errorMessage); + return result; + } + + protected BaseMessage parseMessage(Element root) { + Message message = new Message(); + + String messageId = safeGetElementContent(root, MESSAGE_ID_TAG, null); + if (messageId != null) { + message.setMessageId(messageId); + } + + String messageBody = safeGetElementContent(root, MESSAGE_BODY_TAG, null); + if (messageBody != null) { + message.setMessageBody(messageBody, Message.MessageBodyType.RAW_STRING); + } + + String messageBodyMD5 = safeGetElementContent(root, MESSAGE_BODY_MD5_TAG, null); + message.setMessageBodyMD5(messageBodyMD5); + + String receiptHandle = safeGetElementContent(root, RECEIPT_HANDLE_TAG, null); + message.setReceiptHandle(receiptHandle); + + String enqueueTime = safeGetElementContent(root, ENQUEUE_TIME_TAG, null); + if (enqueueTime != null) { + message.setEnqueueTime(new Date(Long.parseLong(enqueueTime))); + } + + String nextVisibleTime = safeGetElementContent(root, NEXT_VISIBLE_TIME_TAG, null); + if (nextVisibleTime != null) { + message.setNextVisibleTime(new Date(Long.parseLong(nextVisibleTime))); + } + + String firstDequeueTime = safeGetElementContent(root, FIRST_DEQUEUE_TIME_TAG, null); + if (firstDequeueTime != null) { + message.setFirstDequeueTime(new Date(Long.parseLong(firstDequeueTime))); + } + + String dequeueCount = safeGetElementContent(root, DEQUEUE_COUNT_TAG, null); + if (dequeueCount != null) { + message.setDequeueCount(Integer.parseInt(dequeueCount)); + } + + String priority = safeGetElementContent(root, PRIORITY_TAG, null); + if (priority != null) { + message.setPriority(Integer.parseInt(priority)); + } + + // 解析 userProperties + safeAddPropertiesToMessage(root, message); + + // 解析 systemProperties + safeAddSystemPropertiesToMessage(root, message); + + return message; + } + + protected void safeAddPropertiesToMessage(Element root, Message message) { + Element userPropertiesElement = safeGetElement(root, USER_PROPERTIES_TAG); + if (userPropertiesElement != null) { + Map userProperties = message.getUserProperties(); + if (userProperties == null) { + userProperties = new HashMap(); + message.setUserProperties(userProperties); + } + + for (Element propertyValueElement : + safeGetElements(userPropertiesElement, MESSAGE_PROPERTY_TAG)) { + String name = safeGetElementContent(propertyValueElement, PROPERTY_NAME_TAG, null); + String value = safeGetElementContent(propertyValueElement, PROPERTY_VALUE_TAG, null); + String type = safeGetElementContent(propertyValueElement, PROPERTY_TYPE_TAG, null); + + if (name != null && value != null && type != null) { + PropertyType typeEnum = PropertyType.valueOf(type); + // 如果是二进制类型,需要base64解码 + if (typeEnum == PropertyType.BINARY) { + byte[] decodedBytes = Base64.decodeBase64(value); + value = new String(decodedBytes, UTF_8); + } + MessagePropertyValue propertyValue = + new MessagePropertyValue(PropertyType.valueOf(type), value); + userProperties.put(name, propertyValue); + } + } + } + } + + protected void safeAddSystemPropertiesToMessage(Element root, Message message) { + Element systemPropertiesElement = safeGetElement(root, SYSTEM_PROPERTIES_TAG); + if (systemPropertiesElement != null) { + for (Element propertyValueElement : + safeGetElements(systemPropertiesElement, MESSAGE_SYSTEM_PROPERTY_TAG)) { + String name = safeGetElementContent(propertyValueElement, PROPERTY_NAME_TAG, null); + String value = safeGetElementContent(propertyValueElement, PROPERTY_VALUE_TAG, null); + String type = safeGetElementContent(propertyValueElement, PROPERTY_TYPE_TAG, null); + + if (name != null && value != null && type != null) { + SystemPropertyType systemPropertyType = SystemPropertyType.valueOf(type); + MessageSystemPropertyValue propertyValue = + new MessageSystemPropertyValue(systemPropertyType, value); + MessageSystemPropertyName propertyName = MessageSystemPropertyName.getByValue(name); + message.putSystemProperty(propertyName, propertyValue); + } + } + } + } +} diff --git a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/XMLSerializer.java b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/XMLSerializer.java new file mode 100644 index 000000000..1dfd8d99a --- /dev/null +++ b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/ser/XMLSerializer.java @@ -0,0 +1,200 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.messaging.wrappers.mns.broker.ser; + +import static com.aliyun.mns.common.MNSConstants.DEFAULT_XML_NAMESPACE; +import static com.aliyun.mns.common.MNSConstants.DELAY_SECONDS_TAG; +import static com.aliyun.mns.common.MNSConstants.DEQUEUE_COUNT_TAG; +import static com.aliyun.mns.common.MNSConstants.ENQUEUE_TIME_TAG; +import static com.aliyun.mns.common.MNSConstants.ERROR_CODE_TAG; +import static com.aliyun.mns.common.MNSConstants.ERROR_HOST_ID_TAG; +import static com.aliyun.mns.common.MNSConstants.ERROR_REQUEST_ID_TAG; +import static com.aliyun.mns.common.MNSConstants.ERROR_TAG; +import static com.aliyun.mns.common.MNSConstants.FIRST_DEQUEUE_TIME_TAG; +import static com.aliyun.mns.common.MNSConstants.MESSAGE_BODY_MD5_TAG; +import static com.aliyun.mns.common.MNSConstants.MESSAGE_BODY_TAG; +import static com.aliyun.mns.common.MNSConstants.MESSAGE_ID_TAG; +import static com.aliyun.mns.common.MNSConstants.MESSAGE_PROPERTY_TAG; +import static com.aliyun.mns.common.MNSConstants.MESSAGE_SYSTEM_PROPERTY_TAG; +import static com.aliyun.mns.common.MNSConstants.MESSAGE_TAG; +import static com.aliyun.mns.common.MNSConstants.NEXT_VISIBLE_TIME_TAG; +import static com.aliyun.mns.common.MNSConstants.PRIORITY_TAG; +import static com.aliyun.mns.common.MNSConstants.PROPERTY_NAME_TAG; +import static com.aliyun.mns.common.MNSConstants.PROPERTY_TYPE_TAG; +import static com.aliyun.mns.common.MNSConstants.PROPERTY_VALUE_TAG; +import static com.aliyun.mns.common.MNSConstants.RECEIPT_HANDLE_TAG; +import static com.aliyun.mns.common.MNSConstants.SYSTEM_PROPERTIES_TAG; +import static com.aliyun.mns.common.MNSConstants.USER_PROPERTIES_TAG; + +import com.aliyun.mns.model.AbstractMessagePropertyValue; +import com.aliyun.mns.model.ErrorMessage; +import com.aliyun.mns.model.Message; +import com.aliyun.mns.model.serialize.BaseXMLSerializer; +import com.aliyun.mns.model.serialize.Serializer; +import java.util.Date; +import java.util.Map; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +@SuppressWarnings("JavaUtilDate") +public abstract class XMLSerializer extends BaseXMLSerializer implements Serializer { + + public Element safeCreateContentElement( + Document doc, String tagName, Object value, String defaultValue) { + if (value == null && defaultValue == null) { + return null; + } + + Element node = doc.createElement(tagName); + if (value instanceof Date) { + node.setTextContent(String.valueOf(((Date) value).getTime())); + } else if (value != null) { + node.setTextContent(value.toString()); + } else { + node.setTextContent(defaultValue); + } + return node; + } + + public Element serializeError(Document doc, ErrorMessage msg) { + Element root = doc.createElementNS(DEFAULT_XML_NAMESPACE, ERROR_TAG); + + Element node = safeCreateContentElement(doc, ERROR_CODE_TAG, msg.Code, "MessageNotExist"); + + if (node != null) { + root.appendChild(node); + } + + node = safeCreateContentElement(doc, MESSAGE_TAG, msg.Message, null); + + if (node != null) { + root.appendChild(node); + } + + node = safeCreateContentElement(doc, ERROR_REQUEST_ID_TAG, msg.RequestId, null); + + if (node != null) { + root.appendChild(node); + } + + node = safeCreateContentElement(doc, ERROR_HOST_ID_TAG, msg.HostId, null); + + if (node != null) { + root.appendChild(node); + } + + return root; + } + + public Element serializeMessage(Document doc, Message msg) { + Element root = doc.createElementNS(DEFAULT_XML_NAMESPACE, MESSAGE_TAG); + + Element node = safeCreateContentElement(doc, MESSAGE_ID_TAG, msg.getMessageId(), null); + + if (node != null) { + root.appendChild(node); + } + + node = safeCreateContentElement(doc, MESSAGE_BODY_TAG, msg.getOriginalMessageBody(), ""); + + if (node != null) { + root.appendChild(node); + } + + node = safeCreateContentElement(doc, MESSAGE_BODY_MD5_TAG, msg.getMessageBodyMD5(), null); + + if (node != null) { + root.appendChild(node); + } + + node = safeCreateContentElement(doc, DELAY_SECONDS_TAG, msg.getDelaySeconds(), null); + if (node != null) { + root.appendChild(node); + } + + node = safeCreateContentElement(doc, PRIORITY_TAG, msg.getPriority(), null); + if (node != null) { + root.appendChild(node); + } + + node = + safeCreatePropertiesNode( + doc, msg.getUserProperties(), USER_PROPERTIES_TAG, MESSAGE_PROPERTY_TAG); + if (node != null) { + root.appendChild(node); + } + + node = + safeCreatePropertiesNode( + doc, msg.getSystemProperties(), SYSTEM_PROPERTIES_TAG, MESSAGE_SYSTEM_PROPERTY_TAG); + if (node != null) { + root.appendChild(node); + } + + node = safeCreateContentElement(doc, RECEIPT_HANDLE_TAG, msg.getReceiptHandle(), null); + if (node != null) { + root.appendChild(node); + } + + node = safeCreateContentElement(doc, ENQUEUE_TIME_TAG, msg.getEnqueueTime(), null); + if (node != null) { + root.appendChild(node); + } + + node = safeCreateContentElement(doc, NEXT_VISIBLE_TIME_TAG, msg.getNextVisibleTime(), null); + if (node != null) { + root.appendChild(node); + } + + node = safeCreateContentElement(doc, FIRST_DEQUEUE_TIME_TAG, msg.getFirstDequeueTime(), null); + if (node != null) { + root.appendChild(node); + } + + node = safeCreateContentElement(doc, DEQUEUE_COUNT_TAG, msg.getDequeueCount(), null); + if (node != null) { + root.appendChild(node); + } + + return root; + } + + public Element safeCreatePropertiesNode( + Document doc, + Map map, + String nodeName, + String propertyNodeName) { + if (map == null || map.isEmpty()) { + return null; + } + Element propertiesNode = doc.createElement(nodeName); + for (Map.Entry entry : map.entrySet()) { + Element propNode = doc.createElement(propertyNodeName); + + Element nameNode = safeCreateContentElement(doc, PROPERTY_NAME_TAG, entry.getKey(), null); + if (nameNode != null) { + propNode.appendChild(nameNode); + } + + Element valueNode = + safeCreateContentElement( + doc, PROPERTY_VALUE_TAG, entry.getValue().getStringValueByType(), null); + if (valueNode != null) { + propNode.appendChild(valueNode); + } + + Element typeNode = + safeCreateContentElement( + doc, PROPERTY_TYPE_TAG, entry.getValue().getDataTypeString(), null); + if (typeNode != null) { + propNode.appendChild(typeNode); + } + + propertiesNode.appendChild(propNode); + } + return propertiesNode; + } +} From 811d197754f5fe7ce37877bcc64702e7c356c702 Mon Sep 17 00:00:00 2001 From: cirilla-zmh Date: Wed, 28 May 2025 23:31:21 +0800 Subject: [PATCH 13/17] fix it & apply spotless --- messaging-wrappers/README.md | 9 ++++----- messaging-wrappers/aliyun-mns-sdk/build.gradle.kts | 7 +++++++ .../messaging/wrappers/mns/AliyunMnsSdkTest.java | 8 +++++++- .../wrappers/mns/broker/SmqMockedBroker.java | 13 +++++++------ .../messaging/wrappers/mns/broker/SmqUtils.java | 9 ++++++--- messaging-wrappers/kafka-clients/build.gradle.kts | 6 ++++++ .../wrappers/kafka/KafkaClientBaseTest.java | 2 +- .../messaging/wrappers/kafka/KafkaClientTest.java | 2 +- 8 files changed, 39 insertions(+), 17 deletions(-) diff --git a/messaging-wrappers/README.md b/messaging-wrappers/README.md index 19d6a8360..8b6725413 100644 --- a/messaging-wrappers/README.md +++ b/messaging-wrappers/README.md @@ -47,7 +47,7 @@ detailed example, please check out [KafkaClientTest](./kafka-clients/src/test/ja To use OpenTelemetry in your project, you need to add the necessary dependencies. Below are the configurations for both Gradle and Maven. -#### Gradle +### [Given] Gradle ```kotlin dependencies { @@ -55,7 +55,7 @@ dependencies { } ``` -#### Maven +### [Given] Maven ```xml @@ -125,7 +125,7 @@ custom message protocol. For detailed example, please check out [UserDefinedMess ### [Manual] Step 1 Add dependencies -#### Gradle +#### [Manual] Gradle ```kotlin dependencies { @@ -133,7 +133,7 @@ dependencies { } ``` -#### Maven +#### [Manual] Maven ```xml @@ -209,4 +209,3 @@ public class Demo { - [Steve Rao](https://github.com/steverao), Alibaba Learn more about component owners in [component_owners.yml](../.github/component_owners.yml). - diff --git a/messaging-wrappers/aliyun-mns-sdk/build.gradle.kts b/messaging-wrappers/aliyun-mns-sdk/build.gradle.kts index 15396a0a8..286662c26 100644 --- a/messaging-wrappers/aliyun-mns-sdk/build.gradle.kts +++ b/messaging-wrappers/aliyun-mns-sdk/build.gradle.kts @@ -31,3 +31,10 @@ tasks { jvmArgs("-Dotel.logs.exporter=logging") } } + +configurations.testRuntimeClasspath { + resolutionStrategy { + force("ch.qos.logback:logback-classic:1.2.12") + force("org.slf4j:slf4j-api:1.7.35") + } +} diff --git a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/AliyunMnsSdkTest.java b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/AliyunMnsSdkTest.java index 8a5187a18..3e25e6626 100644 --- a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/AliyunMnsSdkTest.java +++ b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/AliyunMnsSdkTest.java @@ -8,6 +8,7 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_ENVELOPE_SIZE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; import static org.assertj.core.api.Assertions.assertThat; @@ -30,6 +31,7 @@ import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MnsProcessRequest; import io.opentelemetry.contrib.messaging.wrappers.testing.AbstractBaseTest; import java.nio.charset.StandardCharsets; +import org.apache.commons.codec.binary.Base64; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -140,11 +142,15 @@ public void assertTraces() { .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( - equalTo(MESSAGING_SYSTEM, "guava-eventbus"), + equalTo(MESSAGING_SYSTEM, "smq"), equalTo(MESSAGING_DESTINATION_NAME, QUEUE), equalTo( MESSAGING_MESSAGE_BODY_SIZE, MESSAGE_BODY.getBytes(StandardCharsets.UTF_8).length), + equalTo( + MESSAGING_MESSAGE_ENVELOPE_SIZE, + Base64.encodeBase64(MESSAGE_BODY.getBytes(StandardCharsets.UTF_8)) + .length), equalTo(MESSAGING_OPERATION, "process")), span -> span.hasName("process child") diff --git a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/SmqMockedBroker.java b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/SmqMockedBroker.java index 03197eca8..bff2ad8ef 100644 --- a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/SmqMockedBroker.java +++ b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/SmqMockedBroker.java @@ -49,18 +49,22 @@ import org.springframework.web.bind.annotation.RequestParam; @SpringBootApplication -@SuppressWarnings({"JavaUtilDate", "LockNotBeforeTry", "SystemOut"}) +@SuppressWarnings({ + "JavaUtilDate", + "LockNotBeforeTry", + "SystemOut", + "PrivateConstructorForUtilityClass" +}) public class SmqMockedBroker { public static void main(String[] args) { - System.out.println(System.getProperty("java.class.path")); SpringApplication.run(SmqMockedBroker.class, args); } @Controller @Scope("singleton") public static class BrokerController { - + Lock queuesLock = new ReentrantLock(); Map> queues = new HashMap<>(); @@ -225,7 +229,4 @@ public ResponseEntity serveDeleteMessage( .build(); } } - - private SmqMockedBroker() { - } } diff --git a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/SmqUtils.java b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/SmqUtils.java index 95887db73..5bdc81bf3 100644 --- a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/SmqUtils.java +++ b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/broker/SmqUtils.java @@ -41,7 +41,11 @@ public static String generateRandomMessageId() { } public static String generateRandomRequestId() { - return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 24).toUpperCase(Locale.ROOT); + return UUID.randomUUID() + .toString() + .replaceAll("-", "") + .substring(0, 24) + .toUpperCase(Locale.ROOT); } public static ErrorMessage createErrorMessage(String requestId) { @@ -68,6 +72,5 @@ private static String bytesToHex(byte[] bytes) { return sb.toString(); } - private SmqUtils() { - } + private SmqUtils() {} } diff --git a/messaging-wrappers/kafka-clients/build.gradle.kts b/messaging-wrappers/kafka-clients/build.gradle.kts index 56ad60c53..ec07abd94 100644 --- a/messaging-wrappers/kafka-clients/build.gradle.kts +++ b/messaging-wrappers/kafka-clients/build.gradle.kts @@ -38,3 +38,9 @@ tasks { jvmArgs("-Dotel.logs.exporter=logging") } } + +configurations.all { + resolutionStrategy { + force("org.apache.kafka:kafka-clients:0.11.0.0") + } +} diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java index b2c55397d..21138dc14 100644 --- a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java +++ b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientBaseTest.java @@ -135,7 +135,7 @@ public void awaitUntilConsumerIsReady() throws InterruptedException { return; } for (int i = 0; i < 60; i++) { - consumer.poll(Duration.ZERO); + consumer.poll(0L); if (consumerReady.await(3, TimeUnit.SECONDS)) { break; } diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java index cc61e0965..f76e1e018 100644 --- a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java +++ b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java @@ -104,7 +104,7 @@ public void sendWithParent(Tracer tracer) { public void consumeWithChild( Tracer tracer, MessagingProcessWrapper wrapper) { // check that the message was received - ConsumerRecords records = consumer.poll(Duration.ofSeconds(5)); + ConsumerRecords records = consumer.poll(Duration.ofSeconds(5).toMillis()); assertThat(records.count()).isEqualTo(1); ConsumerRecord record = records.iterator().next(); assertThat(record.value()).isEqualTo(greeting); From 719057f8c06a6b7006b5720ebb2b8c926561e077 Mon Sep 17 00:00:00 2001 From: cirilla-zmh Date: Wed, 11 Jun 2025 21:06:47 +0800 Subject: [PATCH 14/17] refactor: reduce the complexity of messaging-wrappers --- messaging-wrappers/README.md | 28 +++--- .../messaging/wrappers/mns/MnsHelper.java | 6 +- .../mns/MnsProcessWrapperBuilder.java | 17 +++- .../wrappers/mns/MnsTextMapGetter.java | 10 +-- .../semconv/MnsConsumerAttributesGetter.java | 89 +++++++++++++++++++ .../mns/semconv/MnsProcessRequest.java | 65 +------------- .../wrappers/mns/AliyunMnsSdkTest.java | 2 +- ...DefaultMessagingProcessWrapperBuilder.java | 85 ------------------ .../wrappers/MessagingProcessWrapper.java | 24 ++--- .../MessagingProcessWrapperBuilder.java | 70 +++++++++++++++ .../messaging/wrappers/NoopTextMapGetter.java | 6 +- .../semconv/DefaultMessageTextMapGetter.java | 31 +++++++ .../DefaultMessagingAttributesGetter.java | 65 +++++++------- .../semconv/MessagingProcessRequest.java | 11 +++ .../UserDefinedMessageSystemTest.java | 24 +++-- .../wrappers/impl/MessageRequest.java | 6 +- .../wrappers/impl/MessageTextMapGetter.java | 34 ------- .../wrappers/model/MessageListener.java | 7 +- .../messaging/wrappers/kafka/KafkaHelper.java | 7 +- .../kafka/KafkaProcessWrapperBuilder.java | 20 ++++- .../wrappers/kafka/KafkaTextMapGetter.java | 11 ++- .../KafkaConsumerAttributesExtractor.java | 28 +++--- .../KafkaConsumerAttributesGetter.java | 88 ++++++++++++++++++ .../kafka/semconv/KafkaProcessRequest.java | 81 ++--------------- .../wrappers/kafka/KafkaClientTest.java | 21 +++-- 25 files changed, 452 insertions(+), 384 deletions(-) create mode 100644 messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MnsConsumerAttributesGetter.java delete mode 100644 messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java create mode 100644 messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapperBuilder.java create mode 100644 messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessageTextMapGetter.java delete mode 100644 messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageTextMapGetter.java create mode 100644 messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesGetter.java diff --git a/messaging-wrappers/README.md b/messaging-wrappers/README.md index 8b6725413..06d1ae885 100644 --- a/messaging-wrappers/README.md +++ b/messaging-wrappers/README.md @@ -150,25 +150,33 @@ Below is an example of how to initialize a messaging wrapper. ```java public class Demo { - public static MessagingProcessWrapper createWrapper( - OpenTelemetry openTelemetry, - MyTextMapGetter textMapGetter, - List> additionalExtractor) { + public static MessagingProcessWrapper createWrapper(OpenTelemetry openTelemetry) { - return MessagingProcessWrapper.defaultBuilder() + return MessagingProcessWrapper.defaultBuilder() .openTelemetry(openTelemetry) - .textMapGetter(textMapGetter) - .addAttributesExtractors(additionalExtractor) + .textMapGetter(DefaultMessageTextMapGetter.create()) + .attributesExtractors( + Collections.singletonList( + MessagingAttributesExtractor.create( + DefaultMessagingAttributesGetter.create(), MessageOperation.PROCESS))) .build(); } } public class MyMessagingProcessRequest implements MessagingProcessRequest { // your implementation here -} -public class MyTextMapGetter implements TextMapGetter { - // your implementation here + @Override + public List getMessageHeader(String name) { + // impl: how to get specific header from your message + return Collections.singletonList(message.getHeaders().get(name)); + } + + @Override + public Collection getAllMessageHeadersKey() { + // impl: how to get all headers key set from your message + return message.getHeaders().keySet(); + } } ``` diff --git a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsHelper.java b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsHelper.java index 59c7fb931..a33d569cc 100644 --- a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsHelper.java +++ b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsHelper.java @@ -9,14 +9,12 @@ import com.aliyun.mns.model.MessagePropertyValue; import com.aliyun.mns.model.MessageSystemPropertyName; import com.aliyun.mns.model.MessageSystemPropertyValue; -import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MnsProcessRequest; import javax.annotation.Nullable; public final class MnsHelper { - public static - MnsProcessWrapperBuilder processWrapperBuilder() { - return new MnsProcessWrapperBuilder<>(); + public static MnsProcessWrapperBuilder processWrapperBuilder() { + return new MnsProcessWrapperBuilder(); } @Nullable diff --git a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsProcessWrapperBuilder.java b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsProcessWrapperBuilder.java index 493ff93c1..1993b4716 100644 --- a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsProcessWrapperBuilder.java +++ b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsProcessWrapperBuilder.java @@ -5,14 +5,25 @@ package io.opentelemetry.contrib.messaging.wrappers.mns; -import io.opentelemetry.contrib.messaging.wrappers.DefaultMessagingProcessWrapperBuilder; +import io.opentelemetry.contrib.messaging.wrappers.MessagingProcessWrapperBuilder; +import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MnsConsumerAttributesGetter; import io.opentelemetry.contrib.messaging.wrappers.mns.semconv.MnsProcessRequest; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; +import java.util.ArrayList; -public class MnsProcessWrapperBuilder - extends DefaultMessagingProcessWrapperBuilder { +public class MnsProcessWrapperBuilder extends MessagingProcessWrapperBuilder { MnsProcessWrapperBuilder() { super(); super.textMapGetter = MnsTextMapGetter.create(); + super.spanNameExtractor = + MessagingSpanNameExtractor.create( + MnsConsumerAttributesGetter.INSTANCE, MessageOperation.PROCESS); + super.attributesExtractors = new ArrayList<>(); + super.attributesExtractors.add( + MessagingAttributesExtractor.create( + MnsConsumerAttributesGetter.INSTANCE, MessageOperation.PROCESS)); } } diff --git a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsTextMapGetter.java b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsTextMapGetter.java index 4eb854149..12691bd6a 100644 --- a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsTextMapGetter.java +++ b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/MnsTextMapGetter.java @@ -19,14 +19,14 @@ import java.util.Map; import javax.annotation.Nullable; -public class MnsTextMapGetter implements TextMapGetter { +public class MnsTextMapGetter implements TextMapGetter { - public static TextMapGetter create() { - return new MnsTextMapGetter<>(); + public static TextMapGetter create() { + return new MnsTextMapGetter(); } @Override - public Iterable keys(@Nullable REQUEST carrier) { + public Iterable keys(@Nullable MnsProcessRequest carrier) { if (carrier == null || carrier.getMessage() == null) { return emptyList(); } @@ -48,7 +48,7 @@ public Iterable keys(@Nullable REQUEST carrier) { @Nullable @Override - public String get(@Nullable REQUEST carrier, String key) { + public String get(@Nullable MnsProcessRequest carrier, String key) { if (carrier == null || carrier.getMessage() == null) { return null; } diff --git a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MnsConsumerAttributesGetter.java b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MnsConsumerAttributesGetter.java new file mode 100644 index 000000000..4b94c1ce6 --- /dev/null +++ b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MnsConsumerAttributesGetter.java @@ -0,0 +1,89 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.messaging.wrappers.mns.semconv; + +import io.opentelemetry.contrib.messaging.wrappers.mns.MnsHelper; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; + +public enum MnsConsumerAttributesGetter + implements MessagingAttributesGetter { + INSTANCE; + + @Override + public String getSystem(MnsProcessRequest request) { + return "smq"; + } + + @Nullable + @Override + public String getDestination(MnsProcessRequest request) { + return request.getDestination(); + } + + @Nullable + @Override + public String getDestinationTemplate(MnsProcessRequest request) { + return null; + } + + @Override + public boolean isTemporaryDestination(MnsProcessRequest request) { + return false; + } + + @Override + public boolean isAnonymousDestination(MnsProcessRequest request) { + return false; + } + + @Override + @Nullable + public String getConversationId(MnsProcessRequest request) { + return null; + } + + @Nullable + @Override + public Long getMessageBodySize(MnsProcessRequest request) { + return (long) request.getMessage().getMessageBodyAsBytes().length; + } + + @Nullable + @Override + public Long getMessageEnvelopeSize(MnsProcessRequest request) { + return (long) request.getMessage().getMessageBodyAsRawBytes().length; + } + + @Override + @Nullable + public String getMessageId(MnsProcessRequest request, @Nullable Void unused) { + return request.getMessage().getMessageId(); + } + + @Nullable + @Override + public String getClientId(MnsProcessRequest request) { + return null; + } + + @Nullable + @Override + public Long getBatchMessageCount(MnsProcessRequest request, @Nullable Void unused) { + return null; + } + + @Override + public List getMessageHeader(MnsProcessRequest request, String name) { + String header = MnsHelper.getMessageHeader(request.getMessage(), name); + if (header == null) { + return Collections.emptyList(); + } + return Collections.singletonList(header); + } +} diff --git a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MnsProcessRequest.java b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MnsProcessRequest.java index bd38c156d..4c5ec9e42 100644 --- a/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MnsProcessRequest.java +++ b/messaging-wrappers/aliyun-mns-sdk/src/main/java/io/opentelemetry/contrib/messaging/wrappers/mns/semconv/MnsProcessRequest.java @@ -6,13 +6,9 @@ package io.opentelemetry.contrib.messaging.wrappers.mns.semconv; import com.aliyun.mns.model.Message; -import io.opentelemetry.contrib.messaging.wrappers.mns.MnsHelper; -import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; -import java.util.Collections; -import java.util.List; import javax.annotation.Nullable; -public class MnsProcessRequest implements MessagingProcessRequest { +public class MnsProcessRequest { private final Message message; @@ -26,70 +22,15 @@ public static MnsProcessRequest of(Message message, @Nullable String destination return new MnsProcessRequest(message, destination); } - @Override - public String getSystem() { - return "smq"; + public Message getMessage() { + return message; } @Nullable - @Override public String getDestination() { return this.destination; } - @Nullable - @Override - public String getDestinationTemplate() { - return null; - } - - @Override - public boolean isTemporaryDestination() { - return false; - } - - @Override - public boolean isAnonymousDestination() { - return false; - } - - @Nullable - @Override - public String getConversationId() { - return null; - } - - @Nullable - @Override - public Long getMessageBodySize() { - return (long) message.getMessageBodyAsBytes().length; - } - - @Nullable - @Override - public Long getMessageEnvelopeSize() { - return (long) message.getMessageBodyAsRawBytes().length; - } - - @Nullable - @Override - public String getMessageId() { - return message.getMessageId(); - } - - @Override - public List getMessageHeader(String name) { - String header = MnsHelper.getMessageHeader(message, name); - if (header == null) { - return Collections.emptyList(); - } - return Collections.singletonList(header); - } - - public Message getMessage() { - return message; - } - private MnsProcessRequest(Message message, @Nullable String destination) { this.message = message; this.destination = destination; diff --git a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/AliyunMnsSdkTest.java b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/AliyunMnsSdkTest.java index 3e25e6626..02825b37a 100644 --- a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/AliyunMnsSdkTest.java +++ b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/AliyunMnsSdkTest.java @@ -138,7 +138,7 @@ public void assertTraces() { // instrumentation library. span.hasName("publish " + QUEUE).hasKind(SpanKind.PRODUCER).hasNoParent(), span -> - span.hasName("process " + QUEUE) + span.hasName(QUEUE + " process") .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java deleted file mode 100644 index f8b0c8cf3..000000000 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/DefaultMessagingProcessWrapperBuilder.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.messaging.wrappers; - -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.context.propagation.TextMapGetter; -import io.opentelemetry.contrib.messaging.wrappers.semconv.DefaultMessagingAttributesGetter; -import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; -import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; -import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import javax.annotation.Nullable; - -public class DefaultMessagingProcessWrapperBuilder { - - @Nullable private OpenTelemetry openTelemetry; - - @Nullable protected TextMapGetter textMapGetter; - - @CanIgnoreReturnValue - public DefaultMessagingProcessWrapperBuilder openTelemetry(OpenTelemetry openTelemetry) { - this.openTelemetry = openTelemetry; - return this; - } - - protected List> attributesExtractors; - - @CanIgnoreReturnValue - public DefaultMessagingProcessWrapperBuilder textMapGetter( - TextMapGetter textMapGetter) { - this.textMapGetter = textMapGetter; - return this; - } - - /** - * This method overrides the original items. - * - *

See {@link DefaultMessagingProcessWrapperBuilder#addAttributesExtractor} if you just want to - * append one. - */ - @CanIgnoreReturnValue - public DefaultMessagingProcessWrapperBuilder attributesExtractors( - Collection> attributesExtractors) { - this.attributesExtractors = new ArrayList<>(); - this.attributesExtractors.addAll(attributesExtractors); - return this; - } - - @CanIgnoreReturnValue - public DefaultMessagingProcessWrapperBuilder addAttributesExtractor( - AttributesExtractor attributesExtractor) { - this.attributesExtractors.add(attributesExtractor); - return this; - } - - @CanIgnoreReturnValue - public DefaultMessagingProcessWrapperBuilder addAttributesExtractors( - Collection> attributesExtractor) { - this.attributesExtractors.addAll(attributesExtractor); - return this; - } - - public MessagingProcessWrapper build() { - return new MessagingProcessWrapper<>( - this.openTelemetry == null ? GlobalOpenTelemetry.get() : this.openTelemetry, - this.textMapGetter == null ? NoopTextMapGetter.create() : this.textMapGetter, - this.attributesExtractors); - } - - protected DefaultMessagingProcessWrapperBuilder() { - // init attributes extractors by default - this.attributesExtractors = new ArrayList<>(); - this.attributesExtractors.add( - MessagingAttributesExtractor.create( - DefaultMessagingAttributesGetter.create(), MessageOperation.PROCESS)); - } -} diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java index 89a8f099b..f58c8fd61 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java @@ -17,31 +17,30 @@ import io.opentelemetry.context.Scope; import io.opentelemetry.context.propagation.TextMapGetter; import io.opentelemetry.context.propagation.TextMapPropagator; -import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; import java.util.List; import javax.annotation.Nullable; -public class MessagingProcessWrapper { +public class MessagingProcessWrapper { private static final String INSTRUMENTATION_SCOPE = "messaging-process-wrapper"; private static final String INSTRUMENTATION_VERSION = "1.0.0"; - private static final String OPERATION_NAME = "process"; - private final TextMapPropagator textMapPropagator; private final Tracer tracer; private final TextMapGetter textMapGetter; + private final SpanNameExtractor spanNameExtractor; + // no attributes need to be extracted from responses in process operations private final List> attributesExtractors; - public static - DefaultMessagingProcessWrapperBuilder defaultBuilder() { - return new DefaultMessagingProcessWrapperBuilder<>(); + public static MessagingProcessWrapperBuilder defaultBuilder() { + return new MessagingProcessWrapperBuilder<>(); } public void doProcess(REQUEST request, ThrowingRunnable runnable) @@ -77,7 +76,7 @@ public R doProcess(REQUEST request, ThrowingSupplier textMapGetter, + SpanNameExtractor spanNameExtractor, List> attributesExtractors) { this.textMapPropagator = openTelemetry.getPropagators().getTextMapPropagator(); this.tracer = openTelemetry.getTracer(INSTRUMENTATION_SCOPE, INSTRUMENTATION_VERSION); this.textMapGetter = textMapGetter; + this.spanNameExtractor = spanNameExtractor; this.attributesExtractors = attributesExtractors; } } diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapperBuilder.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapperBuilder.java new file mode 100644 index 000000000..b8438e6f1 --- /dev/null +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapperBuilder.java @@ -0,0 +1,70 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.messaging.wrappers; + +import static java.util.Objects.requireNonNull; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import javax.annotation.Nullable; + +public class MessagingProcessWrapperBuilder { + + @Nullable private OpenTelemetry openTelemetry; + + @Nullable protected TextMapGetter textMapGetter; + + @Nullable protected SpanNameExtractor spanNameExtractor; + + @Nullable protected List> attributesExtractors; + + @CanIgnoreReturnValue + public MessagingProcessWrapperBuilder openTelemetry(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + return this; + } + + @CanIgnoreReturnValue + public MessagingProcessWrapperBuilder textMapGetter( + TextMapGetter textMapGetter) { + this.textMapGetter = textMapGetter; + return this; + } + + @CanIgnoreReturnValue + public MessagingProcessWrapperBuilder spanNameExtractor( + SpanNameExtractor spanNameExtractor) { + this.spanNameExtractor = spanNameExtractor; + return this; + } + + @CanIgnoreReturnValue + public MessagingProcessWrapperBuilder attributesExtractors( + Collection> attributesExtractors) { + this.attributesExtractors = new ArrayList<>(); + this.attributesExtractors.addAll(attributesExtractors); + return this; + } + + public MessagingProcessWrapper build() { + requireNonNull(this.spanNameExtractor); + requireNonNull(this.attributesExtractors); + return new MessagingProcessWrapper<>( + this.openTelemetry == null ? GlobalOpenTelemetry.get() : this.openTelemetry, + this.textMapGetter == null ? NoopTextMapGetter.create() : this.textMapGetter, + this.spanNameExtractor, + this.attributesExtractors); + } + + protected MessagingProcessWrapperBuilder() {} +} diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/NoopTextMapGetter.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/NoopTextMapGetter.java index 213fb4a6a..2237d5a6a 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/NoopTextMapGetter.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/NoopTextMapGetter.java @@ -6,14 +6,12 @@ package io.opentelemetry.contrib.messaging.wrappers; import io.opentelemetry.context.propagation.TextMapGetter; -import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; import java.util.Collections; import javax.annotation.Nullable; -public class NoopTextMapGetter - implements TextMapGetter { +public class NoopTextMapGetter implements TextMapGetter { - public static TextMapGetter create() { + public static TextMapGetter create() { return new NoopTextMapGetter<>(); } diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessageTextMapGetter.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessageTextMapGetter.java new file mode 100644 index 000000000..5643a87d2 --- /dev/null +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessageTextMapGetter.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.messaging.wrappers.semconv; + +import io.opentelemetry.context.propagation.TextMapGetter; +import java.util.List; +import javax.annotation.Nullable; + +public enum DefaultMessageTextMapGetter implements TextMapGetter { + INSTANCE; + + @Override + public Iterable keys(MessagingProcessRequest carrier) { + return carrier.getAllMessageHeadersKey(); + } + + @Nullable + @Override + public String get(@Nullable MessagingProcessRequest carrier, String key) { + if (carrier != null) { + List messageHeader = carrier.getMessageHeader(key); + if (messageHeader != null && !messageHeader.isEmpty()) { + return messageHeader.get(0); + } + } + return null; + } +} diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingAttributesGetter.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingAttributesGetter.java index 89c37c703..67f0f543e 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingAttributesGetter.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/DefaultMessagingAttributesGetter.java @@ -9,86 +9,85 @@ import java.util.List; import javax.annotation.Nullable; -public class DefaultMessagingAttributesGetter - implements MessagingAttributesGetter { - - public static - MessagingAttributesGetter create() { - return new DefaultMessagingAttributesGetter<>(); - } +public enum DefaultMessagingAttributesGetter + implements MessagingAttributesGetter { + INSTANCE; @Nullable @Override - public String getDestinationPartitionId(REQUEST request) { - return request.getDestinationPartitionId(); + public String getDestinationPartitionId(MessagingProcessRequest messagingProcessRequest) { + return messagingProcessRequest.getDestinationPartitionId(); } @Override - public List getMessageHeader(REQUEST request, String name) { - return request.getMessageHeader(name); + public List getMessageHeader( + MessagingProcessRequest messagingProcessRequest, String name) { + return messagingProcessRequest.getMessageHeader(name); } @Nullable @Override - public String getSystem(REQUEST request) { - return request.getSystem(); + public String getSystem(MessagingProcessRequest messagingProcessRequest) { + return messagingProcessRequest.getSystem(); } @Nullable @Override - public String getDestination(REQUEST request) { - return request.getDestination(); + public String getDestination(MessagingProcessRequest messagingProcessRequest) { + return messagingProcessRequest.getDestination(); } @Nullable @Override - public String getDestinationTemplate(REQUEST request) { - return request.getDestinationTemplate(); + public String getDestinationTemplate(MessagingProcessRequest messagingProcessRequest) { + return messagingProcessRequest.getDestinationTemplate(); } @Override - public boolean isTemporaryDestination(REQUEST request) { - return request.isTemporaryDestination(); + public boolean isTemporaryDestination(MessagingProcessRequest messagingProcessRequest) { + return messagingProcessRequest.isTemporaryDestination(); } @Override - public boolean isAnonymousDestination(REQUEST request) { - return request.isAnonymousDestination(); + public boolean isAnonymousDestination(MessagingProcessRequest messagingProcessRequest) { + return messagingProcessRequest.isAnonymousDestination(); } @Nullable @Override - public String getConversationId(REQUEST request) { - return request.getConversationId(); + public String getConversationId(MessagingProcessRequest messagingProcessRequest) { + return messagingProcessRequest.getConversationId(); } @Nullable @Override - public Long getMessageBodySize(REQUEST request) { - return request.getMessageBodySize(); + public Long getMessageBodySize(MessagingProcessRequest messagingProcessRequest) { + return messagingProcessRequest.getMessageBodySize(); } @Nullable @Override - public Long getMessageEnvelopeSize(REQUEST request) { - return request.getMessageEnvelopeSize(); + public Long getMessageEnvelopeSize(MessagingProcessRequest messagingProcessRequest) { + return messagingProcessRequest.getMessageEnvelopeSize(); } @Nullable @Override - public String getMessageId(REQUEST request, @Nullable Void unused) { - return request.getMessageId(); + public String getMessageId( + MessagingProcessRequest messagingProcessRequest, @Nullable Void unused) { + return messagingProcessRequest.getMessageId(); } @Nullable @Override - public String getClientId(REQUEST request) { - return request.getClientId(); + public String getClientId(MessagingProcessRequest messagingProcessRequest) { + return messagingProcessRequest.getClientId(); } @Nullable @Override - public Long getBatchMessageCount(REQUEST request, @Nullable Void unused) { - return request.getBatchMessageCount(); + public Long getBatchMessageCount( + MessagingProcessRequest messagingProcessRequest, @Nullable Void unused) { + return messagingProcessRequest.getBatchMessageCount(); } } diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java index 42d342d79..119b95895 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/semconv/MessagingProcessRequest.java @@ -7,6 +7,7 @@ import static java.util.Collections.emptyList; +import java.util.Collection; import java.util.List; import javax.annotation.Nullable; @@ -67,4 +68,14 @@ default String getDestinationPartitionId() { default List getMessageHeader(String name) { return emptyList(); } + + /** + * Extracts all keys of headers from the request, or an empty list/set if there were none. + * + *

Implementations of this method must not return a null value; an empty list should be + * returned instead. + */ + default Collection getAllMessageHeadersKey() { + return emptyList(); + } } diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java index 43fca9167..63c4fe651 100644 --- a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java @@ -24,13 +24,18 @@ import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.contrib.messaging.wrappers.impl.MessageRequest; -import io.opentelemetry.contrib.messaging.wrappers.impl.MessageTextMapGetter; import io.opentelemetry.contrib.messaging.wrappers.model.Message; import io.opentelemetry.contrib.messaging.wrappers.model.MessageListener; import io.opentelemetry.contrib.messaging.wrappers.model.MessageTextMapSetter; +import io.opentelemetry.contrib.messaging.wrappers.semconv.DefaultMessageTextMapGetter; +import io.opentelemetry.contrib.messaging.wrappers.semconv.DefaultMessagingAttributesGetter; +import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; import io.opentelemetry.contrib.messaging.wrappers.testing.AbstractBaseTest; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.HashMap; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -50,10 +55,17 @@ public class UserDefinedMessageSystemTest extends AbstractBaseTest { void setupClass() { otel = GlobalOpenTelemetry.get(); tracer = otel.getTracer("test-tracer", "1.0.0"); - MessagingProcessWrapper wrapper = - MessagingProcessWrapper.defaultBuilder() + MessagingProcessWrapper wrapper = + MessagingProcessWrapper.defaultBuilder() .openTelemetry(otel) - .textMapGetter(MessageTextMapGetter.create()) + .textMapGetter(DefaultMessageTextMapGetter.INSTANCE) + .spanNameExtractor( + MessagingSpanNameExtractor.create( + DefaultMessagingAttributesGetter.INSTANCE, MessageOperation.PROCESS)) + .attributesExtractors( + Collections.singletonList( + MessagingAttributesExtractor.create( + DefaultMessagingAttributesGetter.INSTANCE, MessageOperation.PROCESS))) .build(); eventBus = new EventBus(); @@ -101,7 +113,7 @@ public void assertTraces() { .hasKind(SpanKind.PRODUCER) .hasNoParent(), span -> - span.hasName("process " + EVENTBUS_NAME) + span.hasName(EVENTBUS_NAME + " process") .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageRequest.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageRequest.java index c70523c4b..8c42a852f 100644 --- a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageRequest.java +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageRequest.java @@ -8,6 +8,7 @@ import io.opentelemetry.contrib.messaging.wrappers.model.Message; import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; import java.nio.charset.StandardCharsets; +import java.util.Collection; import java.util.Collections; import java.util.List; import javax.annotation.Nullable; @@ -91,8 +92,9 @@ public List getMessageHeader(String name) { return Collections.singletonList(message.getHeaders().get(name)); } - public Message getMessage() { - return message; + @Override + public Collection getAllMessageHeadersKey() { + return message.getHeaders().keySet(); } private MessageRequest( diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageTextMapGetter.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageTextMapGetter.java deleted file mode 100644 index 4fa167fcb..000000000 --- a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/impl/MessageTextMapGetter.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.messaging.wrappers.impl; - -import io.opentelemetry.context.propagation.TextMapGetter; -import java.util.Collections; -import javax.annotation.Nullable; - -public class MessageTextMapGetter implements TextMapGetter { - - public static TextMapGetter create() { - return new MessageTextMapGetter(); - } - - @Override - public Iterable keys(MessageRequest carrier) { - if (carrier == null || carrier.getMessage() == null) { - return Collections.emptyList(); - } - return carrier.getMessage().getHeaders().keySet(); - } - - @Nullable - @Override - public String get(@Nullable MessageRequest carrier, String key) { - if (carrier == null || carrier.getMessage() == null) { - return null; - } - return carrier.getMessage().getHeaders().get(key); - } -} diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageListener.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageListener.java index cc22a8958..fccd5c157 100644 --- a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageListener.java +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/model/MessageListener.java @@ -13,6 +13,7 @@ import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.contrib.messaging.wrappers.MessagingProcessWrapper; import io.opentelemetry.contrib.messaging.wrappers.impl.MessageRequest; +import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,10 +23,10 @@ public class MessageListener { private final Tracer tracer; - private final MessagingProcessWrapper wrapper; + private final MessagingProcessWrapper wrapper; public static MessageListener create( - Tracer tracer, MessagingProcessWrapper wrapper) { + Tracer tracer, MessagingProcessWrapper wrapper) { return new MessageListener(tracer, wrapper); } @@ -40,7 +41,7 @@ public void handleEvent(Message event) { }); } - private MessageListener(Tracer tracer, MessagingProcessWrapper wrapper) { + private MessageListener(Tracer tracer, MessagingProcessWrapper wrapper) { this.tracer = tracer; this.wrapper = wrapper; } diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaHelper.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaHelper.java index fee425043..10896cae3 100644 --- a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaHelper.java +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaHelper.java @@ -5,13 +5,10 @@ package io.opentelemetry.contrib.messaging.wrappers.kafka; -import io.opentelemetry.contrib.messaging.wrappers.kafka.semconv.KafkaProcessRequest; - public final class KafkaHelper { - public static - KafkaProcessWrapperBuilder processWrapperBuilder() { - return new KafkaProcessWrapperBuilder<>(); + public static KafkaProcessWrapperBuilder processWrapperBuilder() { + return new KafkaProcessWrapperBuilder(); } private KafkaHelper() {} diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaProcessWrapperBuilder.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaProcessWrapperBuilder.java index 75716640c..52735c180 100644 --- a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaProcessWrapperBuilder.java +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaProcessWrapperBuilder.java @@ -5,14 +5,28 @@ package io.opentelemetry.contrib.messaging.wrappers.kafka; -import io.opentelemetry.contrib.messaging.wrappers.DefaultMessagingProcessWrapperBuilder; +import io.opentelemetry.contrib.messaging.wrappers.MessagingProcessWrapperBuilder; +import io.opentelemetry.contrib.messaging.wrappers.kafka.semconv.KafkaConsumerAttributesExtractor; +import io.opentelemetry.contrib.messaging.wrappers.kafka.semconv.KafkaConsumerAttributesGetter; import io.opentelemetry.contrib.messaging.wrappers.kafka.semconv.KafkaProcessRequest; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessageOperation; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingSpanNameExtractor; +import java.util.ArrayList; -public class KafkaProcessWrapperBuilder - extends DefaultMessagingProcessWrapperBuilder { +public class KafkaProcessWrapperBuilder + extends MessagingProcessWrapperBuilder { KafkaProcessWrapperBuilder() { super(); super.textMapGetter = KafkaTextMapGetter.create(); + super.spanNameExtractor = + MessagingSpanNameExtractor.create( + KafkaConsumerAttributesGetter.INSTANCE, MessageOperation.PROCESS); + super.attributesExtractors = new ArrayList<>(); + super.attributesExtractors.add( + MessagingAttributesExtractor.create( + KafkaConsumerAttributesGetter.INSTANCE, MessageOperation.PROCESS)); + super.attributesExtractors.add(KafkaConsumerAttributesExtractor.INSTANCE); } } diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaTextMapGetter.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaTextMapGetter.java index 483561525..820b7bfca 100644 --- a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaTextMapGetter.java +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaTextMapGetter.java @@ -18,15 +18,14 @@ * Copied from KafkaConsumerRecordGetter. */ -public class KafkaTextMapGetter - implements TextMapGetter { +public class KafkaTextMapGetter implements TextMapGetter { - public static TextMapGetter create() { - return new KafkaTextMapGetter<>(); + public static TextMapGetter create() { + return new KafkaTextMapGetter(); } @Override - public Iterable keys(@Nullable REQUEST carrier) { + public Iterable keys(@Nullable KafkaProcessRequest carrier) { if (carrier == null || carrier.getRecord() == null) { return Collections.emptyList(); } @@ -37,7 +36,7 @@ public Iterable keys(@Nullable REQUEST carrier) { @Nullable @Override - public String get(@Nullable REQUEST carrier, String key) { + public String get(@Nullable KafkaProcessRequest carrier, String key) { if (carrier == null || carrier.getRecord() == null) { return null; } diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesExtractor.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesExtractor.java index e65df97d8..f037427c1 100644 --- a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesExtractor.java +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesExtractor.java @@ -17,32 +17,30 @@ * Copied from KafkaConsumerAttributesExtractor. */ -public final class KafkaConsumerAttributesExtractor - implements AttributesExtractor { +public enum KafkaConsumerAttributesExtractor + implements AttributesExtractor { + INSTANCE; // copied from MessagingIncubatingAttributes private static final AttributeKey MESSAGING_DESTINATION_PARTITION_ID = AttributeKey.stringKey("messaging.destination.partition.id"); - private static final AttributeKey MESSAGING_CONSUMER_GROUP_NAME = - AttributeKey.stringKey("messaging.consumer.group.name"); - private static final AttributeKey MESSAGING_KAFKA_OFFSET = - AttributeKey.longKey("messaging.kafka.offset"); + private static final AttributeKey MESSAGING_KAFKA_CONSUMER_GROUP = + AttributeKey.stringKey("messaging.kafka.consumer.group"); private static final AttributeKey MESSAGING_KAFKA_MESSAGE_KEY = AttributeKey.stringKey("messaging.kafka.message.key"); + private static final AttributeKey MESSAGING_KAFKA_MESSAGE_OFFSET = + AttributeKey.longKey("messaging.kafka.message.offset"); private static final AttributeKey MESSAGING_KAFKA_MESSAGE_TOMBSTONE = AttributeKey.booleanKey("messaging.kafka.message.tombstone"); - public static AttributesExtractor create() { - return new KafkaConsumerAttributesExtractor<>(); - } - @Override - public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) { + public void onStart( + AttributesBuilder attributes, Context parentContext, KafkaProcessRequest request) { ConsumerRecord record = request.getRecord(); attributes.put(MESSAGING_DESTINATION_PARTITION_ID, String.valueOf(record.partition())); - attributes.put(MESSAGING_KAFKA_OFFSET, record.offset()); + attributes.put(MESSAGING_KAFKA_MESSAGE_OFFSET, record.offset()); Object key = record.key(); if (key != null && canSerialize(key.getClass())) { @@ -54,7 +52,7 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST String consumerGroup = request.getConsumerGroup(); if (consumerGroup != null) { - attributes.put(MESSAGING_CONSUMER_GROUP_NAME, consumerGroup); + attributes.put(MESSAGING_KAFKA_CONSUMER_GROUP, consumerGroup); } } @@ -68,9 +66,7 @@ private static boolean canSerialize(Class keyClass) { public void onEnd( AttributesBuilder attributes, Context context, - REQUEST request, + KafkaProcessRequest request, @Nullable Void unused, @Nullable Throwable error) {} - - private KafkaConsumerAttributesExtractor() {} } diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesGetter.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesGetter.java new file mode 100644 index 000000000..5797c7cd1 --- /dev/null +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaConsumerAttributesGetter.java @@ -0,0 +1,88 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.messaging.wrappers.kafka.semconv; + +import io.opentelemetry.instrumentation.api.incubator.semconv.messaging.MessagingAttributesGetter; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; +import javax.annotation.Nullable; + +public enum KafkaConsumerAttributesGetter + implements MessagingAttributesGetter { + INSTANCE; + + @Override + public String getSystem(KafkaProcessRequest request) { + return "kafka"; + } + + @Override + public String getDestination(KafkaProcessRequest request) { + return request.getRecord().topic(); + } + + @Nullable + @Override + public String getDestinationTemplate(KafkaProcessRequest request) { + return null; + } + + @Override + public boolean isTemporaryDestination(KafkaProcessRequest request) { + return false; + } + + @Override + public boolean isAnonymousDestination(KafkaProcessRequest request) { + return false; + } + + @Override + @Nullable + public String getConversationId(KafkaProcessRequest request) { + return null; + } + + @Nullable + @Override + public Long getMessageBodySize(KafkaProcessRequest request) { + long size = request.getRecord().serializedValueSize(); + return size >= 0 ? size : null; + } + + @Nullable + @Override + public Long getMessageEnvelopeSize(KafkaProcessRequest request) { + return null; + } + + @Override + @Nullable + public String getMessageId(KafkaProcessRequest request, @Nullable Void unused) { + return null; + } + + @Nullable + @Override + public String getClientId(KafkaProcessRequest request) { + return request.getClientId(); + } + + @Nullable + @Override + public Long getBatchMessageCount(KafkaProcessRequest request, @Nullable Void unused) { + return null; + } + + @Override + public List getMessageHeader(KafkaProcessRequest request, String name) { + return StreamSupport.stream(request.getRecord().headers().headers(name).spliterator(), false) + .map(header -> new String(header.value(), StandardCharsets.UTF_8)) + .collect(Collectors.toList()); + } +} diff --git a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaProcessRequest.java b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaProcessRequest.java index ef9149b2b..5990a8aee 100644 --- a/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaProcessRequest.java +++ b/messaging-wrappers/kafka-clients/src/main/java/io/opentelemetry/contrib/messaging/wrappers/kafka/semconv/KafkaProcessRequest.java @@ -5,15 +5,10 @@ package io.opentelemetry.contrib.messaging.wrappers.kafka.semconv; -import io.opentelemetry.contrib.messaging.wrappers.semconv.MessagingProcessRequest; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; import javax.annotation.Nullable; import org.apache.kafka.clients.consumer.ConsumerRecord; -public class KafkaProcessRequest implements MessagingProcessRequest { +public class KafkaProcessRequest { private final ConsumerRecord consumerRecord; @@ -32,86 +27,20 @@ public static KafkaProcessRequest of( return new KafkaProcessRequest(consumerRecord, consumerGroup, clientId); } - @Override - public String getSystem() { - return "kafka"; - } - - @Nullable - @Override - public String getDestination() { - return this.consumerRecord.topic(); - } - - @Nullable - @Override - public String getDestinationTemplate() { - return null; - } - - @Override - public boolean isTemporaryDestination() { - return false; - } - - @Override - public boolean isAnonymousDestination() { - return false; - } - - @Override - @Nullable - public String getConversationId() { - return null; - } - - @Nullable - @Override - public Long getMessageBodySize() { - long size = this.consumerRecord.serializedValueSize(); - return size >= 0 ? size : null; - } - - @Nullable - @Override - public Long getMessageEnvelopeSize() { - return null; + public ConsumerRecord getRecord() { + return consumerRecord; } - @Override @Nullable - public String getMessageId() { - return null; + public String getConsumerGroup() { + return this.consumerGroup; } @Nullable - @Override public String getClientId() { return this.clientId; } - @Nullable - @Override - public Long getBatchMessageCount() { - return null; - } - - @Override - public List getMessageHeader(String name) { - return StreamSupport.stream(this.consumerRecord.headers().headers(name).spliterator(), false) - .map(header -> new String(header.value(), StandardCharsets.UTF_8)) - .collect(Collectors.toList()); - } - - @Nullable - public String getConsumerGroup() { - return this.consumerGroup; - } - - public ConsumerRecord getRecord() { - return consumerRecord; - } - private KafkaProcessRequest( ConsumerRecord consumerRecord, @Nullable String consumerGroup, diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java index f76e1e018..22cf1bef6 100644 --- a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java +++ b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java @@ -8,10 +8,12 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_CONSUMER_GROUP_NAME; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_PARTITION_ID; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_OFFSET; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_CONSUMER_GROUP; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_KEY; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_TOMBSTONE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; @@ -24,7 +26,6 @@ import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Scope; import io.opentelemetry.contrib.messaging.wrappers.MessagingProcessWrapper; -import io.opentelemetry.contrib.messaging.wrappers.kafka.semconv.KafkaConsumerAttributesExtractor; import io.opentelemetry.contrib.messaging.wrappers.kafka.semconv.KafkaProcessRequest; import io.opentelemetry.instrumentation.kafkaclients.v2_6.TracingConsumerInterceptor; import io.opentelemetry.instrumentation.kafkaclients.v2_6.TracingProducerInterceptor; @@ -70,10 +71,7 @@ void testInterceptors() throws InterruptedException { OpenTelemetry otel = GlobalOpenTelemetry.get(); Tracer tracer = otel.getTracer("test-tracer", "1.0.0"); MessagingProcessWrapper wrapper = - KafkaHelper.processWrapperBuilder() - .openTelemetry(otel) - .addAttributesExtractor(KafkaConsumerAttributesExtractor.create()) - .build(); + KafkaHelper.processWrapperBuilder().openTelemetry(otel).build(); sendWithParent(tracer); @@ -135,7 +133,7 @@ public void assertTraces() { .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)), span -> - span.hasName("process " + SHARED_TOPIC) + span.hasName(SHARED_TOPIC + " process") .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(1)) .hasAttributesSatisfyingExactly( @@ -153,9 +151,10 @@ public void assertTraces() { // release. equalTo( AttributeKey.stringKey("messaging.client_id"), "test-consumer-1"), - satisfies(MESSAGING_KAFKA_OFFSET, AbstractAssert::isNotNull), - equalTo(MESSAGING_CONSUMER_GROUP_NAME, "test"), - equalTo(MESSAGING_OPERATION, "process")), + satisfies(MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractAssert::isNotNull), + equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test"), + equalTo(MESSAGING_OPERATION, "process"), + equalTo(MESSAGING_KAFKA_MESSAGE_KEY, "42")), span -> span.hasName("process child") .hasKind(SpanKind.INTERNAL) From c84ab3d537d88b30e78ab55855dd7cb1ef4216d9 Mon Sep 17 00:00:00 2001 From: otelbot <197425009+otelbot@users.noreply.github.com> Date: Wed, 11 Jun 2025 13:57:25 +0000 Subject: [PATCH 15/17] ./gradlew spotlessApply --- .../contrib/messaging/wrappers/kafka/KafkaClientTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java index 22cf1bef6..c53a47d79 100644 --- a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java +++ b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java @@ -13,7 +13,6 @@ import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_CONSUMER_GROUP; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_KEY; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_TOMBSTONE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; From 78da0f9802f8047d8b62d14230d1edb1b15561e9 Mon Sep 17 00:00:00 2001 From: cirilla-zmh Date: Thu, 12 Jun 2025 09:56:35 +0800 Subject: [PATCH 16/17] fix: missing attributes for the end of process --- .../wrappers/mns/AliyunMnsSdkTest.java | 6 +++++- .../wrappers/MessagingProcessWrapper.java | 20 +++++++++++-------- .../UserDefinedMessageSystemTest.java | 7 ++++++- .../wrappers/kafka/KafkaClientTest.java | 5 ++--- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/AliyunMnsSdkTest.java b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/AliyunMnsSdkTest.java index 02825b37a..c772465f6 100644 --- a/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/AliyunMnsSdkTest.java +++ b/messaging-wrappers/aliyun-mns-sdk/src/test/java/io/opentelemetry/contrib/messaging/wrappers/mns/AliyunMnsSdkTest.java @@ -6,9 +6,11 @@ package io.opentelemetry.contrib.messaging.wrappers.mns; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_ENVELOPE_SIZE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_ID; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; import static org.assertj.core.api.Assertions.assertThat; @@ -32,6 +34,7 @@ import io.opentelemetry.contrib.messaging.wrappers.testing.AbstractBaseTest; import java.nio.charset.StandardCharsets; import org.apache.commons.codec.binary.Base64; +import org.assertj.core.api.AbstractAssert; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -151,7 +154,8 @@ public void assertTraces() { MESSAGING_MESSAGE_ENVELOPE_SIZE, Base64.encodeBase64(MESSAGE_BODY.getBytes(StandardCharsets.UTF_8)) .length), - equalTo(MESSAGING_OPERATION, "process")), + equalTo(MESSAGING_OPERATION, "process"), + satisfies(MESSAGING_MESSAGE_ID, AbstractAssert::isNotNull)), span -> span.hasName("process child") .hasKind(SpanKind.INTERNAL) diff --git a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java index f58c8fd61..073927507 100644 --- a/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java +++ b/messaging-wrappers/api/src/main/java/io/opentelemetry/contrib/messaging/wrappers/MessagingProcessWrapper.java @@ -46,30 +46,32 @@ public static MessagingProcessWrapperBuilder defaultBuilder() public void doProcess(REQUEST request, ThrowingRunnable runnable) throws E { Span span = handleStart(request); + Context context = span.storeInContext(Context.current()); - try (Scope scope = span.makeCurrent()) { + try (Scope scope = context.makeCurrent()) { runnable.run(); } catch (Throwable t) { - handleEnd(span, request, t); + handleEnd(span, context, request, t); throw t; } - handleEnd(span, request, null); + handleEnd(span, context, request, null); } public R doProcess(REQUEST request, ThrowingSupplier supplier) throws E { Span span = handleStart(request); + Context context = span.storeInContext(Context.current()); R result = null; - try (Scope scope = span.makeCurrent()) { + try (Scope scope = context.makeCurrent()) { result = supplier.get(); } catch (Throwable t) { - handleEnd(span, request, t); + handleEnd(span, context, request, t); throw t; } - handleEnd(span, request, null); + handleEnd(span, context, request, null); return result; } @@ -86,11 +88,13 @@ protected Span handleStart(REQUEST request) { return spanBuilder.setAllAttributes(builder.build()).startSpan(); } - protected void handleEnd(Span span, REQUEST request, @Nullable Throwable t) { + protected void handleEnd(Span span, Context context, REQUEST request, @Nullable Throwable t) { AttributesBuilder builder = Attributes.builder(); for (AttributesExtractor extractor : this.attributesExtractors) { - extractor.onEnd(builder, Context.current(), request, null, t); + extractor.onEnd(builder, context, request, null, t); } + + span.setAllAttributes(builder.build()); span.end(); } diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java index 63c4fe651..248074209 100644 --- a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java @@ -10,8 +10,10 @@ import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.MESSAGE_BODY; import static io.opentelemetry.contrib.messaging.wrappers.TestConstants.MESSAGE_ID; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_ID; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; @@ -37,6 +39,8 @@ import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; + +import org.assertj.core.api.AbstractAssert; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -127,7 +131,8 @@ public void assertTraces() { // semconv library right now. It should be replaced after semconv // release. equalTo(AttributeKey.stringKey("messaging.client_id"), CLIENT_ID), - equalTo(MESSAGING_OPERATION, "process")), + equalTo(MESSAGING_OPERATION, "process"), + satisfies(MESSAGING_MESSAGE_ID, AbstractAssert::isNotNull)), span -> span.hasName("process child") .hasKind(SpanKind.INTERNAL) diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java index c53a47d79..b02edde32 100644 --- a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java +++ b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java @@ -11,9 +11,9 @@ import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_DESTINATION_PARTITION_ID; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_CONSUMER_GROUP; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_KEY; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; +import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_ID; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM; @@ -152,8 +152,7 @@ public void assertTraces() { AttributeKey.stringKey("messaging.client_id"), "test-consumer-1"), satisfies(MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractAssert::isNotNull), equalTo(MESSAGING_KAFKA_CONSUMER_GROUP, "test"), - equalTo(MESSAGING_OPERATION, "process"), - equalTo(MESSAGING_KAFKA_MESSAGE_KEY, "42")), + equalTo(MESSAGING_OPERATION, "process")), span -> span.hasName("process child") .hasKind(SpanKind.INTERNAL) From b2737864c42d9442f9fc0f45b3ae5424ae0f71f4 Mon Sep 17 00:00:00 2001 From: otelbot <197425009+otelbot@users.noreply.github.com> Date: Thu, 12 Jun 2025 02:15:40 +0000 Subject: [PATCH 17/17] ./gradlew spotlessApply --- .../contrib/messaging/wrappers/UserDefinedMessageSystemTest.java | 1 - .../contrib/messaging/wrappers/kafka/KafkaClientTest.java | 1 - 2 files changed, 2 deletions(-) diff --git a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java index 248074209..9cbbdf38a 100644 --- a/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java +++ b/messaging-wrappers/api/src/test/java/io/opentelemetry/contrib/messaging/wrappers/UserDefinedMessageSystemTest.java @@ -39,7 +39,6 @@ import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; - import org.assertj.core.api.AbstractAssert; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; diff --git a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java index b02edde32..329663a07 100644 --- a/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java +++ b/messaging-wrappers/kafka-clients/src/test/java/io/opentelemetry/contrib/messaging/wrappers/kafka/KafkaClientTest.java @@ -13,7 +13,6 @@ import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_CONSUMER_GROUP; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE; -import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_MESSAGE_ID; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_SYSTEM;