From 2d48857980381b3223dbc4292c83a84d0d6b530e Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Thu, 17 Mar 2022 18:50:56 +0000 Subject: [PATCH 1/2] Use OpenTelemetry Instrumenter --- .../reactive/messaging/TracingMetadata.java | 8 + pom.xml | 25 +- smallrye-reactive-messaging-amqp/pom.xml | 8 + .../messaging/amqp/AmqpConnector.java | 72 ++-- .../messaging/amqp/AmqpCreditBasedSender.java | 80 ++-- .../reactive/messaging/amqp/AmqpMessage.java | 15 - .../amqp/tracing/AmqpAttributesExtractor.java | 107 +++++ .../tracing/AmqpMessageTextMapGetter.java | 22 + .../tracing/AmqpMessageTextMapSetter.java | 24 ++ .../amqp/tracing/HeaderExtractAdapter.java | 29 -- .../amqp/tracing/HeaderInjectAdapter.java | 25 -- .../messaging/amqp/AmqpSourceTest.java | 2 + .../reactive/messaging/amqp/AmqpUsage.java | 14 +- ...entTest.java => TracingAmqpToAppTest.java} | 99 ++--- .../amqp/TracingAmqpToAppToAmqpTest.java | 189 +++------ .../amqp/TracingAmqpToAppWithParentTest.java | 209 ---------- .../messaging/amqp/TracingAppToAmqpTest.java | 76 +--- smallrye-reactive-messaging-kafka/pom.xml | 8 + .../messaging/kafka/IncomingKafkaRecord.java | 16 - .../messaging/kafka/KafkaConnector.java | 11 +- .../kafka/impl/KafkaRecordHelper.java | 50 --- .../messaging/kafka/impl/KafkaSink.java | 62 ++- .../messaging/kafka/impl/KafkaSource.java | 124 +++--- .../kafka/impl/ce/KafkaCloudEventHelper.java | 8 +- .../kafka/tracing/HeaderExtractAdapter.java | 43 -- .../kafka/tracing/HeaderInjectAdapter.java | 18 - .../tracing/KafkaAttributesExtractor.java | 114 +++++ .../messaging/kafka/tracing/KafkaTrace.java | 77 ++++ .../tracing/KafkaTraceTextMapGetter.java | 38 ++ .../tracing/KafkaTraceTextMapSetter.java | 17 + .../kafka/client/PauseResumeTest.java | 4 +- .../DeprecatedCommitStrategiesTest.java | 5 +- .../KeyDeserializerConfigurationTest.java | 8 - .../ValueDeserializerConfigurationTest.java | 5 +- .../tracing/BatchTracingPropagationTest.java | 183 ++------ .../tracing/HeaderExtractAdapterTest.java | 5 +- .../kafka/tracing/TracingPropagationTest.java | 389 ++++++------------ smallrye-reactive-messaging-rabbitmq/pom.xml | 8 + .../rabbitmq/IncomingRabbitMQMessage.java | 41 +- .../rabbitmq/IncomingRabbitMQMetadata.java | 13 +- .../messaging/rabbitmq/RabbitMQConnector.java | 22 +- .../rabbitmq/RabbitMQMessageConverter.java | 14 +- .../rabbitmq/RabbitMQMessageSender.java | 31 +- .../tracing/HeadersMapExtractAdapter.java | 30 -- .../tracing/HeadersMapInjectAdapter.java | 24 -- .../rabbitmq/tracing/RabbitMQTrace.java | 27 ++ .../RabbitMQTraceAttributesExtractor.java | 92 +++++ .../tracing/RabbitMQTraceTextMapGetter.java | 33 ++ .../tracing/RabbitMQTraceTextMapSetter.java | 19 + .../RabbitMQTracingSubscriberDecorator.java | 87 ++++ .../rabbitmq/tracing/TracingUtils.java | 235 ++--------- .../rabbitmq/IncomingRabbitMQMessageTest.java | 38 +- .../IncomingRabbitMQMetadataTest.java | 16 +- .../rabbitmq/RabbitMQMetadataTest.java | 9 +- ...omingTracingTest.java => TracingTest.java} | 23 +- .../messaging/rabbitmq/WeldTestBase.java | 2 + 56 files changed, 1367 insertions(+), 1586 deletions(-) create mode 100644 smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/AmqpAttributesExtractor.java create mode 100644 smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/AmqpMessageTextMapGetter.java create mode 100644 smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/AmqpMessageTextMapSetter.java delete mode 100644 smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/HeaderExtractAdapter.java delete mode 100644 smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/HeaderInjectAdapter.java rename smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/{TracingAmqpToAppNoParentTest.java => TracingAmqpToAppTest.java} (54%) delete mode 100644 smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppWithParentTest.java delete mode 100644 smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/HeaderExtractAdapter.java delete mode 100644 smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/HeaderInjectAdapter.java create mode 100644 smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/KafkaAttributesExtractor.java create mode 100644 smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/KafkaTrace.java create mode 100644 smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/KafkaTraceTextMapGetter.java create mode 100644 smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/KafkaTraceTextMapSetter.java delete mode 100644 smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/HeadersMapExtractAdapter.java delete mode 100644 smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/HeadersMapInjectAdapter.java create mode 100644 smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTrace.java create mode 100644 smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTraceAttributesExtractor.java create mode 100644 smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTraceTextMapGetter.java create mode 100644 smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTraceTextMapSetter.java create mode 100644 smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTracingSubscriberDecorator.java rename smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/{tracing/IncomingTracingTest.java => TracingTest.java} (88%) diff --git a/api/src/main/java/io/smallrye/reactive/messaging/TracingMetadata.java b/api/src/main/java/io/smallrye/reactive/messaging/TracingMetadata.java index 70b6d149f9..15d5f4e65c 100644 --- a/api/src/main/java/io/smallrye/reactive/messaging/TracingMetadata.java +++ b/api/src/main/java/io/smallrye/reactive/messaging/TracingMetadata.java @@ -59,6 +59,14 @@ public static TracingMetadata withCurrent(Context currentContext) { return EMPTY; } + public static TracingMetadata with(Context currentSpanContext, Context previousSpanContext) { + if (currentSpanContext != null || previousSpanContext != null) { + return new TracingMetadata(currentSpanContext, previousSpanContext); + } + return EMPTY; + } + + @Deprecated public TracingMetadata withSpan(Span span) { if (span != null) { return new TracingMetadata(Context.root().with(span), previousSpanContext); diff --git a/pom.xml b/pom.xml index 4e71352d52..e422226a46 100644 --- a/pom.xml +++ b/pom.xml @@ -264,26 +264,13 @@ ${jboss-log-manager.version} + - io.opentelemetry - opentelemetry-api - ${opentelemetry.version} - - - io.opentelemetry - opentelemetry-semconv - ${opentelemetry-semver.version} - - - io.opentelemetry - opentelemetry-sdk-trace - ${opentelemetry.version} - - - io.opentelemetry - opentelemetry-sdk-testing - ${opentelemetry.version} - test + io.opentelemetry.instrumentation + opentelemetry-instrumentation-bom-alpha + ${opentelemetry.version}-alpha + pom + import diff --git a/smallrye-reactive-messaging-amqp/pom.xml b/smallrye-reactive-messaging-amqp/pom.xml index 1cc40b0d59..52adbfd661 100644 --- a/smallrye-reactive-messaging-amqp/pom.xml +++ b/smallrye-reactive-messaging-amqp/pom.xml @@ -41,6 +41,14 @@ io.opentelemetry opentelemetry-semconv + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-api + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-api-semconv + org.apache.activemq diff --git a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpConnector.java b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpConnector.java index 0e234bc9da..22ac717db3 100644 --- a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpConnector.java +++ b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpConnector.java @@ -38,12 +38,14 @@ import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanBuilder; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessageOperation; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesGetter; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingSpanNameExtractor; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor; @@ -55,6 +57,8 @@ import io.smallrye.reactive.messaging.amqp.fault.AmqpModifiedFailedAndUndeliverableHere; import io.smallrye.reactive.messaging.amqp.fault.AmqpReject; import io.smallrye.reactive.messaging.amqp.fault.AmqpRelease; +import io.smallrye.reactive.messaging.amqp.tracing.AmqpAttributesExtractor; +import io.smallrye.reactive.messaging.amqp.tracing.AmqpMessageTextMapGetter; import io.smallrye.reactive.messaging.annotations.ConnectorAttribute; import io.smallrye.reactive.messaging.health.HealthReport; import io.smallrye.reactive.messaging.health.HealthReporter; @@ -114,8 +118,6 @@ public class AmqpConnector implements IncomingConnectorFactory, OutgoingConnecto public static final String CONNECTOR_NAME = "smallrye-amqp"; - static Tracer TRACER; - @Inject private ExecutionHolder executionHolder; @@ -154,6 +156,8 @@ public class AmqpConnector implements IncomingConnectorFactory, OutgoingConnecto */ private final Map holders = new ConcurrentHashMap<>(); + private Instrumenter, Void> instrumenter; + void setup(ExecutionHolder executionHolder) { this.executionHolder = executionHolder; } @@ -164,7 +168,19 @@ void setup(ExecutionHolder executionHolder) { @PostConstruct void init() { - TRACER = GlobalOpenTelemetry.getTracerProvider().get("io.smallrye.reactive.messaging.amqp"); + // TODO - radcortez - We may want to move this to the constructor injection. SR OTel provides CDI Producer for OTel + AmqpAttributesExtractor amqpAttributesExtractor = new AmqpAttributesExtractor(); + MessagingAttributesGetter, Void> messagingAttributesGetter = amqpAttributesExtractor + .getMessagingAttributesGetter(); + InstrumenterBuilder, Void> builder = Instrumenter.builder(GlobalOpenTelemetry.get(), + "io.smallrye.reactive.messaging", + MessagingSpanNameExtractor.create(messagingAttributesGetter, MessageOperation.RECEIVE)); + + instrumenter = builder + .addAttributesExtractor( + MessagingAttributesExtractor.create(messagingAttributesGetter, MessageOperation.RECEIVE)) + .addAttributesExtractor(amqpAttributesExtractor) + .buildConsumerInstrumenter(AmqpMessageTextMapGetter.INSTANCE); } @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -449,29 +465,25 @@ public void reportFailure(String channel, Throwable reason) { private void incomingTrace(AmqpMessage message) { TracingMetadata tracingMetadata = TracingMetadata.fromMessage(message).orElse(TracingMetadata.empty()); - final SpanBuilder spanBuilder = TRACER.spanBuilder(message.getAddress() + " receive") - .setSpanKind(SpanKind.CONSUMER); - - // Handle possible parent span - final Context parentSpanContext = tracingMetadata.getPreviousContext(); - if (parentSpanContext != null) { - spanBuilder.setParent(parentSpanContext); - } else { - spanBuilder.setNoParent(); + Context parentContext = tracingMetadata.getPreviousContext(); + if (parentContext == null) { + parentContext = Context.current(); } + Context spanContext; + Scope scope = null; - final Span span = spanBuilder.startSpan(); - - // Set Span attributes - span.setAttribute(SemanticAttributes.MESSAGING_SYSTEM, "AMQP 1.0"); - span.setAttribute(SemanticAttributes.MESSAGING_DESTINATION, message.getAddress()); - span.setAttribute(SemanticAttributes.MESSAGING_DESTINATION_KIND, "queue"); - - // Make available as parent for subsequent spans inside message processing - span.makeCurrent(); - - message.injectTracingMetadata(tracingMetadata.withSpan(span)); - - span.end(); + boolean shouldStart = instrumenter.shouldStart(parentContext, message); + if (shouldStart) { + try { + spanContext = instrumenter.start(parentContext, message); + scope = spanContext.makeCurrent(); + message.injectTracingMetadata(TracingMetadata.with(spanContext, parentContext)); + instrumenter.end(spanContext, message, null, null); + } finally { + if (scope != null) { + scope.close(); + } + } + } } } diff --git a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpCreditBasedSender.java b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpCreditBasedSender.java index 55314c8dc2..b7c8c49833 100644 --- a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpCreditBasedSender.java +++ b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpCreditBasedSender.java @@ -16,19 +16,22 @@ import org.reactivestreams.Subscription; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanBuilder; -import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessageOperation; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesGetter; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingSpanNameExtractor; import io.smallrye.common.annotation.CheckReturnValue; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.helpers.Subscriptions; import io.smallrye.mutiny.tuples.Tuple2; import io.smallrye.reactive.messaging.TracingMetadata; import io.smallrye.reactive.messaging.amqp.ce.AmqpCloudEventHelper; -import io.smallrye.reactive.messaging.amqp.tracing.HeaderInjectAdapter; +import io.smallrye.reactive.messaging.amqp.tracing.AmqpAttributesExtractor; +import io.smallrye.reactive.messaging.amqp.tracing.AmqpMessageTextMapSetter; import io.smallrye.reactive.messaging.ce.OutgoingCloudEventMetadata; import io.vertx.amqp.impl.AmqpMessageImpl; import io.vertx.mutiny.amqp.AmqpSender; @@ -54,6 +57,8 @@ public class AmqpCreditBasedSender implements Processor, Message>, private final int retryAttempts; private final int retryInterval; + private final Instrumenter, Void> instrumenter; + private volatile boolean isAnonymous; /** @@ -79,6 +84,19 @@ public AmqpCreditBasedSender(AmqpConnector connector, ConnectionHolder holder, this.retryAttempts = configuration.getReconnectAttempts(); this.retryInterval = configuration.getReconnectInterval(); + + AmqpAttributesExtractor amqpAttributesExtractor = new AmqpAttributesExtractor(); + MessagingAttributesGetter, Void> messagingAttributesGetter = amqpAttributesExtractor + .getMessagingAttributesGetter(); + InstrumenterBuilder, Void> builder = Instrumenter.builder(GlobalOpenTelemetry.get(), + "io.smallrye.reactive.messaging", + MessagingSpanNameExtractor.create(messagingAttributesGetter, MessageOperation.SEND)); + + instrumenter = builder + .addAttributesExtractor( + MessagingAttributesExtractor.create(messagingAttributesGetter, MessageOperation.SEND)) + .addAttributesExtractor(amqpAttributesExtractor) + .buildProducerInstrumenter(AmqpMessageTextMapSetter.INSTANCE); } @Override @@ -307,7 +325,9 @@ private Uni> send(AmqpSender sender, Message msg, boolean durable, amqp.getDelegate().unwrap().setAddress(actualAddress); } - createOutgoingTrace(msg, amqp); + if (tracingEnabled) { + createOutgoingTrace(msg, amqp); + } log.sendingMessageToAddress(actualAddress); return sender.sendWithAck(amqp) @@ -323,39 +343,25 @@ private Uni> send(AmqpSender sender, Message msg, boolean durable, } private void createOutgoingTrace(Message msg, io.vertx.mutiny.amqp.AmqpMessage amqp) { - if (tracingEnabled) { - Optional tracingMetadata = TracingMetadata.fromMessage(msg); - - final SpanBuilder spanBuilder = AmqpConnector.TRACER.spanBuilder(amqp.address() + " send") - .setSpanKind(SpanKind.PRODUCER); - - if (tracingMetadata.isPresent()) { - // Handle possible parent span - final Context parentSpanContext = tracingMetadata.get().getCurrentContext(); - if (parentSpanContext != null) { - spanBuilder.setParent(parentSpanContext); - } else { - spanBuilder.setNoParent(); + Optional tracingMetadata = TracingMetadata.fromMessage(msg); + AmqpMessage message = new AmqpMessage<>(amqp, null, null, false, true); + + Context parentContext = tracingMetadata.map(TracingMetadata::getCurrentContext).orElse(Context.current()); + Context spanContext; + Scope scope = null; + + boolean shouldStart = instrumenter.shouldStart(parentContext, message); + if (shouldStart) { + try { + spanContext = instrumenter.start(parentContext, message); + scope = spanContext.makeCurrent(); + message.injectTracingMetadata(TracingMetadata.with(spanContext, parentContext)); + instrumenter.end(spanContext, message, null, null); + } finally { + if (scope != null) { + scope.close(); } - } else { - spanBuilder.setNoParent(); } - - final Span span = spanBuilder.startSpan(); - Scope scope = span.makeCurrent(); - - // Set Span attributes - span.setAttribute(SemanticAttributes.MESSAGING_SYSTEM, "AMQP 1.0"); - span.setAttribute(SemanticAttributes.MESSAGING_DESTINATION, amqp.address()); - span.setAttribute(SemanticAttributes.MESSAGING_DESTINATION_KIND, "queue"); - span.setAttribute(SemanticAttributes.MESSAGING_PROTOCOL, "AMQP"); - span.setAttribute(SemanticAttributes.MESSAGING_PROTOCOL_VERSION, "1.0"); - - // Set span onto headers - GlobalOpenTelemetry.getPropagators().getTextMapPropagator() - .inject(Context.current(), amqp, HeaderInjectAdapter.SETTER); - span.end(); - scope.close(); } } diff --git a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpMessage.java b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpMessage.java index aaa23f1e2d..57f90c35ed 100644 --- a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpMessage.java +++ b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpMessage.java @@ -16,11 +16,9 @@ import org.apache.qpid.proton.message.MessageError; import org.eclipse.microprofile.reactive.messaging.Metadata; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.smallrye.reactive.messaging.TracingMetadata; import io.smallrye.reactive.messaging.amqp.ce.AmqpCloudEventHelper; import io.smallrye.reactive.messaging.amqp.fault.AmqpFailureHandler; -import io.smallrye.reactive.messaging.amqp.tracing.HeaderExtractAdapter; import io.smallrye.reactive.messaging.ce.CloudEventMetadata; import io.smallrye.reactive.messaging.providers.helpers.VertxContext; import io.smallrye.reactive.messaging.providers.locals.ContextAwareMessage; @@ -94,19 +92,6 @@ public AmqpMessage(io.vertx.amqp.AmqpMessage msg, Context context, AmqpFailureHa payload = (T) convert(message); } - if (tracingEnabled) { - TracingMetadata tracingMetadata = TracingMetadata.empty(); - if (msg.applicationProperties() != null) { - // Read tracing headers - io.opentelemetry.context.Context otelContext = GlobalOpenTelemetry.getPropagators().getTextMapPropagator() - .extract(io.opentelemetry.context.Context.root(), msg.applicationProperties(), - HeaderExtractAdapter.GETTER); - tracingMetadata = TracingMetadata.withPrevious(otelContext); - } - - meta.add(tracingMetadata); - } - this.metadata = captureContextMetadata(meta); } diff --git a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/AmqpAttributesExtractor.java b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/AmqpAttributesExtractor.java new file mode 100644 index 0000000000..319997e2f9 --- /dev/null +++ b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/AmqpAttributesExtractor.java @@ -0,0 +1,107 @@ +package io.smallrye.reactive.messaging.amqp.tracing; + +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesGetter; +import io.smallrye.reactive.messaging.amqp.AmqpConnector; +import io.smallrye.reactive.messaging.amqp.AmqpMessage; + +public class AmqpAttributesExtractor implements AttributesExtractor, Void> { + private final MessagingAttributesGetter, Void> messagingAttributesGetter; + + public AmqpAttributesExtractor() { + this.messagingAttributesGetter = new AmqpMessagingAttributesGetter(); + } + + public MessagingAttributesGetter, Void> getMessagingAttributesGetter() { + return messagingAttributesGetter; + } + + @Override + public void onStart( + final AttributesBuilder attributes, + final Context parentContext, + final AmqpMessage amqpMessage) { + + } + + @Override + public void onEnd( + final AttributesBuilder attributes, + final Context context, + final AmqpMessage amqpMessage, + final Void unused, + final Throwable error) { + } + + private static class AmqpMessagingAttributesGetter implements MessagingAttributesGetter, Void> { + // Required + @Override + public String system(final AmqpMessage amqpMessage) { + return AmqpConnector.CONNECTOR_NAME; + } + + // Required if the message destination is either a queue or topic + @Override + public String destinationKind(final AmqpMessage amqpMessage) { + return "queue"; + } + + // Required + @Override + public String destination(final AmqpMessage amqpMessage) { + return amqpMessage.getAddress(); + } + + @Override + public boolean temporaryDestination(final AmqpMessage amqpMessage) { + return false; + } + + // Recommended + @Override + public String protocol(final AmqpMessage amqpMessage) { + return "AMQP"; + } + + // Recommended + @Override + public String protocolVersion(final AmqpMessage amqpMessage) { + return "1.0"; + } + + // Recommended + @Override + public String url(final AmqpMessage amqpMessage) { + // TODO - radcortez - Need to get it from the configuration + return null; + } + + // Recommended + @Override + public String conversationId(final AmqpMessage amqpMessage) { + Object correlationId = amqpMessage.getCorrelationId(); + return correlationId instanceof String ? (String) correlationId : null; + } + + // Recommended + @Override + public Long messagePayloadSize(final AmqpMessage amqpMessage) { + return null; + } + + // Recommended + @Override + public Long messagePayloadCompressedSize(final AmqpMessage amqpMessage) { + return null; + } + + // Recommended + @Override + public String messageId(final AmqpMessage amqpMessage, final Void unused) { + Object messageId = amqpMessage.getMessageId(); + return messageId instanceof String ? (String) messageId : null; + } + } +} diff --git a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/AmqpMessageTextMapGetter.java b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/AmqpMessageTextMapGetter.java new file mode 100644 index 0000000000..d9e51c3589 --- /dev/null +++ b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/AmqpMessageTextMapGetter.java @@ -0,0 +1,22 @@ +package io.smallrye.reactive.messaging.amqp.tracing; + +import java.util.Collections; + +import io.opentelemetry.context.propagation.TextMapGetter; +import io.smallrye.reactive.messaging.amqp.AmqpMessage; +import io.vertx.core.json.JsonObject; + +public enum AmqpMessageTextMapGetter implements TextMapGetter> { + INSTANCE; + + @Override + public Iterable keys(final AmqpMessage carrier) { + JsonObject applicationProperties = carrier.getApplicationProperties(); + return applicationProperties != null ? applicationProperties.fieldNames() : Collections.emptyList(); + } + + public String get(final AmqpMessage carrier, final String key) { + JsonObject applicationProperties = carrier.getApplicationProperties(); + return applicationProperties != null ? applicationProperties.getString(key) : null; + } +} diff --git a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/AmqpMessageTextMapSetter.java b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/AmqpMessageTextMapSetter.java new file mode 100644 index 0000000000..20a3272d01 --- /dev/null +++ b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/AmqpMessageTextMapSetter.java @@ -0,0 +1,24 @@ +package io.smallrye.reactive.messaging.amqp.tracing; + +import static java.util.Collections.singletonMap; + +import io.opentelemetry.context.propagation.TextMapSetter; +import io.smallrye.reactive.messaging.amqp.AmqpMessage; +import io.vertx.core.json.JsonObject; + +public enum AmqpMessageTextMapSetter implements TextMapSetter> { + INSTANCE; + + @Override + public void set(final AmqpMessage carrier, final String key, final String value) { + if (carrier != null) { + JsonObject applicationProperties = carrier.getApplicationProperties(); + if (applicationProperties != null) { + applicationProperties.put(key, value.getBytes()); + } else { + io.vertx.mutiny.amqp.AmqpMessage.create(carrier.getAmqpMessage()).applicationProperties(new JsonObject( + singletonMap(key, value))).build(); + } + } + } +} diff --git a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/HeaderExtractAdapter.java b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/HeaderExtractAdapter.java deleted file mode 100644 index 488a231797..0000000000 --- a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/HeaderExtractAdapter.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.smallrye.reactive.messaging.amqp.tracing; - -import java.nio.charset.StandardCharsets; - -import io.opentelemetry.context.propagation.TextMapGetter; -import io.vertx.core.json.JsonObject; - -public class HeaderExtractAdapter implements TextMapGetter { - public static final HeaderExtractAdapter GETTER = new HeaderExtractAdapter(); - - private Iterable keys; - - @Override - public Iterable keys(JsonObject properties) { - if (keys == null) { - keys = properties.fieldNames(); - } - - return keys; - } - - @Override - public String get(JsonObject properties, String key) { - if (properties == null || properties.getBinary(key) == null) { - return null; - } - return new String(properties.getBinary(key), StandardCharsets.UTF_8); - } -} diff --git a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/HeaderInjectAdapter.java b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/HeaderInjectAdapter.java deleted file mode 100644 index a12f220c24..0000000000 --- a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/tracing/HeaderInjectAdapter.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.smallrye.reactive.messaging.amqp.tracing; - -import java.nio.charset.StandardCharsets; -import java.util.Properties; - -import io.opentelemetry.context.propagation.TextMapSetter; -import io.vertx.core.json.JsonObject; -import io.vertx.mutiny.amqp.AmqpMessage; - -public class HeaderInjectAdapter implements TextMapSetter { - public static final HeaderInjectAdapter SETTER = new HeaderInjectAdapter(); - - @Override - public void set(AmqpMessage msg, String key, String value) { - if (msg != null) { - if (msg.applicationProperties() != null) { - msg.applicationProperties().put(key, value.getBytes(StandardCharsets.UTF_8)); - } else { - Properties props = new Properties(); - props.put(key, value.getBytes(StandardCharsets.UTF_8)); - msg = AmqpMessage.create(msg).applicationProperties(JsonObject.mapFrom(props)).build(); - } - } - } -} diff --git a/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/AmqpSourceTest.java b/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/AmqpSourceTest.java index a2d8ab2d1c..722669ed06 100644 --- a/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/AmqpSourceTest.java +++ b/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/AmqpSourceTest.java @@ -635,6 +635,7 @@ private Map getConfig(String topic, int port) { config.put("host", "localhost"); config.put("port", port); config.put("name", "some name"); + config.put("tracing-enabled", false); return config; } @@ -645,6 +646,7 @@ private Map getConfigUsingChannelName(String topic, int port) { config.put("host", "localhost"); config.put("port", port); config.put("name", "some name"); + config.put("tracing-enabled", false); return config; } diff --git a/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/AmqpUsage.java b/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/AmqpUsage.java index d3af101378..9cd74219a0 100644 --- a/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/AmqpUsage.java +++ b/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/AmqpUsage.java @@ -23,9 +23,6 @@ import org.apache.qpid.proton.message.Message; import org.jboss.logging.Logger; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.context.Context; -import io.smallrye.reactive.messaging.amqp.tracing.HeaderExtractAdapter; import io.vertx.amqp.AmqpClientOptions; import io.vertx.amqp.AmqpReceiverOptions; import io.vertx.amqp.impl.AmqpMessageImpl; @@ -90,6 +87,7 @@ public void produce(String topic, int messageCount, Supplier messageSupp } } catch (Exception e) { LOGGER.error("Unable to send message", e); + e.printStackTrace(); } }); t.setName(topic + "-thread"); @@ -131,16 +129,6 @@ public void consumeIntegers(String topic, Consumer consumer) { .await().indefinitely(); } - public void consumeIntegersWithTracing(String topicName, Consumer consumer, Consumer tracingConsumer) { - this.consume(topicName, - (msg) -> { - consumer.accept(msg.bodyAsInteger()); - tracingConsumer.accept( - GlobalOpenTelemetry.getPropagators().getTextMapPropagator() - .extract(Context.current(), msg.applicationProperties(), HeaderExtractAdapter.GETTER)); - }); - } - public void close() { client.closeAndAwait(); } diff --git a/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppNoParentTest.java b/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppTest.java similarity index 54% rename from smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppNoParentTest.java rename to smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppTest.java index ca18b503b5..67fffec6d8 100644 --- a/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppNoParentTest.java +++ b/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppTest.java @@ -2,8 +2,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletionStage; import java.util.concurrent.CopyOnWriteArrayList; @@ -16,14 +16,17 @@ import org.eclipse.microprofile.reactive.messaging.Message; import org.jboss.weld.environment.se.Weld; import org.jboss.weld.environment.se.WeldContainer; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SpanProcessor; @@ -31,14 +34,13 @@ import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.samplers.Sampler; import io.smallrye.config.SmallRyeConfigProviderResolver; -import io.smallrye.reactive.messaging.TracingMetadata; import io.smallrye.reactive.messaging.test.common.config.MapBasedConfig; +import io.vertx.mutiny.amqp.AmqpMessage; @Disabled("See https://github.com/smallrye/smallrye-reactive-messaging/issues/1268") -public class TracingAmqpToAppNoParentTest extends AmqpBrokerTestBase { - - private InMemorySpanExporter testExporter; - private SpanProcessor spanProcessor; +public class TracingAmqpToAppTest extends AmqpBrokerTestBase { + private SdkTracerProvider tracerProvider; + private InMemorySpanExporter spanExporter; private WeldContainer container; private final Weld weld = new Weld(); @@ -48,10 +50,10 @@ public void setup() { super.setup(); GlobalOpenTelemetry.resetForTest(); - testExporter = InMemorySpanExporter.create(); - spanProcessor = SimpleSpanProcessor.create(testExporter); + spanExporter = InMemorySpanExporter.create(); + SpanProcessor spanProcessor = SimpleSpanProcessor.create(spanExporter); - SdkTracerProvider tracerProvider = SdkTracerProvider.builder() + tracerProvider = SdkTracerProvider.builder() .addSpanProcessor(spanProcessor) .setSampler(Sampler.alwaysOn()) .build(); @@ -69,13 +71,6 @@ public void cleanup() { } // Release the config objects SmallRyeConfigProviderResolver.instance().releaseConfig(ConfigProvider.getConfig()); - - if (testExporter != null) { - testExporter.shutdown(); - } - if (spanProcessor != null) { - spanProcessor.shutdown(); - } } @AfterAll @@ -84,68 +79,50 @@ static void shutdown() { } @Test - public void testFromAmqpToAppWithNoParent() { - weld.addBeanClass(MyAppReceivingData.class); - + public void testFromAmqpToAppWithParentSpan() { new MapBasedConfig() - .put("mp.messaging.incoming.stuff.connector", AmqpConnector.CONNECTOR_NAME) - .put("mp.messaging.incoming.stuff.host", host) - .put("mp.messaging.incoming.stuff.port", port) - .put("mp.messaging.incoming.stuff.address", "no-parent-stuff") - - .put("amqp-username", username) - .put("amqp-password", password) + .with("mp.messaging.incoming.stuff.connector", AmqpConnector.CONNECTOR_NAME) + .with("mp.messaging.incoming.stuff.host", host) + .with("mp.messaging.incoming.stuff.port", port) + .with("amqp-username", username) + .with("amqp-password", password) .write(); + weld.addBeanClass(MyAppReceivingData.class); container = weld.initialize(); - MyAppReceivingData bean = container.getBeanManager().createInstance().select(MyAppReceivingData.class).get(); - await().until(() -> isAmqpConnectorReady(container)); - AtomicInteger count = new AtomicInteger(); - - usage.produce("no-parent-stuff", 10, count::getAndIncrement); - - await().until(() -> bean.list().size() >= 10); - assertThat(bean.list()).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - - assertThat(bean.tracing()).hasSizeGreaterThanOrEqualTo(10); - assertThat(bean.tracing()).doesNotContainNull().doesNotHaveDuplicates(); - List spanIds = new ArrayList<>(); - - for (TracingMetadata tracing : bean.tracing()) { - spanIds.add(Span.fromContext(tracing.getCurrentContext()).getSpanContext().getSpanId()); - assertThat(Span.fromContextOrNull(tracing.getPreviousContext())).isNull(); - } - - assertThat(spanIds).doesNotContainNull().doesNotHaveDuplicates().hasSizeGreaterThanOrEqualTo(10); + MyAppReceivingData bean = container.getBeanManager().createInstance().select(MyAppReceivingData.class).get(); - for (SpanData data : testExporter.getFinishedSpanItems()) { - assertThat(data.getSpanId()).isIn(spanIds); - assertThat(data.getSpanId()).isNotEqualTo(data.getParentSpanId()); - assertThat(data.getKind()).isEqualByComparingTo(SpanKind.CONSUMER); - } + AtomicInteger count = new AtomicInteger(); + usage.produce("stuff", 10, () -> AmqpMessage.create() + .durable(false) + .ttl(10000) + .withIntegerAsBody(count.getAndIncrement()) + .build()); + + await().until(() -> bean.results().size() >= 10); + assertThat(bean.results()).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + + CompletableResultCode completableResultCode = tracerProvider.forceFlush(); + completableResultCode.whenComplete(() -> { + List spans = spanExporter.getFinishedSpanItems(); + assertEquals(10, spans.size()); + }); } @ApplicationScoped public static class MyAppReceivingData { - private final List tracingMetadata = new ArrayList<>(); private final List results = new CopyOnWriteArrayList<>(); @Incoming("stuff") public CompletionStage consume(Message input) { results.add(input.getPayload()); - tracingMetadata.add(input.getMetadata(TracingMetadata.class).orElse(TracingMetadata.empty())); return input.ack(); } - public List list() { + public List results() { return results; } - - public List tracing() { - return tracingMetadata; - } } - } diff --git a/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppToAmqpTest.java b/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppToAmqpTest.java index 653c8ad86c..c01d741ce8 100644 --- a/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppToAmqpTest.java +++ b/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppToAmqpTest.java @@ -1,16 +1,22 @@ package io.smallrye.reactive.messaging.amqp; +import static io.opentelemetry.api.trace.SpanKind.CONSUMER; +import static io.opentelemetry.api.trace.SpanKind.PRODUCER; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_DESTINATION; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_DESTINATION_KIND; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_PROTOCOL; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_PROTOCOL_VERSION; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_SYSTEM; +import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.ArrayList; import java.util.List; -import java.util.Properties; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; import javax.enterprise.context.ApplicationScoped; @@ -20,19 +26,16 @@ import org.eclipse.microprofile.reactive.messaging.Outgoing; import org.jboss.weld.environment.se.Weld; import org.jboss.weld.environment.se.WeldContainer; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.SpanId; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; -import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SpanProcessor; @@ -40,15 +43,12 @@ import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.samplers.Sampler; import io.smallrye.config.SmallRyeConfigProviderResolver; -import io.smallrye.reactive.messaging.TracingMetadata; import io.smallrye.reactive.messaging.test.common.config.MapBasedConfig; -import io.vertx.core.json.JsonObject; import io.vertx.mutiny.amqp.AmqpMessage; public class TracingAmqpToAppToAmqpTest extends AmqpBrokerTestBase { - - private InMemorySpanExporter testExporter; - private SpanProcessor spanProcessor; + private SdkTracerProvider tracerProvider; + private InMemorySpanExporter spanExporter; private WeldContainer container; private final Weld weld = new Weld(); @@ -58,10 +58,10 @@ public void setup() { super.setup(); GlobalOpenTelemetry.resetForTest(); - testExporter = InMemorySpanExporter.create(); - spanProcessor = SimpleSpanProcessor.create(testExporter); + spanExporter = InMemorySpanExporter.create(); + SpanProcessor spanProcessor = SimpleSpanProcessor.create(spanExporter); - SdkTracerProvider tracerProvider = SdkTracerProvider.builder() + tracerProvider = SdkTracerProvider.builder() .addSpanProcessor(spanProcessor) .setSampler(Sampler.alwaysOn()) .build(); @@ -79,148 +79,79 @@ public void cleanup() { } // Release the config objects SmallRyeConfigProviderResolver.instance().releaseConfig(ConfigProvider.getConfig()); - - if (testExporter != null) { - testExporter.shutdown(); - } - if (spanProcessor != null) { - spanProcessor.shutdown(); - } - } - - @AfterAll - static void shutdown() { - GlobalOpenTelemetry.resetForTest(); } @Test public void testFromAmqpToAppToAmqp() { - List payloads = new CopyOnWriteArrayList<>(); - List receivedContexts = new CopyOnWriteArrayList<>(); - usage.consumeIntegersWithTracing("result-topic", - payloads::add, - receivedContexts::add); - - weld.addBeanClass(MyAppProcessingData.class); - new MapBasedConfig() - .put("mp.messaging.outgoing.result-topic.connector", AmqpConnector.CONNECTOR_NAME) - .put("mp.messaging.outgoing.result-topic.durable", false) - .put("mp.messaging.outgoing.result-topic.host", host) - .put("mp.messaging.outgoing.result-topic.port", port) - - .put("mp.messaging.incoming.parent-topic.connector", AmqpConnector.CONNECTOR_NAME) - .put("mp.messaging.incoming.parent-topic.host", host) - .put("mp.messaging.incoming.parent-topic.port", port) - - .put("amqp-username", username) - .put("amqp-password", password) + .with("mp.messaging.outgoing.result-topic.connector", AmqpConnector.CONNECTOR_NAME) + .with("mp.messaging.outgoing.result-topic.durable", false) + .with("mp.messaging.outgoing.result-topic.host", host) + .with("mp.messaging.outgoing.result-topic.port", port) + .with("mp.messaging.incoming.parent-topic.connector", AmqpConnector.CONNECTOR_NAME) + .with("mp.messaging.incoming.parent-topic.host", host) + .with("mp.messaging.incoming.parent-topic.port", port) + .with("amqp-username", username) + .with("amqp-password", password) .write(); + weld.addBeanClass(MyAppProcessingData.class); container = weld.initialize(); - MyAppProcessingData bean = container.getBeanManager().createInstance().select(MyAppProcessingData.class).get(); - await().until(() -> isAmqpConnectorReady(container)); + List payloads = new CopyOnWriteArrayList<>(); + usage.consumeIntegers("result-topic", payloads::add); + AtomicInteger count = new AtomicInteger(); - List producedSpanContexts = new CopyOnWriteArrayList<>(); usage.produce("parent-topic", 10, () -> AmqpMessage.create() .durable(false) .ttl(10000) .withIntegerAsBody(count.getAndIncrement()) - .applicationProperties(createTracingSpan(producedSpanContexts, "parent-topic")) .build()); - await().atMost(Duration.ofMinutes(5)).until(() -> payloads.size() >= 10); + await().until(() -> payloads.size() >= 10); assertThat(payloads).containsExactly(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); - List producedTraceIds = producedSpanContexts.stream() - .map(SpanContext::getTraceId) - .collect(Collectors.toList()); - assertThat(producedTraceIds).hasSize(10); + CompletableResultCode completableResultCode = tracerProvider.forceFlush(); + completableResultCode.whenComplete(() -> { + List spans = spanExporter.getFinishedSpanItems(); + assertEquals(20, spans.size()); - assertThat(receivedContexts).hasSize(10); - assertThat(receivedContexts).doesNotContainNull().doesNotHaveDuplicates(); + List parentSpans = spans.stream() + .filter(spanData -> spanData.getParentSpanId().equals(SpanId.getInvalid())).collect(toList()); + assertEquals(10, parentSpans.size()); - List receivedSpanIds = receivedContexts.stream() - .map(context -> Span.fromContextOrNull(context).getSpanContext().getSpanId()) - .collect(Collectors.toList()); - assertThat(receivedSpanIds).doesNotContainNull().doesNotHaveDuplicates().hasSize(10); - - List receivedTraceIds = receivedContexts.stream() - .map(context -> Span.fromContextOrNull(context).getSpanContext().getTraceId()) - .collect(Collectors.toList()); - assertThat(receivedTraceIds).doesNotContainNull().doesNotHaveDuplicates().hasSize(10); - assertThat(receivedTraceIds).containsExactlyInAnyOrderElementsOf(producedTraceIds); - - List receivedParentSpanIds = new ArrayList<>(); - - assertThat(bean.tracing()).hasSizeGreaterThanOrEqualTo(10); - assertThat(bean.tracing()).doesNotContainNull().doesNotHaveDuplicates(); - List spanIds = new ArrayList<>(); - - for (TracingMetadata tracing : bean.tracing()) { - Span span = Span.fromContext(tracing.getCurrentContext()); - spanIds.add(span.getSpanContext().getSpanId()); - assertThat(Span.fromContextOrNull(tracing.getPreviousContext())).isNotNull(); - } - - await().atMost(Duration.ofMinutes(2)).until(() -> testExporter.getFinishedSpanItems().size() >= 10); - - List outgoingParentIds = new ArrayList<>(); - List incomingParentIds = new ArrayList<>(); - - for (SpanData data : testExporter.getFinishedSpanItems()) { - if (data.getKind().equals(SpanKind.CONSUMER)) { - incomingParentIds.add(data.getParentSpanId()); - // Need to skip the spans created during @Incoming processing - continue; + for (SpanData parentSpan : parentSpans) { + assertEquals(1, + spans.stream().filter(spanData -> spanData.getParentSpanId().equals(parentSpan.getSpanId())).count()); } - assertThat(data.getSpanId()).isIn(receivedSpanIds); - assertThat(data.getSpanId()).isNotEqualTo(data.getParentSpanId()); - assertThat(data.getTraceId()).isIn(producedTraceIds); - assertThat(data.getKind()).isEqualByComparingTo(SpanKind.PRODUCER); - outgoingParentIds.add(data.getParentSpanId()); - } - - // Assert span created on AMQP record is the parent of consumer span we create - assertThat(producedSpanContexts.stream() - .map(SpanContext::getSpanId)).containsExactlyElementsOf(incomingParentIds); - - // Assert consumer span is the parent of the producer span we received in AMQP - assertThat(spanIds.stream()) - .containsExactlyElementsOf(outgoingParentIds); - } - private JsonObject createTracingSpan(List spanContexts, String topic) { - Properties properties = new Properties(); - final Span span = AmqpConnector.TRACER.spanBuilder(topic).setSpanKind(SpanKind.PRODUCER).startSpan(); - final Context context = span.storeInContext(Context.current()); - GlobalOpenTelemetry.getPropagators() - .getTextMapPropagator() - .inject(context, properties, (headers, key, value) -> { - if (headers != null) { - headers.put(key, value.getBytes(StandardCharsets.UTF_8)); - } - }); - spanContexts.add(span.getSpanContext()); - return JsonObject.mapFrom(properties); + SpanData consumer = parentSpans.get(0); + assertEquals(CONSUMER, consumer.getKind()); + assertEquals("smallrye-amqp", consumer.getAttributes().get(MESSAGING_SYSTEM)); + assertEquals("AMQP", consumer.getAttributes().get(MESSAGING_PROTOCOL)); + assertEquals("1.0", consumer.getAttributes().get(MESSAGING_PROTOCOL_VERSION)); + assertEquals("queue", consumer.getAttributes().get(MESSAGING_DESTINATION_KIND)); + assertEquals("parent-topic", consumer.getAttributes().get(MESSAGING_DESTINATION)); + assertEquals("parent-topic receive", consumer.getName()); + assertEquals("receive", consumer.getAttributes().get(MESSAGING_OPERATION)); + + SpanData producer = spans.stream().filter(span -> span.getParentSpanId().equals(consumer.getSpanId())).findFirst() + .get(); + assertEquals(PRODUCER, producer.getKind()); + assertEquals("queue", producer.getAttributes().get(MESSAGING_DESTINATION_KIND)); + assertEquals("result-topic", producer.getAttributes().get(MESSAGING_DESTINATION)); + assertEquals("result-topic send", producer.getName()); + assertNull(producer.getAttributes().get(MESSAGING_OPERATION)); + }); } @ApplicationScoped public static class MyAppProcessingData { - private final List tracingMetadata = new ArrayList<>(); - @Incoming("parent-topic") @Outgoing("result-topic") public Message processMessage(Message input) { - tracingMetadata.add(input.getMetadata(TracingMetadata.class).orElse(TracingMetadata.empty())); return input.withPayload(input.getPayload() + 1); } - - public List tracing() { - return tracingMetadata; - } } - } diff --git a/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppWithParentTest.java b/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppWithParentTest.java deleted file mode 100644 index b7809f14cb..0000000000 --- a/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppWithParentTest.java +++ /dev/null @@ -1,209 +0,0 @@ -package io.smallrye.reactive.messaging.amqp; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; - -import javax.enterprise.context.ApplicationScoped; - -import org.eclipse.microprofile.config.ConfigProvider; -import org.eclipse.microprofile.reactive.messaging.Incoming; -import org.eclipse.microprofile.reactive.messaging.Message; -import org.jboss.weld.environment.se.Weld; -import org.jboss.weld.environment.se.WeldContainer; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.propagation.ContextPropagators; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import io.opentelemetry.sdk.trace.SpanProcessor; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; -import io.opentelemetry.sdk.trace.samplers.Sampler; -import io.smallrye.config.SmallRyeConfigProviderResolver; -import io.smallrye.reactive.messaging.TracingMetadata; -import io.smallrye.reactive.messaging.test.common.config.MapBasedConfig; -import io.vertx.core.json.JsonObject; -import io.vertx.mutiny.amqp.AmqpMessage; - -public class TracingAmqpToAppWithParentTest extends AmqpBrokerTestBase { - - private InMemorySpanExporter testExporter; - private SpanProcessor spanProcessor; - - private WeldContainer container; - private final Weld weld = new Weld(); - - @BeforeEach - public void setup() { - super.setup(); - GlobalOpenTelemetry.resetForTest(); - - testExporter = InMemorySpanExporter.create(); - spanProcessor = SimpleSpanProcessor.create(testExporter); - - SdkTracerProvider tracerProvider = SdkTracerProvider.builder() - .addSpanProcessor(spanProcessor) - .setSampler(Sampler.alwaysOn()) - .build(); - - OpenTelemetrySdk.builder() - .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) - .setTracerProvider(tracerProvider) - .buildAndRegisterGlobal(); - } - - @AfterEach - public void cleanup() { - if (container != null) { - container.close(); - } - // Release the config objects - SmallRyeConfigProviderResolver.instance().releaseConfig(ConfigProvider.getConfig()); - - if (testExporter != null) { - testExporter.shutdown(); - } - if (spanProcessor != null) { - spanProcessor.shutdown(); - } - } - - @AfterAll - static void shutdown() { - GlobalOpenTelemetry.resetForTest(); - } - - @Test - public void testFromAmqpToAppWithParentSpan() { - weld.addBeanClass(MyAppReceivingData.class); - - new MapBasedConfig() - .put("mp.messaging.incoming.stuff.connector", AmqpConnector.CONNECTOR_NAME) - .put("mp.messaging.incoming.stuff.host", host) - .put("mp.messaging.incoming.stuff.port", port) - - .put("amqp-username", username) - .put("amqp-password", password) - .write(); - - container = weld.initialize(); - MyAppReceivingData bean = container.getBeanManager().createInstance().select(MyAppReceivingData.class).get(); - - await().until(() -> isAmqpConnectorReady(container)); - - AtomicInteger count = new AtomicInteger(); - List producedSpanContexts = new CopyOnWriteArrayList<>(); - - usage.produce("stuff", 10, () -> AmqpMessage.create() - .durable(false) - .ttl(10000) - .withIntegerAsBody(count.getAndIncrement()) - .applicationProperties(createTracingSpan(producedSpanContexts, "stuff")) - .build()); - - await().until(() -> bean.list().size() >= 10); - assertThat(bean.list()).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - - List producedTraceIds = producedSpanContexts.stream() - .map(SpanContext::getTraceId) - .collect(Collectors.toList()); - assertThat(producedTraceIds).hasSize(10); - - assertThat(bean.tracing()).hasSizeGreaterThanOrEqualTo(10); - assertThat(bean.tracing()).doesNotContainNull().doesNotHaveDuplicates(); - - List receivedTraceIds = bean.tracing().stream() - .map(tracingMetadata -> Span.fromContext(tracingMetadata.getCurrentContext()).getSpanContext().getTraceId()) - .collect(Collectors.toList()); - assertThat(receivedTraceIds).doesNotContainNull().doesNotHaveDuplicates().hasSize(10); - assertThat(receivedTraceIds).containsExactlyInAnyOrderElementsOf(producedTraceIds); - - List spanIds = new ArrayList<>(); - - for (TracingMetadata tracing : bean.tracing()) { - spanIds.add(Span.fromContext(tracing.getCurrentContext()).getSpanContext().getSpanId()); - - assertThat(tracing.getPreviousContext()).isNotNull(); - Span previousSpan = Span.fromContextOrNull(tracing.getPreviousContext()); - assertThat(previousSpan).isNotNull(); - assertThat(previousSpan.getSpanContext().getTraceId()) - .isEqualTo(Span.fromContext(tracing.getCurrentContext()).getSpanContext().getTraceId()); - assertThat(previousSpan.getSpanContext().getSpanId()) - .isNotEqualTo(Span.fromContext(tracing.getCurrentContext()).getSpanContext().getSpanId()); - } - - assertThat(spanIds).doesNotContainNull().doesNotHaveDuplicates().hasSizeGreaterThanOrEqualTo(10); - - List parentIds = bean.tracing().stream() - .map(tracingMetadata -> Span.fromContextOrNull(tracingMetadata.getPreviousContext()) - .getSpanContext().getSpanId()) - .collect(Collectors.toList()); - - assertThat(producedSpanContexts.stream() - .map(SpanContext::getSpanId)).containsExactlyElementsOf(parentIds); - - for (SpanData data : testExporter.getFinishedSpanItems()) { - assertThat(data.getSpanId()).isIn(spanIds); - assertThat(data.getSpanId()).isNotEqualTo(data.getParentSpanId()); - assertThat(data.getKind()).isEqualByComparingTo(SpanKind.CONSUMER); - assertThat(data.getParentSpanId()).isNotNull(); - assertThat(data.getParentSpanId()).isIn(parentIds); - } - } - - private JsonObject createTracingSpan(List spanContexts, String topic) { - Properties properties = new Properties(); - final Span span = AmqpConnector.TRACER.spanBuilder(topic).setSpanKind(SpanKind.PRODUCER).startSpan(); - final Context context = span.storeInContext(Context.current()); - GlobalOpenTelemetry.getPropagators() - .getTextMapPropagator() - .inject(context, properties, (headers, key, value) -> { - if (headers != null) { - headers.put(key, value.getBytes(StandardCharsets.UTF_8)); - } - }); - spanContexts.add(span.getSpanContext()); - return JsonObject.mapFrom(properties); - } - - @ApplicationScoped - public static class MyAppReceivingData { - private final List tracingMetadata = new ArrayList<>(); - private final List results = new CopyOnWriteArrayList<>(); - - @Incoming("stuff") - public CompletionStage consume(Message input) { - results.add(input.getPayload()); - tracingMetadata.add(input.getMetadata(TracingMetadata.class).orElse(TracingMetadata.empty())); - return input.ack(); - } - - public List list() { - return results; - } - - public List tracing() { - return tracingMetadata; - } - } - -} diff --git a/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAppToAmqpTest.java b/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAppToAmqpTest.java index 8254982d69..580ab047fd 100644 --- a/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAppToAmqpTest.java +++ b/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAppToAmqpTest.java @@ -2,10 +2,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.stream.Collectors; import javax.enterprise.context.ApplicationScoped; @@ -20,12 +20,10 @@ import org.reactivestreams.Publisher; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; -import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SpanProcessor; @@ -37,9 +35,8 @@ import io.smallrye.reactive.messaging.test.common.config.MapBasedConfig; public class TracingAppToAmqpTest extends AmqpBrokerTestBase { - - private InMemorySpanExporter testExporter; - private SpanProcessor spanProcessor; + private SdkTracerProvider tracerProvider; + private InMemorySpanExporter spanExporter; private WeldContainer container; private final Weld weld = new Weld(); @@ -49,10 +46,10 @@ public void setup() { super.setup(); GlobalOpenTelemetry.resetForTest(); - testExporter = InMemorySpanExporter.create(); - spanProcessor = SimpleSpanProcessor.create(testExporter); + spanExporter = InMemorySpanExporter.create(); + SpanProcessor spanProcessor = SimpleSpanProcessor.create(spanExporter); - SdkTracerProvider tracerProvider = SdkTracerProvider.builder() + tracerProvider = SdkTracerProvider.builder() .addSpanProcessor(spanProcessor) .setSampler(Sampler.alwaysOn()) .build(); @@ -70,13 +67,6 @@ public void cleanup() { } // Release the config objects SmallRyeConfigProviderResolver.instance().releaseConfig(ConfigProvider.getConfig()); - - if (testExporter != null) { - testExporter.shutdown(); - } - if (spanProcessor != null) { - spanProcessor.shutdown(); - } } @AfterAll @@ -84,61 +74,39 @@ static void shutdown() { GlobalOpenTelemetry.resetForTest(); } - @SuppressWarnings("ConstantConditions") @Test public void testFromAppToAmqp() { - List payloads = new CopyOnWriteArrayList<>(); - List contexts = new CopyOnWriteArrayList<>(); - usage.consumeIntegersWithTracing("amqp", - payloads::add, - contexts::add); - - weld.addBeanClass(MyAppGeneratingData.class); - new MapBasedConfig() - .put("mp.messaging.outgoing.amqp.connector", AmqpConnector.CONNECTOR_NAME) - .put("mp.messaging.outgoing.amqp.durable", false) - .put("mp.messaging.outgoing.amqp.host", host) - .put("mp.messaging.outgoing.amqp.port", port) - .put("amqp-username", username) - .put("amqp-password", password) + .with("mp.messaging.outgoing.amqp.connector", AmqpConnector.CONNECTOR_NAME) + .with("mp.messaging.outgoing.amqp.durable", false) + .with("mp.messaging.outgoing.amqp.host", host) + .with("mp.messaging.outgoing.amqp.port", port) + .with("amqp-username", username) + .with("amqp-password", password) .write(); + weld.addBeanClass(MyAppGeneratingData.class); container = weld.initialize(); - await().until(() -> isAmqpConnectorReady(container)); + List payloads = new CopyOnWriteArrayList<>(); + usage.consumeIntegers("amqp", payloads::add); + await().until(() -> payloads.size() >= 10); assertThat(payloads).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - assertThat(contexts).hasSize(10); - assertThat(contexts).doesNotContainNull().doesNotHaveDuplicates(); - - List spanIds = contexts.stream() - .map(context -> Span.fromContextOrNull(context).getSpanContext().getSpanId()) - .collect(Collectors.toList()); - assertThat(spanIds).doesNotContainNull().doesNotHaveDuplicates().hasSize(10); - - List traceIds = contexts.stream() - .map(context -> Span.fromContextOrNull(context).getSpanContext().getTraceId()) - .collect(Collectors.toList()); - assertThat(traceIds).doesNotContainNull().doesNotHaveDuplicates().hasSize(10); - - for (SpanData data : testExporter.getFinishedSpanItems()) { - assertThat(data.getSpanId()).isIn(spanIds); - assertThat(data.getSpanId()).isNotEqualTo(data.getParentSpanId()); - assertThat(data.getTraceId()).isIn(traceIds); - assertThat(data.getKind()).isEqualByComparingTo(SpanKind.PRODUCER); - } + CompletableResultCode completableResultCode = tracerProvider.forceFlush(); + completableResultCode.whenComplete(() -> { + List spans = spanExporter.getFinishedSpanItems(); + assertEquals(10, spans.size()); + }); } @ApplicationScoped public static class MyAppGeneratingData { - @Outgoing("amqp") public Publisher source() { return Multi.createFrom().range(0, 10); } } - } diff --git a/smallrye-reactive-messaging-kafka/pom.xml b/smallrye-reactive-messaging-kafka/pom.xml index da33fd9d8a..ba0a511e0c 100644 --- a/smallrye-reactive-messaging-kafka/pom.xml +++ b/smallrye-reactive-messaging-kafka/pom.xml @@ -43,6 +43,14 @@ io.opentelemetry opentelemetry-semconv + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-api + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-api-semconv + io.smallrye.config diff --git a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/IncomingKafkaRecord.java b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/IncomingKafkaRecord.java index 12a942ac3f..d88cb6fb27 100644 --- a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/IncomingKafkaRecord.java +++ b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/IncomingKafkaRecord.java @@ -10,14 +10,10 @@ import org.apache.kafka.common.header.Headers; import org.eclipse.microprofile.reactive.messaging.Metadata; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.context.Context; -import io.smallrye.reactive.messaging.TracingMetadata; import io.smallrye.reactive.messaging.ce.CloudEventMetadata; import io.smallrye.reactive.messaging.kafka.commit.KafkaCommitHandler; import io.smallrye.reactive.messaging.kafka.fault.KafkaFailureHandler; import io.smallrye.reactive.messaging.kafka.impl.ce.KafkaCloudEventHelper; -import io.smallrye.reactive.messaging.kafka.tracing.HeaderExtractAdapter; import io.smallrye.reactive.messaging.providers.locals.ContextAwareMessage; public class IncomingKafkaRecord implements KafkaRecord { @@ -67,18 +63,6 @@ public IncomingKafkaRecord(ConsumerRecord record, } } - if (tracingEnabled) { - TracingMetadata tracingMetadata = TracingMetadata.empty(); - if (record.headers() != null) { - // Read tracing headers - Context context = GlobalOpenTelemetry.getPropagators().getTextMapPropagator() - .extract(Context.root(), kafkaMetadata.getHeaders(), HeaderExtractAdapter.GETTER); - tracingMetadata = TracingMetadata.withPrevious(context); - } - - meta.add(tracingMetadata); - } - this.metadata = ContextAwareMessage.captureContextMetadata(meta); this.onNack = onNack; if (payload == null && !payloadSet) { diff --git a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/KafkaConnector.java b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/KafkaConnector.java index 262fa64edb..1f58f77adc 100644 --- a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/KafkaConnector.java +++ b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/KafkaConnector.java @@ -23,7 +23,7 @@ import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; -import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.Tracer; import io.smallrye.mutiny.Multi; import io.smallrye.reactive.messaging.annotations.ConnectorAttribute; @@ -118,7 +118,13 @@ public class KafkaConnector implements InboundConnector, OutboundConnector, Heal public static final String CONNECTOR_NAME = "smallrye-kafka"; - public static Tracer TRACER; + @Deprecated + public static Tracer TRACER = new Tracer() { + @Override + public SpanBuilder spanBuilder(final String spanName) { + throw new UnsupportedOperationException(); + } + }; @Inject ExecutionHolder executionHolder; @@ -165,7 +171,6 @@ public void terminate( @PostConstruct void init() { this.vertx = executionHolder.vertx(); - TRACER = GlobalOpenTelemetry.getTracerProvider().get("io.smallrye.reactive.messaging.kafka"); } @Override diff --git a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaRecordHelper.java b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaRecordHelper.java index f033fa59b6..d1cab93b82 100644 --- a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaRecordHelper.java +++ b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaRecordHelper.java @@ -1,28 +1,15 @@ package io.smallrye.reactive.messaging.kafka.impl; -import static io.smallrye.reactive.messaging.kafka.KafkaConnector.TRACER; - import java.util.Arrays; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import org.apache.kafka.common.header.Header; import org.apache.kafka.common.header.Headers; import org.apache.kafka.common.header.internals.RecordHeaders; -import org.eclipse.microprofile.reactive.messaging.Message; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanBuilder; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import io.smallrye.reactive.messaging.TracingMetadata; import io.smallrye.reactive.messaging.kafka.api.IncomingKafkaRecordMetadata; import io.smallrye.reactive.messaging.kafka.api.OutgoingKafkaRecordMetadata; -import io.smallrye.reactive.messaging.kafka.tracing.HeaderInjectAdapter; public class KafkaRecordHelper { @@ -52,41 +39,4 @@ public static Headers getHeaders(OutgoingKafkaRecordMetadata om, public static boolean isNotBlank(String s) { return s != null && !s.trim().isEmpty(); } - - public static void createOutgoingTrace(Message message, String topic, Integer partition, Headers headers) { - Optional tracingMetadata = TracingMetadata.fromMessage(message); - - final SpanBuilder spanBuilder = TRACER.spanBuilder(topic + " send") - .setSpanKind(SpanKind.PRODUCER); - - if (tracingMetadata.isPresent()) { - // Handle possible parent span - final Context parentSpanContext = tracingMetadata.get().getCurrentContext(); - if (parentSpanContext != null) { - spanBuilder.setParent(parentSpanContext); - } else { - spanBuilder.setNoParent(); - } - } else { - spanBuilder.setNoParent(); - } - - final Span span = spanBuilder.startSpan(); - Scope scope = span.makeCurrent(); - - // Set Span attributes - if (partition != null && partition != -1) { - span.setAttribute(SemanticAttributes.MESSAGING_KAFKA_PARTITION, partition); - } - span.setAttribute(SemanticAttributes.MESSAGING_SYSTEM, "kafka"); - span.setAttribute(SemanticAttributes.MESSAGING_DESTINATION, topic); - span.setAttribute(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"); - - // Set span onto headers - GlobalOpenTelemetry.getPropagators().getTextMapPropagator() - .inject(Context.current(), headers, HeaderInjectAdapter.SETTER); - span.end(); - scope.close(); - } - } diff --git a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaSink.java b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaSink.java index 79a63efedf..6c54035404 100644 --- a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaSink.java +++ b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaSink.java @@ -15,6 +15,7 @@ import javax.enterprise.inject.Instance; +import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.RecordMetadata; @@ -30,8 +31,18 @@ import org.eclipse.microprofile.reactive.messaging.Message; import org.reactivestreams.Subscriber; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessageOperation; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesGetter; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingSpanNameExtractor; import io.smallrye.mutiny.Uni; import io.smallrye.reactive.messaging.OutgoingMessageMetadata; +import io.smallrye.reactive.messaging.TracingMetadata; import io.smallrye.reactive.messaging.ce.OutgoingCloudEventMetadata; import io.smallrye.reactive.messaging.health.HealthReport; import io.smallrye.reactive.messaging.kafka.KafkaCDIEvents; @@ -43,7 +54,9 @@ import io.smallrye.reactive.messaging.kafka.api.OutgoingKafkaRecordMetadata; import io.smallrye.reactive.messaging.kafka.health.KafkaSinkHealth; import io.smallrye.reactive.messaging.kafka.impl.ce.KafkaCloudEventHelper; -import io.smallrye.reactive.messaging.providers.helpers.MultiUtils; +import io.smallrye.reactive.messaging.kafka.tracing.KafkaAttributesExtractor; +import io.smallrye.reactive.messaging.kafka.tracing.KafkaTrace; +import io.smallrye.reactive.messaging.kafka.tracing.KafkaTraceTextMapSetter; @SuppressWarnings("jol") public class KafkaSink { @@ -70,6 +83,8 @@ public class KafkaSink { private final RuntimeKafkaSinkConfiguration runtimeConfiguration; + private final Instrumenter instrumenter; + public KafkaSink(KafkaConnectorOutgoingConfiguration config, KafkaCDIEvents kafkaCDIEvents, Instance> serializationFailureHandlers) { this.isTracingEnabled = config.getTracingEnabled(); @@ -123,6 +138,18 @@ public KafkaSink(KafkaConnectorOutgoingConfiguration config, KafkaCDIEvents kafk reportFailure(f); })); + KafkaAttributesExtractor kafkaAttributesExtractor = new KafkaAttributesExtractor(); + MessagingAttributesGetter messagingAttributesGetter = kafkaAttributesExtractor + .getMessagingAttributesGetter(); + InstrumenterBuilder builder = Instrumenter.builder(GlobalOpenTelemetry.get(), + "io.smallrye.reactive.messaging", + MessagingSpanNameExtractor.create(messagingAttributesGetter, MessageOperation.SEND)); + + instrumenter = builder + .addAttributesExtractor(MessagingAttributesExtractor.create(messagingAttributesGetter, MessageOperation.SEND)) + .addAttributesExtractor(kafkaAttributesExtractor) + .buildProducerInstrumenter(KafkaTraceTextMapSetter.INSTANCE); + } private static String getClientId(Map config) { @@ -191,6 +218,36 @@ record = KafkaCloudEventHelper } else { record = getProducerRecord(message, outgoingMetadata, incomingMetadata, actualTopic); } + + if (isTracingEnabled) { + KafkaTrace kafkaTrace = new KafkaTrace.Builder() + .withPartition(record.partition() != null ? record.partition() : -1) + .withTopic(record.topic()) + .withHeaders(record.headers()) + .withGroupId(client.get(ConsumerConfig.GROUP_ID_CONFIG)) + .withClientId(client.get(ConsumerConfig.CLIENT_ID_CONFIG)) + .build(); + + Optional tracingMetadata = TracingMetadata.fromMessage(message); + + Context parentContext = tracingMetadata.map(TracingMetadata::getCurrentContext).orElse(Context.current()); + Context spanContext; + Scope scope = null; + + boolean shouldStart = instrumenter.shouldStart(parentContext, kafkaTrace); + if (shouldStart) { + try { + spanContext = instrumenter.start(parentContext, kafkaTrace); + scope = spanContext.makeCurrent(); + instrumenter.end(spanContext, kafkaTrace, null, null); + } finally { + if (scope != null) { + scope.close(); + } + } + } + } + log.sendingMessageToTopic(message, actualTopic); @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -265,9 +322,6 @@ private Optional> getOutgoingKafkaRecordMetadata( } Headers kafkaHeaders = KafkaRecordHelper.getHeaders(om, im, runtimeConfiguration); - if (isTracingEnabled) { - KafkaRecordHelper.createOutgoingTrace(message, actualTopic, actualPartition, kafkaHeaders); - } Object payload = message.getPayload(); if (payload instanceof Record) { payload = ((Record) payload).value(); diff --git a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaSource.java b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaSource.java index 98f01a4ddb..67bbfcc7d3 100644 --- a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaSource.java +++ b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaSource.java @@ -1,11 +1,14 @@ package io.smallrye.reactive.messaging.kafka.impl; -import static io.smallrye.reactive.messaging.kafka.KafkaConnector.TRACER; import static io.smallrye.reactive.messaging.kafka.i18n.KafkaExceptions.ex; import static io.smallrye.reactive.messaging.kafka.i18n.KafkaLogging.log; import java.time.Duration; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -17,20 +20,34 @@ import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.errors.RebalanceInProgressException; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanBuilder; -import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.context.Context; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessageOperation; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesGetter; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingSpanNameExtractor; import io.smallrye.common.annotation.Identifier; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; import io.smallrye.reactive.messaging.TracingMetadata; import io.smallrye.reactive.messaging.health.HealthReport; -import io.smallrye.reactive.messaging.kafka.*; -import io.smallrye.reactive.messaging.kafka.commit.*; +import io.smallrye.reactive.messaging.kafka.DeserializationFailureHandler; +import io.smallrye.reactive.messaging.kafka.IncomingKafkaRecord; +import io.smallrye.reactive.messaging.kafka.IncomingKafkaRecordBatch; +import io.smallrye.reactive.messaging.kafka.KafkaCDIEvents; +import io.smallrye.reactive.messaging.kafka.KafkaConnectorIncomingConfiguration; +import io.smallrye.reactive.messaging.kafka.KafkaConsumerRebalanceListener; +import io.smallrye.reactive.messaging.kafka.KafkaRecord; +import io.smallrye.reactive.messaging.kafka.commit.ContextHolder; +import io.smallrye.reactive.messaging.kafka.commit.KafkaCommitHandler; import io.smallrye.reactive.messaging.kafka.fault.KafkaFailureHandler; import io.smallrye.reactive.messaging.kafka.health.KafkaSourceHealth; +import io.smallrye.reactive.messaging.kafka.tracing.KafkaAttributesExtractor; +import io.smallrye.reactive.messaging.kafka.tracing.KafkaTrace; +import io.smallrye.reactive.messaging.kafka.tracing.KafkaTraceTextMapGetter; import io.vertx.core.impl.EventLoopContext; import io.vertx.core.impl.VertxInternal; import io.vertx.mutiny.core.Vertx; @@ -60,6 +77,8 @@ public class KafkaSource { private final ReactiveKafkaConsumer client; private final EventLoopContext context; + private final Instrumenter instrumenter; + public KafkaSource(Vertx vertx, String consumerGroup, KafkaConnectorIncomingConfiguration config, @@ -190,10 +209,18 @@ public KafkaSource(Vertx vertx, this.stream = null; } - } - - public Set getSubscribedTopics() { - return topics; + KafkaAttributesExtractor kafkaAttributesExtractor = new KafkaAttributesExtractor(); + MessagingAttributesGetter messagingAttributesGetter = kafkaAttributesExtractor + .getMessagingAttributesGetter(); + InstrumenterBuilder builder = Instrumenter.builder(GlobalOpenTelemetry.get(), + "io.smallrye.reactive.messaging", + MessagingSpanNameExtractor.create(messagingAttributesGetter, MessageOperation.RECEIVE)); + + instrumenter = builder + .addAttributesExtractor( + MessagingAttributesExtractor.create(messagingAttributesGetter, MessageOperation.RECEIVE)) + .addAttributesExtractor(kafkaAttributesExtractor) + .buildConsumerInstrumenter(KafkaTraceTextMapGetter.INSTANCE); } private Set getTopics(KafkaConnectorIncomingConfiguration config) { @@ -243,59 +270,46 @@ public synchronized void reportFailure(Throwable failure, boolean fatal) { } public void incomingTrace(IncomingKafkaRecord kafkaRecord, boolean insideBatch) { - if (isTracingEnabled && TRACER != null) { - TracingMetadata tracingMetadata = TracingMetadata.fromMessage(kafkaRecord).orElse(TracingMetadata.empty()); + if (isTracingEnabled) { + KafkaTrace kafkaTrace = new KafkaTrace.Builder() + .withPartition(kafkaRecord.getPartition()) + .withTopic(kafkaRecord.getTopic()) + .withHeaders(kafkaRecord.getHeaders()) + .withGroupId(client.get(ConsumerConfig.GROUP_ID_CONFIG)) + .withClientId(client.get(ConsumerConfig.CLIENT_ID_CONFIG)) + .build(); - final SpanBuilder spanBuilder = TRACER.spanBuilder(kafkaRecord.getTopic() + " receive") - .setSpanKind(SpanKind.CONSUMER); - - // Handle possible parent span - final Context parentSpanContext = tracingMetadata.getPreviousContext(); - if (parentSpanContext != null) { - spanBuilder.setParent(parentSpanContext); - } else { - spanBuilder.setNoParent(); + TracingMetadata tracingMetadata = TracingMetadata.fromMessage(kafkaRecord).orElse(TracingMetadata.empty()); + Context parentContext = tracingMetadata.getPreviousContext(); + if (parentContext == null) { + parentContext = Context.current(); } + Context spanContext; + Scope scope = null; + boolean shouldStart = instrumenter.shouldStart(parentContext, kafkaTrace); + + if (shouldStart) { + spanContext = instrumenter.start(parentContext, kafkaTrace); + if (!insideBatch) { + scope = spanContext.makeCurrent(); + } - final Span span = spanBuilder.startSpan(); - - // Set Span attributes - span.setAttribute(SemanticAttributes.MESSAGING_KAFKA_PARTITION, kafkaRecord.getPartition()); - span.setAttribute("offset", kafkaRecord.getOffset()); - span.setAttribute(SemanticAttributes.MESSAGING_SYSTEM, "kafka"); - span.setAttribute(SemanticAttributes.MESSAGING_DESTINATION, kafkaRecord.getTopic()); - span.setAttribute(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"); - - final String groupId = client.get(ConsumerConfig.GROUP_ID_CONFIG); - final String clientId = client.get(ConsumerConfig.CLIENT_ID_CONFIG); - span.setAttribute("messaging.consumer_id", constructConsumerId(groupId, clientId)); - span.setAttribute(SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, groupId); - if (!clientId.isEmpty()) { - span.setAttribute(SemanticAttributes.MESSAGING_KAFKA_CLIENT_ID, clientId); - } + kafkaRecord.injectMetadata(TracingMetadata.with(spanContext, parentContext)); - if (!insideBatch) { - // Make available as parent for subsequent spans inside message processing - span.makeCurrent(); + try { + instrumenter.end(spanContext, kafkaTrace, null, null); + } finally { + if (scope != null) { + scope.close(); + } + } } - - kafkaRecord.injectMetadata(tracingMetadata.withSpan(span)); - - span.end(); - } - } - - private String constructConsumerId(String groupId, String clientId) { - String consumerId = groupId; - if (!clientId.isEmpty()) { - consumerId += " - " + clientId; } - return consumerId; } @SuppressWarnings("unchecked") public void incomingTrace(IncomingKafkaRecordBatch kafkaBatchRecord) { - if (isTracingEnabled && TRACER != null) { + if (isTracingEnabled) { for (KafkaRecord record : kafkaBatchRecord.getRecords()) { IncomingKafkaRecord kafkaRecord = record.unwrap(IncomingKafkaRecord.class); incomingTrace(kafkaRecord, true); diff --git a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/ce/KafkaCloudEventHelper.java b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/ce/KafkaCloudEventHelper.java index 9b2e1345de..82b273d1a0 100644 --- a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/ce/KafkaCloudEventHelper.java +++ b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/ce/KafkaCloudEventHelper.java @@ -216,9 +216,7 @@ public static IncomingKafkaCloudEventMetadata createFromBinaryCloud Object key = getKey(message, outgoingMetadata, ceMetadata, configuration); Long timestamp = getTimestamp(outgoingMetadata); Headers headers = KafkaRecordHelper.getHeaders(outgoingMetadata, incomingMetadata, configuration); - if (configuration.getTracingEnabled()) { - KafkaRecordHelper.createOutgoingTrace(message, topic, partition, headers); - } + Optional subject = getSubject(ceMetadata, configuration); Optional contentType = getDataContentType(ceMetadata, configuration); Optional schema = getDataSchema(ceMetadata, configuration); @@ -382,9 +380,7 @@ private static Integer getPartition(OutgoingKafkaRecordMetadata metadata, Object key = getKey(message, outgoingMetadata, ceMetadata, configuration); Long timestamp = getTimestamp(outgoingMetadata); Headers headers = KafkaRecordHelper.getHeaders(outgoingMetadata, incomingMetadata, configuration); - if (configuration.getTracingEnabled()) { - KafkaRecordHelper.createOutgoingTrace(message, topic, partition, headers); - } + String source = getSource(ceMetadata, configuration); String type = getType(ceMetadata, configuration); Optional subject = getSubject(ceMetadata, configuration); diff --git a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/HeaderExtractAdapter.java b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/HeaderExtractAdapter.java deleted file mode 100644 index f83c01685a..0000000000 --- a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/HeaderExtractAdapter.java +++ /dev/null @@ -1,43 +0,0 @@ -package io.smallrye.reactive.messaging.kafka.tracing; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.stream.Collectors; - -import org.apache.kafka.common.header.Header; -import org.apache.kafka.common.header.Headers; - -import io.opentelemetry.context.propagation.TextMapGetter; - -public class HeaderExtractAdapter implements TextMapGetter { - public static final HeaderExtractAdapter GETTER = new HeaderExtractAdapter(); - - private Iterable keys; - - @Override - public Iterable keys(Headers headers) { - if (keys == null) { - keys = Arrays.stream(headers.toArray()) - .map(Header::key) - .collect(Collectors.toList()); - } - - return keys; - } - - @Override - public String get(Headers headers, String key) { - if (headers == null) { - return null; - } - final Header header = headers.lastHeader(key); - if (header == null) { - return null; - } - byte[] headerValue = header.value(); - if (headerValue == null) { - return null; - } - return new String(headerValue, StandardCharsets.UTF_8); - } -} diff --git a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/HeaderInjectAdapter.java b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/HeaderInjectAdapter.java deleted file mode 100644 index f07ef43b0e..0000000000 --- a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/HeaderInjectAdapter.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.smallrye.reactive.messaging.kafka.tracing; - -import java.nio.charset.StandardCharsets; - -import org.apache.kafka.common.header.Headers; - -import io.opentelemetry.context.propagation.TextMapSetter; - -public class HeaderInjectAdapter implements TextMapSetter { - public static final HeaderInjectAdapter SETTER = new HeaderInjectAdapter(); - - @Override - public void set(Headers headers, String key, String value) { - if (headers != null) { - headers.remove(key).add(key, value.getBytes(StandardCharsets.UTF_8)); - } - } -} diff --git a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/KafkaAttributesExtractor.java b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/KafkaAttributesExtractor.java new file mode 100644 index 0000000000..3cce67f2dd --- /dev/null +++ b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/KafkaAttributesExtractor.java @@ -0,0 +1,114 @@ +package io.smallrye.reactive.messaging.kafka.tracing; + +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_CONSUMER_ID; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_KAFKA_CLIENT_ID; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_KAFKA_PARTITION; + +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesGetter; +import io.smallrye.reactive.messaging.kafka.KafkaConnector; + +public class KafkaAttributesExtractor implements AttributesExtractor { + private final MessagingAttributesGetter messagingAttributesGetter; + + public KafkaAttributesExtractor() { + this.messagingAttributesGetter = new KafkaMessagingAttributesGetter(); + } + + @Override + public void onStart(final AttributesBuilder attributes, final Context parentContext, final KafkaTrace kafkaTrace) { + if (kafkaTrace.getPartition() != -1) { + attributes.put(MESSAGING_KAFKA_PARTITION, kafkaTrace.getPartition()); + } + + String groupId = kafkaTrace.getGroupId(); + String clientId = kafkaTrace.getClientId(); + if (groupId != null && clientId != null) { + String consumerId = groupId; + if (!clientId.isEmpty()) { + consumerId += " - " + clientId; + } + attributes.put(MESSAGING_CONSUMER_ID, consumerId); + } + if (groupId != null) { + attributes.put(MESSAGING_KAFKA_CONSUMER_GROUP, groupId); + } + if (clientId != null) { + attributes.put(MESSAGING_KAFKA_CLIENT_ID, clientId); + } + } + + @Override + public void onEnd( + final AttributesBuilder attributes, + final Context context, + final KafkaTrace kafkaTrace, + final Void unused, + final Throwable error) { + + } + + public MessagingAttributesGetter getMessagingAttributesGetter() { + return messagingAttributesGetter; + } + + private static final class KafkaMessagingAttributesGetter implements MessagingAttributesGetter { + @Override + public String system(final KafkaTrace kafkaTrace) { + return KafkaConnector.CONNECTOR_NAME; + } + + @Override + public String destinationKind(final KafkaTrace kafkaTrace) { + return "topic"; + } + + @Override + public String destination(final KafkaTrace kafkaTrace) { + return kafkaTrace.getTopic(); + } + + @Override + public boolean temporaryDestination(final KafkaTrace kafkaTrace) { + return false; + } + + @Override + public String protocol(final KafkaTrace kafkaTrace) { + return null; + } + + @Override + public String protocolVersion(final KafkaTrace kafkaTrace) { + return null; + } + + @Override + public String url(final KafkaTrace kafkaTrace) { + return null; + } + + @Override + public String conversationId(final KafkaTrace kafkaTrace) { + return null; + } + + @Override + public Long messagePayloadSize(final KafkaTrace kafkaTrace) { + return null; + } + + @Override + public Long messagePayloadCompressedSize(final KafkaTrace kafkaTrace) { + return null; + } + + @Override + public String messageId(final KafkaTrace kafkaTrace, final Void unused) { + return null; + } + } +} diff --git a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/KafkaTrace.java b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/KafkaTrace.java new file mode 100644 index 0000000000..61109a3e3d --- /dev/null +++ b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/KafkaTrace.java @@ -0,0 +1,77 @@ +package io.smallrye.reactive.messaging.kafka.tracing; + +import org.apache.kafka.common.header.Headers; + +public class KafkaTrace { + private final String groupId; + private final String clientId; + private final int partition; + private final String topic; + private final Headers headers; + + private KafkaTrace(final String groupId, final String clientId, final int partition, final String topic, + final Headers headers) { + this.groupId = groupId; + this.clientId = clientId; + this.partition = partition; + this.topic = topic; + this.headers = headers; + } + + public String getGroupId() { + return groupId; + } + + public String getClientId() { + return clientId; + } + + public int getPartition() { + return partition; + } + + public String getTopic() { + return topic; + } + + public Headers getHeaders() { + return headers; + } + + public static class Builder { + private String groupId; + private String clientId; + private int partition; + private String topic; + private Headers headers; + + public Builder withGroupId(final String groupId) { + this.groupId = groupId; + return this; + } + + public Builder withClientId(final String clientId) { + this.clientId = clientId; + return this; + } + + public Builder withPartition(final int partition) { + this.partition = partition; + return this; + } + + public Builder withTopic(final String topic) { + this.topic = topic; + return this; + } + + public Builder withHeaders(final Headers headers) { + this.headers = headers; + return this; + } + + public KafkaTrace build() { + return new KafkaTrace(groupId, clientId, partition, topic, headers); + } + } +} diff --git a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/KafkaTraceTextMapGetter.java b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/KafkaTraceTextMapGetter.java new file mode 100644 index 0000000000..de6bc1a645 --- /dev/null +++ b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/KafkaTraceTextMapGetter.java @@ -0,0 +1,38 @@ +package io.smallrye.reactive.messaging.kafka.tracing; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import org.apache.kafka.common.header.Header; +import org.apache.kafka.common.header.Headers; + +import io.opentelemetry.context.propagation.TextMapGetter; + +public enum KafkaTraceTextMapGetter implements TextMapGetter { + INSTANCE; + + @Override + public Iterable keys(final KafkaTrace carrier) { + List keys = new ArrayList<>(); + Headers headers = carrier.getHeaders(); + for (Header header : headers) { + keys.add(header.key()); + } + return keys; + } + + @Override + public String get(final KafkaTrace carrier, final String key) { + if (carrier != null) { + Header header = carrier.getHeaders().lastHeader(key); + if (header != null) { + byte[] value = header.value(); + if (value != null) { + return new String(value, StandardCharsets.UTF_8); + } + } + } + return null; + } +} diff --git a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/KafkaTraceTextMapSetter.java b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/KafkaTraceTextMapSetter.java new file mode 100644 index 0000000000..b2f277374b --- /dev/null +++ b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/tracing/KafkaTraceTextMapSetter.java @@ -0,0 +1,17 @@ +package io.smallrye.reactive.messaging.kafka.tracing; + +import org.apache.kafka.common.header.Headers; + +import io.opentelemetry.context.propagation.TextMapSetter; + +public enum KafkaTraceTextMapSetter implements TextMapSetter { + INSTANCE; + + @Override + public void set(final KafkaTrace carrier, final String key, final String value) { + if (carrier != null) { + Headers headers = carrier.getHeaders(); + headers.add(key, value.getBytes()); + } + } +} diff --git a/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/client/PauseResumeTest.java b/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/client/PauseResumeTest.java index 5e959bf959..b68dfc0259 100644 --- a/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/client/PauseResumeTest.java +++ b/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/client/PauseResumeTest.java @@ -19,7 +19,6 @@ import org.apache.kafka.common.serialization.StringDeserializer; import org.junit.jupiter.api.*; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.helpers.test.AssertSubscriber; import io.smallrye.reactive.messaging.kafka.*; @@ -37,10 +36,9 @@ public class PauseResumeTest extends WeldTestBase { private KafkaSource source; @BeforeEach - public void initializing() { + public void setup() { vertx = Vertx.vertx(); consumer = new MockConsumer<>(OffsetResetStrategy.EARLIEST); - KafkaConnector.TRACER = GlobalOpenTelemetry.getTracerProvider().get("io.smallrye.reactive.messaging.kafka"); } @AfterEach diff --git a/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/commit/DeprecatedCommitStrategiesTest.java b/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/commit/DeprecatedCommitStrategiesTest.java index 551034c6dc..4fc2f41ff6 100644 --- a/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/commit/DeprecatedCommitStrategiesTest.java +++ b/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/commit/DeprecatedCommitStrategiesTest.java @@ -36,11 +36,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.smallrye.reactive.messaging.health.HealthReport; import io.smallrye.reactive.messaging.kafka.CountKafkaCdiEvents; import io.smallrye.reactive.messaging.kafka.DeserializationFailureHandler; -import io.smallrye.reactive.messaging.kafka.KafkaConnector; import io.smallrye.reactive.messaging.kafka.KafkaConnectorIncomingConfiguration; import io.smallrye.reactive.messaging.kafka.KafkaConsumerRebalanceListener; import io.smallrye.reactive.messaging.kafka.LegacyMetadataTestUtils; @@ -63,9 +61,8 @@ public class DeprecatedCommitStrategiesTest extends WeldTestBase { private KafkaSource source2; @BeforeEach - public void initializing() { + public void setup() { vertx = Vertx.vertx(); - KafkaConnector.TRACER = GlobalOpenTelemetry.getTracerProvider().get("io.smallrye.reactive.messaging.kafka"); consumer = new MockConsumer<>(OffsetResetStrategy.EARLIEST); } diff --git a/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/serde/KeyDeserializerConfigurationTest.java b/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/serde/KeyDeserializerConfigurationTest.java index d5f4d32c62..e64504dc97 100644 --- a/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/serde/KeyDeserializerConfigurationTest.java +++ b/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/serde/KeyDeserializerConfigurationTest.java @@ -19,13 +19,10 @@ import org.apache.kafka.common.serialization.StringDeserializer; import org.eclipse.microprofile.reactive.messaging.Message; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.smallrye.reactive.messaging.kafka.CountKafkaCdiEvents; import io.smallrye.reactive.messaging.kafka.DeserializationFailureHandler; -import io.smallrye.reactive.messaging.kafka.KafkaConnector; import io.smallrye.reactive.messaging.kafka.KafkaConnectorIncomingConfiguration; import io.smallrye.reactive.messaging.kafka.KafkaRecord; import io.smallrye.reactive.messaging.kafka.base.DoubleInstance; @@ -42,11 +39,6 @@ public class KeyDeserializerConfigurationTest extends KafkaCompanionTestBase { private KafkaSource source; - @BeforeAll - static void initTracer() { - KafkaConnector.TRACER = GlobalOpenTelemetry.getTracerProvider().get("io.smallrye.reactive.messaging.kafka"); - } - @AfterEach public void cleanup() { if (source != null) { diff --git a/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/serde/ValueDeserializerConfigurationTest.java b/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/serde/ValueDeserializerConfigurationTest.java index ac59fd441f..25413ccb65 100644 --- a/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/serde/ValueDeserializerConfigurationTest.java +++ b/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/serde/ValueDeserializerConfigurationTest.java @@ -23,11 +23,9 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.smallrye.reactive.messaging.health.HealthReport; import io.smallrye.reactive.messaging.kafka.CountKafkaCdiEvents; import io.smallrye.reactive.messaging.kafka.DeserializationFailureHandler; -import io.smallrye.reactive.messaging.kafka.KafkaConnector; import io.smallrye.reactive.messaging.kafka.KafkaConnectorIncomingConfiguration; import io.smallrye.reactive.messaging.kafka.KafkaRecord; import io.smallrye.reactive.messaging.kafka.base.DoubleInstance; @@ -45,8 +43,7 @@ public class ValueDeserializerConfigurationTest extends KafkaCompanionTestBase { private KafkaSource source; @BeforeAll - static void initTracer() { - KafkaConnector.TRACER = GlobalOpenTelemetry.getTracerProvider().get("io.smallrye.reactive.messaging.kafka"); + static void setup() { companion.registerSerde(JsonObject.class, Serdes.serdeFrom(new JsonObjectSerde.JsonObjectSerializer(), new JsonObjectSerde.JsonObjectDeserializer())); } diff --git a/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/tracing/BatchTracingPropagationTest.java b/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/tracing/BatchTracingPropagationTest.java index b12b855cf1..1f23c2d1b8 100644 --- a/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/tracing/BatchTracingPropagationTest.java +++ b/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/tracing/BatchTracingPropagationTest.java @@ -3,19 +3,17 @@ import static io.smallrye.reactive.messaging.kafka.companion.RecordQualifiers.until; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; -import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletionStage; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.stream.Collectors; import javax.enterprise.context.ApplicationScoped; import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.common.header.Header; import org.apache.kafka.common.header.internals.RecordHeaders; import org.apache.kafka.common.serialization.IntegerDeserializer; import org.apache.kafka.common.serialization.IntegerSerializer; @@ -23,19 +21,20 @@ import org.eclipse.microprofile.reactive.messaging.Incoming; import org.eclipse.microprofile.reactive.messaging.Outgoing; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SpanProcessor; @@ -43,27 +42,23 @@ import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.samplers.Sampler; import io.smallrye.mutiny.Multi; -import io.smallrye.reactive.messaging.TracingMetadata; -import io.smallrye.reactive.messaging.kafka.KafkaConnector; -import io.smallrye.reactive.messaging.kafka.KafkaRecord; import io.smallrye.reactive.messaging.kafka.KafkaRecordBatch; import io.smallrye.reactive.messaging.kafka.base.KafkaCompanionTestBase; import io.smallrye.reactive.messaging.kafka.base.KafkaMapBasedConfig; import io.smallrye.reactive.messaging.kafka.companion.ConsumerTask; public class BatchTracingPropagationTest extends KafkaCompanionTestBase { - - private InMemorySpanExporter testExporter; - private SpanProcessor spanProcessor; + private SdkTracerProvider tracerProvider; + private InMemorySpanExporter spanExporter; @BeforeEach public void setup() { GlobalOpenTelemetry.resetForTest(); - testExporter = InMemorySpanExporter.create(); - spanProcessor = SimpleSpanProcessor.create(testExporter); + spanExporter = InMemorySpanExporter.create(); + SpanProcessor spanProcessor = SimpleSpanProcessor.create(spanExporter); - SdkTracerProvider tracerProvider = SdkTracerProvider.builder() + tracerProvider = SdkTracerProvider.builder() .addSpanProcessor(spanProcessor) .setSampler(Sampler.alwaysOn()) .build(); @@ -74,16 +69,6 @@ public void setup() { .buildAndRegisterGlobal(); } - @AfterEach - public void cleanup() { - if (testExporter != null) { - testExporter.shutdown(); - } - if (spanProcessor != null) { - spanProcessor.shutdown(); - } - } - @AfterAll static void shutdown() { GlobalOpenTelemetry.resetForTest(); @@ -92,13 +77,8 @@ static void shutdown() { @SuppressWarnings("ConstantConditions") @Test public void testFromAppToKafka() { - List contexts = new CopyOnWriteArrayList<>(); ConsumerTask consumed = companion.consumeIntegers().fromTopics(topic, - m -> m.plug(until(10L, Duration.ofMinutes(1), null)) - .onItem().invoke(record -> { - contexts.add(GlobalOpenTelemetry.getPropagators().getTextMapPropagator() - .extract(Context.current(), record.headers(), new HeaderExtractAdapter())); - })); + m -> m.plug(until(10L, Duration.ofMinutes(1), null))); runApplication(getKafkaSinkConfigForMyAppGeneratingData(), MyAppGeneratingData.class); await().until(() -> consumed.getRecords().size() >= 10); @@ -109,87 +89,39 @@ public void testFromAppToKafka() { }); assertThat(values).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - assertThat(contexts).hasSize(10); - assertThat(contexts).doesNotContainNull().doesNotHaveDuplicates(); - - List spanIds = contexts.stream() - .map(context -> Span.fromContextOrNull(context).getSpanContext().getSpanId()) - .collect(Collectors.toList()); - assertThat(spanIds).doesNotContainNull().doesNotHaveDuplicates().hasSize(10); - - List traceIds = contexts.stream() - .map(context -> Span.fromContextOrNull(context).getSpanContext().getTraceId()) - .collect(Collectors.toList()); - assertThat(traceIds).doesNotContainNull().doesNotHaveDuplicates().hasSize(10); - - for (SpanData data : testExporter.getFinishedSpanItems()) { - assertThat(data.getSpanId()).isIn(spanIds); - assertThat(data.getSpanId()).isNotEqualTo(data.getParentSpanId()); - assertThat(data.getTraceId()).isIn(traceIds); - assertThat(data.getKind()).isEqualByComparingTo(SpanKind.PRODUCER); - } + CompletableResultCode completableResultCode = tracerProvider.forceFlush(); + completableResultCode.whenComplete(() -> { + List spans = spanExporter.getFinishedSpanItems(); + assertEquals(10, spans.size()); + }); } @Test public void testFromKafkaToAppWithParentSpan() { String parentTopic = topic + "-parent"; - String stuffTopic = topic + "-stuff"; MyAppReceivingData bean = runApplication(getKafkaSinkConfigForMyAppReceivingData(parentTopic), MyAppReceivingData.class); - List producedSpanContexts = new CopyOnWriteArrayList<>(); - - companion.produceIntegers().usingGenerator(i -> new ProducerRecord<>(parentTopic, null, null, "a-key", i, - createTracingSpan(producedSpanContexts, stuffTopic)), 10); - - await().until(() -> bean.list().size() >= 10); - assertThat(bean.list()).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - - List producedTraceIds = producedSpanContexts.stream() - .map(SpanContext::getTraceId) - .collect(Collectors.toList()); - assertThat(producedTraceIds).hasSize(10); - - assertThat(bean.tracing()).hasSizeGreaterThanOrEqualTo(10); - assertThat(bean.tracing()).doesNotContainNull().doesNotHaveDuplicates(); - - List receivedTraceIds = bean.tracing().stream() - .map(tracingMetadata -> Span.fromContext(tracingMetadata.getCurrentContext()).getSpanContext().getTraceId()) - .collect(Collectors.toList()); - assertThat(receivedTraceIds).doesNotContainNull().doesNotHaveDuplicates().hasSize(10); - assertThat(receivedTraceIds).containsExactlyInAnyOrderElementsOf(producedTraceIds); - - List spanIds = new ArrayList<>(); - - for (TracingMetadata tracing : bean.tracing()) { - spanIds.add(Span.fromContext(tracing.getCurrentContext()).getSpanContext().getSpanId()); - - assertThat(tracing.getPreviousContext()).isNotNull(); - Span previousSpan = Span.fromContextOrNull(tracing.getPreviousContext()); - assertThat(previousSpan).isNotNull(); - assertThat(previousSpan.getSpanContext().getTraceId()) - .isEqualTo(Span.fromContext(tracing.getCurrentContext()).getSpanContext().getTraceId()); - assertThat(previousSpan.getSpanContext().getSpanId()) - .isNotEqualTo(Span.fromContext(tracing.getCurrentContext()).getSpanContext().getSpanId()); + RecordHeaders headers = new RecordHeaders(); + try (Scope ignored = Context.current().makeCurrent()) { + Tracer tracer = GlobalOpenTelemetry.getTracerProvider().get("io.smallrye.reactive.messaging"); + Span span = tracer.spanBuilder("producer").setSpanKind(SpanKind.PRODUCER).startSpan(); + Context current = Context.current().with(span); + GlobalOpenTelemetry.getPropagators() + .getTextMapPropagator() + .inject(current, headers, (carrier, key, value) -> carrier.add(key, value.getBytes())); + span.end(); } + companion.produceIntegers().usingGenerator(i -> new ProducerRecord<>(parentTopic, null, null, "a-key", i, headers), 10); - assertThat(spanIds).doesNotContainNull().doesNotHaveDuplicates().hasSizeGreaterThanOrEqualTo(10); - - List parentIds = bean.tracing().stream() - .map(tracingMetadata -> Span.fromContextOrNull(tracingMetadata.getPreviousContext()) - .getSpanContext().getSpanId()) - .collect(Collectors.toList()); + await().until(() -> bean.records().size() >= 10); + assertThat(bean.records()).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - assertThat(producedSpanContexts.stream() - .map(SpanContext::getSpanId)).containsExactlyElementsOf(parentIds); - - for (SpanData data : testExporter.getFinishedSpanItems()) { - assertThat(data.getSpanId()).isIn(spanIds); - assertThat(data.getSpanId()).isNotEqualTo(data.getParentSpanId()); - assertThat(data.getKind()).isEqualByComparingTo(SpanKind.CONSUMER); - assertThat(data.getParentSpanId()).isNotNull(); - assertThat(data.getParentSpanId()).isIn(parentIds); - } + CompletableResultCode completableResultCode = tracerProvider.forceFlush(); + completableResultCode.whenComplete(() -> { + List spans = spanExporter.getFinishedSpanItems(); + assertEquals(11, spans.size()); + }); } @Test @@ -199,40 +131,14 @@ public void testFromKafkaToAppWithNoParent() { companion.produceIntegers().usingGenerator(i -> new ProducerRecord<>(topic, null, null, "a-key", i), 10); - await().until(() -> bean.list().size() >= 10); - assertThat(bean.list()).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - - assertThat(bean.tracing()).hasSizeGreaterThanOrEqualTo(10); - assertThat(bean.tracing()).doesNotContainNull().doesNotHaveDuplicates(); - List spanIds = new ArrayList<>(); - - for (TracingMetadata tracing : bean.tracing()) { - spanIds.add(Span.fromContext(tracing.getCurrentContext()).getSpanContext().getSpanId()); - assertThat(Span.fromContextOrNull(tracing.getPreviousContext())).isNull(); - } - - assertThat(spanIds).doesNotContainNull().doesNotHaveDuplicates().hasSizeGreaterThanOrEqualTo(10); - - for (SpanData data : testExporter.getFinishedSpanItems()) { - assertThat(data.getSpanId()).isIn(spanIds); - assertThat(data.getSpanId()).isNotEqualTo(data.getParentSpanId()); - assertThat(data.getKind()).isEqualByComparingTo(SpanKind.CONSUMER); - } - } + await().until(() -> bean.records().size() >= 10); + assertThat(bean.records()).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - private Iterable
createTracingSpan(List spanContexts, String topic) { - RecordHeaders proposedHeaders = new RecordHeaders(); - final Span span = KafkaConnector.TRACER.spanBuilder(topic).setSpanKind(SpanKind.PRODUCER).startSpan(); - final Context context = span.storeInContext(Context.current()); - GlobalOpenTelemetry.getPropagators() - .getTextMapPropagator() - .inject(context, proposedHeaders, (headers, key, value) -> { - if (headers != null) { - headers.remove(key).add(key, value.getBytes(StandardCharsets.UTF_8)); - } - }); - spanContexts.add(span.getSpanContext()); - return proposedHeaders; + CompletableResultCode completableResultCode = tracerProvider.forceFlush(); + completableResultCode.whenComplete(() -> { + List spans = spanExporter.getFinishedSpanItems(); + assertEquals(10, spans.size()); + }); } private KafkaMapBasedConfig getKafkaSinkConfigForMyAppGeneratingData() { @@ -262,25 +168,16 @@ public Publisher source() { @ApplicationScoped public static class MyAppReceivingData { - private final List tracingMetadata = new ArrayList<>(); private final List results = new CopyOnWriteArrayList<>(); @Incoming("stuff") public CompletionStage consume(KafkaRecordBatch batchInput) { results.addAll(batchInput.getPayload()); - for (KafkaRecord record : batchInput) { - tracingMetadata.add(record.getMetadata(TracingMetadata.class).orElse(TracingMetadata.empty())); - } return batchInput.ack(); } - public List list() { + public List records() { return results; } - - public List tracing() { - return tracingMetadata; - } } - } diff --git a/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/tracing/HeaderExtractAdapterTest.java b/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/tracing/HeaderExtractAdapterTest.java index 9a7e5ce300..45f6dd85c9 100644 --- a/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/tracing/HeaderExtractAdapterTest.java +++ b/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/tracing/HeaderExtractAdapterTest.java @@ -11,9 +11,10 @@ class HeaderExtractAdapterTest { public void verifyNullHeaderHandled() { Headers headers = new RecordHeaders(); headers.add("test_null_header", null); - HeaderExtractAdapter headerExtractAdapter = new HeaderExtractAdapter(); - final String headerValue = headerExtractAdapter.get(headers, "test_null_header"); + KafkaTrace kafkaTrace = new KafkaTrace.Builder().withHeaders(headers).build(); + + String headerValue = KafkaTraceTextMapGetter.INSTANCE.get(kafkaTrace, "test_null_header"); assertThat(headerValue).isNull(); } diff --git a/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/tracing/TracingPropagationTest.java b/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/tracing/TracingPropagationTest.java index f7878b5c2f..164738e037 100644 --- a/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/tracing/TracingPropagationTest.java +++ b/smallrye-reactive-messaging-kafka/src/test/java/io/smallrye/reactive/messaging/kafka/tracing/TracingPropagationTest.java @@ -1,8 +1,13 @@ package io.smallrye.reactive.messaging.kafka.tracing; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_DESTINATION; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_DESTINATION_KIND; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_SYSTEM; import static io.smallrye.reactive.messaging.kafka.companion.RecordQualifiers.until; +import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.net.URI; import java.nio.charset.StandardCharsets; @@ -16,7 +21,6 @@ import javax.enterprise.context.ApplicationScoped; import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.common.header.Header; import org.apache.kafka.common.header.internals.RecordHeaders; import org.apache.kafka.common.serialization.IntegerDeserializer; import org.apache.kafka.common.serialization.IntegerSerializer; @@ -26,31 +30,29 @@ import org.eclipse.microprofile.reactive.messaging.Message; import org.eclipse.microprofile.reactive.messaging.Outgoing; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.SpanId; import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SpanProcessor; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.samplers.Sampler; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import io.smallrye.mutiny.Multi; -import io.smallrye.reactive.messaging.TracingMetadata; import io.smallrye.reactive.messaging.ce.OutgoingCloudEventMetadata; -import io.smallrye.reactive.messaging.kafka.KafkaConnector; import io.smallrye.reactive.messaging.kafka.base.KafkaCompanionTestBase; import io.smallrye.reactive.messaging.kafka.base.KafkaMapBasedConfig; import io.smallrye.reactive.messaging.kafka.companion.ConsumerTask; @@ -59,18 +61,17 @@ import io.vertx.core.json.JsonObject; public class TracingPropagationTest extends KafkaCompanionTestBase { - - private InMemorySpanExporter testExporter; - private SpanProcessor spanProcessor; + private SdkTracerProvider tracerProvider; + private InMemorySpanExporter spanExporter; @BeforeEach public void setup() { GlobalOpenTelemetry.resetForTest(); - testExporter = InMemorySpanExporter.create(); - spanProcessor = SimpleSpanProcessor.create(testExporter); + spanExporter = InMemorySpanExporter.create(); + SpanProcessor spanProcessor = SimpleSpanProcessor.create(spanExporter); - SdkTracerProvider tracerProvider = SdkTracerProvider.builder() + tracerProvider = SdkTracerProvider.builder() .addSpanProcessor(spanProcessor) .setSampler(Sampler.alwaysOn()) .build(); @@ -81,16 +82,6 @@ public void setup() { .buildAndRegisterGlobal(); } - @AfterEach - public void cleanup() { - if (testExporter != null) { - testExporter.shutdown(); - } - if (spanProcessor != null) { - spanProcessor.shutdown(); - } - } - @AfterAll static void shutdown() { GlobalOpenTelemetry.resetForTest(); @@ -99,14 +90,8 @@ static void shutdown() { @SuppressWarnings("ConstantConditions") @Test public void testFromAppToKafka() { - List contexts = new CopyOnWriteArrayList<>(); - ConsumerTask consumed = companion.consumeIntegers().fromTopics(topic, - m -> m.plug(until(10L, Duration.ofMinutes(1), null)) - .onItem().invoke(record -> { - contexts.add(GlobalOpenTelemetry.getPropagators().getTextMapPropagator() - .extract(Context.current(), record.headers(), new HeaderExtractAdapter())); - })); + m -> m.plug(until(10L, Duration.ofMinutes(1), null))); runApplication(getKafkaSinkConfigForMyAppGeneratingData(), MyAppGeneratingData.class); await().until(() -> consumed.getRecords().size() >= 10); @@ -117,37 +102,26 @@ public void testFromAppToKafka() { }); assertThat(values).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - assertThat(contexts).hasSize(10); - assertThat(contexts).doesNotContainNull().doesNotHaveDuplicates(); - - List spanIds = contexts.stream() - .map(context -> Span.fromContextOrNull(context).getSpanContext().getSpanId()) - .collect(Collectors.toList()); - assertThat(spanIds).doesNotContainNull().doesNotHaveDuplicates().hasSize(10); + CompletableResultCode completableResultCode = tracerProvider.forceFlush(); + completableResultCode.whenComplete(() -> { + List spans = spanExporter.getFinishedSpanItems(); + assertEquals(10, spans.size()); - List traceIds = contexts.stream() - .map(context -> Span.fromContextOrNull(context).getSpanContext().getTraceId()) - .collect(Collectors.toList()); - assertThat(traceIds).doesNotContainNull().doesNotHaveDuplicates().hasSize(10); + assertEquals(10, spans.stream().map(SpanData::getTraceId).collect(Collectors.toSet()).size()); - for (SpanData data : testExporter.getFinishedSpanItems()) { - assertThat(data.getSpanId()).isIn(spanIds); - assertThat(data.getSpanId()).isNotEqualTo(data.getParentSpanId()); - assertThat(data.getTraceId()).isIn(traceIds); - assertThat(data.getKind()).isEqualByComparingTo(SpanKind.PRODUCER); - } + SpanData span = spans.get(0); + assertEquals(SpanKind.PRODUCER, span.getKind()); + assertEquals("smallrye-kafka", span.getAttributes().get(MESSAGING_SYSTEM)); + assertEquals("topic", span.getAttributes().get(MESSAGING_DESTINATION_KIND)); + assertEquals(topic, span.getAttributes().get(MESSAGING_DESTINATION)); + assertEquals(topic + " send", span.getName()); + }); } @Test public void testFromAppToKafkaWithStructuredCloudEvents() { - List contexts = new CopyOnWriteArrayList<>(); - ConsumerTask consumed = companion.consumeStrings().fromTopics(topic, - m -> m.plug(until(10L, Duration.ofMinutes(1), null)) - .onItem().invoke(record -> { - contexts.add(GlobalOpenTelemetry.getPropagators().getTextMapPropagator() - .extract(Context.current(), record.headers(), new HeaderExtractAdapter())); - })); + m -> m.plug(until(10L, Duration.ofMinutes(1), null))); runApplication(getKafkaSinkConfigForMyAppGeneratingDataWithStructuredCloudEvent("structured"), MyAppGeneratingCloudEventData.class); @@ -162,37 +136,26 @@ public void testFromAppToKafkaWithStructuredCloudEvents() { }); assertThat(values).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - assertThat(contexts).hasSize(10); - assertThat(contexts).doesNotContainNull().doesNotHaveDuplicates(); + CompletableResultCode completableResultCode = tracerProvider.forceFlush(); + completableResultCode.whenComplete(() -> { + List spans = spanExporter.getFinishedSpanItems(); + assertEquals(10, spans.size()); - List spanIds = contexts.stream() - .map(context -> Span.fromContextOrNull(context).getSpanContext().getSpanId()) - .collect(Collectors.toList()); - assertThat(spanIds).doesNotContainNull().doesNotHaveDuplicates().hasSize(10); + assertEquals(10, spans.stream().map(SpanData::getTraceId).collect(Collectors.toSet()).size()); - List traceIds = contexts.stream() - .map(context -> Span.fromContextOrNull(context).getSpanContext().getTraceId()) - .collect(Collectors.toList()); - assertThat(traceIds).doesNotContainNull().doesNotHaveDuplicates().hasSize(10); - - for (SpanData data : testExporter.getFinishedSpanItems()) { - assertThat(data.getSpanId()).isIn(spanIds); - assertThat(data.getSpanId()).isNotEqualTo(data.getParentSpanId()); - assertThat(data.getTraceId()).isIn(traceIds); - assertThat(data.getKind()).isEqualByComparingTo(SpanKind.PRODUCER); - } + SpanData span = spans.get(0); + assertEquals(SpanKind.PRODUCER, span.getKind()); + assertEquals("smallrye-kafka", span.getAttributes().get(MESSAGING_SYSTEM)); + assertEquals("topic", span.getAttributes().get(MESSAGING_DESTINATION_KIND)); + assertEquals(topic, span.getAttributes().get(MESSAGING_DESTINATION)); + assertEquals(topic + " send", span.getName()); + }); } @Test public void testFromAppToKafkaWithBinaryCloudEvents() { - List contexts = new CopyOnWriteArrayList<>(); - ConsumerTask consumed = companion.consumeStrings().fromTopics(topic, - m -> m.plug(until(10L, Duration.ofMinutes(1), null)) - .onItem().invoke(record -> { - contexts.add(GlobalOpenTelemetry.getPropagators().getTextMapPropagator() - .extract(Context.current(), record.headers(), new HeaderExtractAdapter())); - })); + m -> m.plug(until(10L, Duration.ofMinutes(1), null))); runApplication(getKafkaSinkConfigForMyAppGeneratingDataWithStructuredCloudEvent("binary"), MyAppGeneratingCloudEventData.class); @@ -207,47 +170,34 @@ public void testFromAppToKafkaWithBinaryCloudEvents() { }); assertThat(values).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - assertThat(contexts).hasSize(10); - assertThat(contexts).doesNotContainNull().doesNotHaveDuplicates(); - - List spanIds = contexts.stream() - .map(context -> Span.fromContextOrNull(context).getSpanContext().getSpanId()) - .collect(Collectors.toList()); - assertThat(spanIds).doesNotContainNull().doesNotHaveDuplicates().hasSize(10); + CompletableResultCode completableResultCode = tracerProvider.forceFlush(); + completableResultCode.whenComplete(() -> { + List spans = spanExporter.getFinishedSpanItems(); + assertEquals(10, spans.size()); - List traceIds = contexts.stream() - .map(context -> Span.fromContextOrNull(context).getSpanContext().getTraceId()) - .collect(Collectors.toList()); - assertThat(traceIds).doesNotContainNull().doesNotHaveDuplicates().hasSize(10); + assertEquals(10, spans.stream().map(SpanData::getTraceId).collect(Collectors.toSet()).size()); - for (SpanData data : testExporter.getFinishedSpanItems()) { - assertThat(data.getSpanId()).isIn(spanIds); - assertThat(data.getSpanId()).isNotEqualTo(data.getParentSpanId()); - assertThat(data.getTraceId()).isIn(traceIds); - assertThat(data.getKind()).isEqualByComparingTo(SpanKind.PRODUCER); - } + SpanData span = spans.get(0); + assertEquals(SpanKind.PRODUCER, span.getKind()); + assertEquals("smallrye-kafka", span.getAttributes().get(MESSAGING_SYSTEM)); + assertEquals("topic", span.getAttributes().get(MESSAGING_DESTINATION_KIND)); + assertEquals(topic, span.getAttributes().get(MESSAGING_DESTINATION)); + assertEquals(topic + " send", span.getName()); + }); } @Test public void testFromKafkaToAppToKafka() { - List receivedContexts = new CopyOnWriteArrayList<>(); String resultTopic = topic + "-result"; String parentTopic = topic + "-parent"; ConsumerTask consumed = companion.consumeIntegers().fromTopics(resultTopic, - m -> m.plug(until(10L, Duration.ofMinutes(1), null)) - .onItem().invoke(record -> { - receivedContexts.add(GlobalOpenTelemetry.getPropagators().getTextMapPropagator() - .extract(Context.current(), record.headers(), new HeaderExtractAdapter())); - })); - MyAppProcessingData bean = runApplication(getKafkaSinkConfigForMyAppProcessingData(resultTopic, parentTopic), - MyAppProcessingData.class); - - List producedSpanContexts = new CopyOnWriteArrayList<>(); + m -> m.plug(until(10L, Duration.ofMinutes(1), null))); + runApplication(getKafkaSinkConfigForMyAppProcessingData(resultTopic, parentTopic), MyAppProcessingData.class); + companion.produceIntegers() - .usingGenerator(i -> new ProducerRecord<>(parentTopic, null, null, "a-key", i, - createTracingSpan(producedSpanContexts, parentTopic)), 10); + .usingGenerator(i -> new ProducerRecord<>(parentTopic, null, null, "a-key", i, new ArrayList<>()), 10); - await().until(() -> consumed.getRecords().size() >= 10); + await().until(() -> consumed.getRecords().size() == 10); List values = new ArrayList<>(); assertThat(consumed.getRecords()).allSatisfy(record -> { assertThat(record.value()).isNotNull(); @@ -255,126 +205,86 @@ public void testFromKafkaToAppToKafka() { }); assertThat(values).containsExactly(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); - List producedTraceIds = producedSpanContexts.stream() - .map(SpanContext::getTraceId) - .collect(Collectors.toList()); - assertThat(producedTraceIds).hasSize(10); - - assertThat(receivedContexts).hasSize(10); - assertThat(receivedContexts).doesNotContainNull().doesNotHaveDuplicates(); - - List receivedSpanIds = receivedContexts.stream() - .map(context -> Span.fromContextOrNull(context).getSpanContext().getSpanId()) - .collect(Collectors.toList()); - assertThat(receivedSpanIds).doesNotContainNull().doesNotHaveDuplicates().hasSize(10); - - List receivedTraceIds = receivedContexts.stream() - .map(context -> Span.fromContextOrNull(context).getSpanContext().getTraceId()) - .collect(Collectors.toList()); - assertThat(receivedTraceIds).doesNotContainNull().doesNotHaveDuplicates().hasSize(10); - assertThat(receivedTraceIds).containsExactlyInAnyOrderElementsOf(producedTraceIds); - - assertThat(bean.tracing()).hasSizeGreaterThanOrEqualTo(10); - assertThat(bean.tracing()).doesNotContainNull().doesNotHaveDuplicates(); - List spanIds = new ArrayList<>(); - - for (TracingMetadata tracing : bean.tracing()) { - Span span = Span.fromContext(tracing.getCurrentContext()); - spanIds.add(span.getSpanContext().getSpanId()); - assertThat(Span.fromContextOrNull(tracing.getPreviousContext())).isNotNull(); - } - - await().atMost(Duration.ofMinutes(2)).until(() -> testExporter.getFinishedSpanItems().size() >= 10); + CompletableResultCode completableResultCode = tracerProvider.forceFlush(); + completableResultCode.whenComplete(() -> { + List spans = spanExporter.getFinishedSpanItems(); + assertEquals(20, spans.size()); - List outgoingParentIds = new ArrayList<>(); - List incomingParentIds = new ArrayList<>(); + List parentSpans = spans.stream() + .filter(spanData -> spanData.getParentSpanId().equals(SpanId.getInvalid())) + .collect(toList()); + assertEquals(10, parentSpans.size()); - for (SpanData data : testExporter.getFinishedSpanItems()) { - if (data.getKind().equals(SpanKind.CONSUMER)) { - incomingParentIds.add(data.getParentSpanId()); - assertThat(data.getAttributes().get(SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP)).isNotNull(); - assertThat(data.getAttributes().get(AttributeKey.stringKey("messaging.consumer_id"))).isNotNull(); - // Need to skip the spans created during @Incoming processing - continue; + for (SpanData parentSpan : parentSpans) { + assertEquals(1, + spans.stream().filter(spanData -> spanData.getParentSpanId().equals(parentSpan.getSpanId())).count()); } - assertThat(data.getSpanId()).isIn(receivedSpanIds); - assertThat(data.getSpanId()).isNotEqualTo(data.getParentSpanId()); - assertThat(data.getTraceId()).isIn(producedTraceIds); - assertThat(data.getKind()).isEqualByComparingTo(SpanKind.PRODUCER); - outgoingParentIds.add(data.getParentSpanId()); - } - // Assert span created on Kafka record is the parent of consumer span we create - assertThat(producedSpanContexts.stream() - .map(SpanContext::getSpanId)).containsExactlyElementsOf(incomingParentIds); - - // Assert consumer span is the parent of the producer span we received in Kafka - assertThat(spanIds.stream()) - .containsExactlyElementsOf(outgoingParentIds); + SpanData consumer = parentSpans.get(0); + assertEquals(SpanKind.CONSUMER, consumer.getKind()); + assertEquals("topic", consumer.getAttributes().get(MESSAGING_DESTINATION_KIND)); + assertEquals(parentTopic, consumer.getAttributes().get(MESSAGING_DESTINATION)); + assertEquals(parentTopic + " receive", consumer.getName()); + + SpanData producer = spans.stream().filter(spanData -> spanData.getParentSpanId().equals(consumer.getSpanId())) + .findFirst().get(); + assertEquals(SpanKind.PRODUCER, producer.getKind()); + assertEquals("smallrye-kafka", producer.getAttributes().get(MESSAGING_SYSTEM)); + assertEquals("topic", producer.getAttributes().get(MESSAGING_DESTINATION_KIND)); + assertEquals(resultTopic, producer.getAttributes().get(MESSAGING_DESTINATION)); + assertEquals(resultTopic + " send", producer.getName()); + }); } @Test public void testFromKafkaToAppWithParentSpan() { String parentTopic = topic + "-parent"; - String stuffTopic = topic + "-stuff"; MyAppReceivingData bean = runApplication(getKafkaSinkConfigForMyAppReceivingData(parentTopic), MyAppReceivingData.class); - List producedSpanContexts = new CopyOnWriteArrayList<>(); - - companion.produceIntegers().usingGenerator( - i -> new ProducerRecord<>(parentTopic, null, null, "a-key", i, - createTracingSpan(producedSpanContexts, stuffTopic)), - 10); - - await().until(() -> bean.list().size() >= 10); - assertThat(bean.list()).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - - List producedTraceIds = producedSpanContexts.stream() - .map(SpanContext::getTraceId) - .collect(Collectors.toList()); - assertThat(producedTraceIds).hasSize(10); - - assertThat(bean.tracing()).hasSizeGreaterThanOrEqualTo(10); - assertThat(bean.tracing()).doesNotContainNull().doesNotHaveDuplicates(); - - List receivedTraceIds = bean.tracing().stream() - .map(tracingMetadata -> Span.fromContext(tracingMetadata.getCurrentContext()).getSpanContext().getTraceId()) - .collect(Collectors.toList()); - assertThat(receivedTraceIds).doesNotContainNull().doesNotHaveDuplicates().hasSize(10); - assertThat(receivedTraceIds).containsExactlyInAnyOrderElementsOf(producedTraceIds); + RecordHeaders headers = new RecordHeaders(); + try (Scope ignored = Context.current().makeCurrent()) { + Tracer tracer = GlobalOpenTelemetry.getTracerProvider().get("io.smallrye.reactive.messaging"); + Span span = tracer.spanBuilder("producer").setSpanKind(SpanKind.PRODUCER).startSpan(); + Context current = Context.current().with(span); + GlobalOpenTelemetry.getPropagators() + .getTextMapPropagator() + .inject(current, headers, (carrier, key, value) -> carrier.add(key, value.getBytes())); + span.end(); + } + companion.produceIntegers().usingGenerator(i -> new ProducerRecord<>(parentTopic, null, null, "a-key", i, headers), 10); - List spanIds = new ArrayList<>(); + await().until(() -> bean.results().size() >= 10); + assertThat(bean.results()).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - for (TracingMetadata tracing : bean.tracing()) { - spanIds.add(Span.fromContext(tracing.getCurrentContext()).getSpanContext().getSpanId()); + CompletableResultCode completableResultCode = tracerProvider.forceFlush(); + completableResultCode.whenComplete(() -> { + // 1 Parent, 10 Children + List spans = spanExporter.getFinishedSpanItems(); + assertEquals(11, spans.size()); - assertThat(tracing.getPreviousContext()).isNotNull(); - Span previousSpan = Span.fromContextOrNull(tracing.getPreviousContext()); - assertThat(previousSpan).isNotNull(); - assertThat(previousSpan.getSpanContext().getTraceId()) - .isEqualTo(Span.fromContext(tracing.getCurrentContext()).getSpanContext().getTraceId()); - assertThat(previousSpan.getSpanContext().getSpanId()) - .isNotEqualTo(Span.fromContext(tracing.getCurrentContext()).getSpanContext().getSpanId()); - } + // All should use the same Trace + assertEquals(1, spans.stream().map(SpanData::getTraceId).collect(Collectors.toSet()).size()); - assertThat(spanIds).doesNotContainNull().doesNotHaveDuplicates().hasSizeGreaterThanOrEqualTo(10); + List parentSpans = spans.stream() + .filter(spanData -> spanData.getParentSpanId().equals(SpanId.getInvalid())).collect(toList()); + assertEquals(1, parentSpans.size()); - List parentIds = bean.tracing().stream() - .map(tracingMetadata -> Span.fromContextOrNull(tracingMetadata.getPreviousContext()) - .getSpanContext().getSpanId()) - .collect(Collectors.toList()); + for (SpanData parentSpan : parentSpans) { + assertEquals(10, + spans.stream().filter(spanData -> spanData.getParentSpanId().equals(parentSpan.getSpanId())).count()); + } - assertThat(producedSpanContexts.stream() - .map(SpanContext::getSpanId)).containsExactlyElementsOf(parentIds); + SpanData producer = parentSpans.get(0); + assertEquals(SpanKind.PRODUCER, producer.getKind()); - for (SpanData data : testExporter.getFinishedSpanItems()) { - assertThat(data.getSpanId()).isIn(spanIds); - assertThat(data.getSpanId()).isNotEqualTo(data.getParentSpanId()); - assertThat(data.getKind()).isEqualByComparingTo(SpanKind.CONSUMER); - assertThat(data.getParentSpanId()).isNotNull(); - assertThat(data.getParentSpanId()).isIn(parentIds); - } + SpanData consumer = spans.stream().filter(spanData -> spanData.getParentSpanId().equals(producer.getSpanId())) + .findFirst().get(); + assertEquals("smallrye-kafka", consumer.getAttributes().get(MESSAGING_SYSTEM)); + assertEquals("topic", consumer.getAttributes().get(MESSAGING_DESTINATION_KIND)); + assertEquals(parentTopic, consumer.getAttributes().get(MESSAGING_DESTINATION)); + assertEquals(parentTopic + " receive", consumer.getName()); + }); } @Test @@ -384,40 +294,19 @@ public void testFromKafkaToAppWithNoParent() { companion.produceIntegers().usingGenerator(i -> new ProducerRecord<>(topic, null, null, "a-key", i), 10); - await().until(() -> bean.list().size() >= 10); - assertThat(bean.list()).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - - assertThat(bean.tracing()).hasSizeGreaterThanOrEqualTo(10); - assertThat(bean.tracing()).doesNotContainNull().doesNotHaveDuplicates(); - List spanIds = new ArrayList<>(); + await().until(() -> bean.results().size() >= 10); + assertThat(bean.results()).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - for (TracingMetadata tracing : bean.tracing()) { - spanIds.add(Span.fromContext(tracing.getCurrentContext()).getSpanContext().getSpanId()); - assertThat(Span.fromContextOrNull(tracing.getPreviousContext())).isNull(); - } - - assertThat(spanIds).doesNotContainNull().doesNotHaveDuplicates().hasSizeGreaterThanOrEqualTo(10); + CompletableResultCode completableResultCode = tracerProvider.forceFlush(); + completableResultCode.whenComplete(() -> { + List spans = spanExporter.getFinishedSpanItems(); + assertEquals(10, spans.size()); - for (SpanData data : testExporter.getFinishedSpanItems()) { - assertThat(data.getSpanId()).isIn(spanIds); - assertThat(data.getSpanId()).isNotEqualTo(data.getParentSpanId()); - assertThat(data.getKind()).isEqualByComparingTo(SpanKind.CONSUMER); - } - } - - private Iterable
createTracingSpan(List spanContexts, String topic) { - RecordHeaders proposedHeaders = new RecordHeaders(); - final Span span = KafkaConnector.TRACER.spanBuilder(topic).setSpanKind(SpanKind.PRODUCER).startSpan(); - final Context context = span.storeInContext(Context.current()); - GlobalOpenTelemetry.getPropagators() - .getTextMapPropagator() - .inject(context, proposedHeaders, (headers, key, value) -> { - if (headers != null) { - headers.remove(key).add(key, value.getBytes(StandardCharsets.UTF_8)); - } - }); - spanContexts.add(span.getSpanContext()); - return proposedHeaders; + for (SpanData span : spans) { + assertEquals(SpanKind.CONSUMER, span.getKind()); + assertEquals(SpanId.getInvalid(), span.getParentSpanId()); + } + }); } private KafkaMapBasedConfig getKafkaSinkConfigForMyAppGeneratingData() { @@ -454,7 +343,6 @@ private KafkaMapBasedConfig getKafkaSinkConfigForMyAppReceivingData(String topic @ApplicationScoped public static class MyAppGeneratingData { - @Outgoing("kafka") public Publisher source() { return Multi.createFrom().range(0, 10); @@ -463,7 +351,6 @@ public Publisher source() { @ApplicationScoped public static class MyAppGeneratingCloudEventData { - @Outgoing("kafka") public Publisher> source() { return Multi.createFrom().range(0, 10) @@ -478,39 +365,25 @@ public Publisher> source() { @ApplicationScoped public static class MyAppProcessingData { - private final List tracingMetadata = new ArrayList<>(); - @Incoming("source") @Outgoing("kafka") public Message processMessage(Message input) { - tracingMetadata.add(input.getMetadata(TracingMetadata.class).orElse(TracingMetadata.empty())); return input.withPayload(input.getPayload() + 1); } - - public List tracing() { - return tracingMetadata; - } } @ApplicationScoped public static class MyAppReceivingData { - private final List tracingMetadata = new ArrayList<>(); private final List results = new CopyOnWriteArrayList<>(); @Incoming("stuff") public CompletionStage consume(Message input) { results.add(input.getPayload()); - tracingMetadata.add(input.getMetadata(TracingMetadata.class).orElse(TracingMetadata.empty())); return input.ack(); } - public List list() { + public List results() { return results; } - - public List tracing() { - return tracingMetadata; - } } - } diff --git a/smallrye-reactive-messaging-rabbitmq/pom.xml b/smallrye-reactive-messaging-rabbitmq/pom.xml index 191e45640d..a5ff4035d1 100644 --- a/smallrye-reactive-messaging-rabbitmq/pom.xml +++ b/smallrye-reactive-messaging-rabbitmq/pom.xml @@ -59,6 +59,14 @@ io.opentelemetry opentelemetry-semconv + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-api + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-api-semconv + org.testcontainers diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMessage.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMessage.java index 1469e4bea0..78626dabae 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMessage.java +++ b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMessage.java @@ -20,7 +20,6 @@ import io.smallrye.reactive.messaging.providers.locals.ContextAwareMessage; import io.smallrye.reactive.messaging.rabbitmq.ack.RabbitMQAckHandler; import io.smallrye.reactive.messaging.rabbitmq.fault.RabbitMQFailureHandler; -import io.smallrye.reactive.messaging.rabbitmq.tracing.TracingUtils; import io.vertx.core.buffer.Buffer; import io.vertx.mutiny.core.Context; @@ -57,33 +56,35 @@ public CompletionStage handle(IncomingRabbitMQMessage message, Cont private final long deliveryTag; private RabbitMQFailureHandler onNack; private RabbitMQAckHandler onAck; - private final String contentTypeOverride; + private final RabbitMQConnectorIncomingConfiguration incomingConfiguration; private final T payload; - IncomingRabbitMQMessage(io.vertx.mutiny.rabbitmq.RabbitMQMessage delegate, ConnectionHolder holder, - boolean isTracingEnabled, RabbitMQFailureHandler onNack, - RabbitMQAckHandler onAck, String contentTypeOverride) { - this(delegate.getDelegate(), holder, isTracingEnabled, onNack, onAck, contentTypeOverride); - } - - IncomingRabbitMQMessage(io.vertx.rabbitmq.RabbitMQMessage msg, ConnectionHolder holder, boolean isTracingEnabled, - RabbitMQFailureHandler onNack, RabbitMQAckHandler onAck, String contentTypeOverride) { + IncomingRabbitMQMessage( + final io.vertx.mutiny.rabbitmq.RabbitMQMessage delegate, + final ConnectionHolder holder, + final RabbitMQFailureHandler onNack, + final RabbitMQAckHandler onAck, + final RabbitMQConnectorIncomingConfiguration incomingConfiguration) { + this(delegate.getDelegate(), holder, onNack, onAck, incomingConfiguration); + } + + IncomingRabbitMQMessage( + final io.vertx.rabbitmq.RabbitMQMessage msg, + final ConnectionHolder holder, + final RabbitMQFailureHandler onNack, + final RabbitMQAckHandler onAck, + final RabbitMQConnectorIncomingConfiguration incomingConfiguration) { this.message = msg; this.deliveryTag = msg.envelope().getDeliveryTag(); this.holder = holder; this.context = holder.getContext(); - this.contentTypeOverride = contentTypeOverride; - this.rabbitMQMetadata = new IncomingRabbitMQMetadata(this.message); + this.rabbitMQMetadata = new IncomingRabbitMQMetadata(message, incomingConfiguration); this.onNack = onNack; this.onAck = onAck; + this.incomingConfiguration = incomingConfiguration; this.metadata = captureContextMetadata(rabbitMQMetadata); //noinspection unchecked this.payload = (T) convertPayload(message); - - // If tracing is enabled, ensure any tracing metadata in the received msg headers is transferred as metadata. - if (isTracingEnabled) { - this.metadata = this.metadata.with(TracingUtils.getTracingMetaData(msg)); - } } @Override @@ -154,14 +155,10 @@ public Metadata getMetadata() { private Object convertPayload(io.vertx.rabbitmq.RabbitMQMessage msg) { // Neither of these are guaranteed to be non-null - String contentType = msg.properties().getContentType(); + final String contentType = incomingConfiguration.getContentTypeOverride().orElse(msg.properties().getContentType()); final String contentEncoding = msg.properties().getContentEncoding(); final Buffer body = msg.body(); - if (this.contentTypeOverride != null) { - contentType = contentTypeOverride; - } - // If there is a content encoding specified, we don't try to unwrap if (contentEncoding == null) { try { diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMetadata.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMetadata.java index e619d478c8..7166807bb0 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMetadata.java +++ b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMetadata.java @@ -21,6 +21,7 @@ public class IncomingRabbitMQMetadata { private final RabbitMQMessage message; + private final RabbitMQConnectorIncomingConfiguration incomingConfiguration; private final Map headers; /** @@ -28,8 +29,10 @@ public class IncomingRabbitMQMetadata { * * @param message the underlying {@link RabbitMQMessage} */ - IncomingRabbitMQMetadata(RabbitMQMessage message) { + IncomingRabbitMQMetadata(final RabbitMQMessage message, + final RabbitMQConnectorIncomingConfiguration incomingConfiguration) { this.message = message; + this.incomingConfiguration = incomingConfiguration; // Ensure the message headers are cast appropriately final Map incomingHeaders = message.properties().getHeaders(); @@ -275,6 +278,10 @@ public Optional getId() { return Optional.ofNullable(message.properties().getMessageId()); } + public String getQueueName() { + return incomingConfiguration.getQueueName(); + } + /** * The exchange the message was delivered to. *

@@ -309,4 +316,8 @@ public String getRoutingKey() { public boolean isRedeliver() { return message.envelope().isRedeliver(); } + + public boolean isTracingEnabled() { + return incomingConfiguration.getTracingEnabled(); + } } diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQConnector.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQConnector.java index ddfa044986..12ce13663a 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQConnector.java +++ b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQConnector.java @@ -12,7 +12,6 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; -import javax.annotation.PostConstruct; import javax.annotation.Priority; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.BeforeDestroyed; @@ -49,7 +48,6 @@ import io.smallrye.reactive.messaging.rabbitmq.fault.RabbitMQFailStop; import io.smallrye.reactive.messaging.rabbitmq.fault.RabbitMQFailureHandler; import io.smallrye.reactive.messaging.rabbitmq.fault.RabbitMQReject; -import io.smallrye.reactive.messaging.rabbitmq.tracing.TracingUtils; import io.vertx.core.json.JsonObject; import io.vertx.mutiny.core.Vertx; import io.vertx.mutiny.rabbitmq.RabbitMQClient; @@ -176,26 +174,16 @@ public static String getExchangeName(final RabbitMQConnectorCommonConfiguration return config.getExchangeName().map(s -> "\"\"".equals(s) ? "" : s).orElse(config.getChannel()); } - @PostConstruct - void init() { - TracingUtils.initialise(); - } - - private Multi> getStreamOfMessages(RabbitMQConsumer receiver, + private Multi> getStreamOfMessages( + RabbitMQConsumer receiver, ConnectionHolder holder, RabbitMQConnectorIncomingConfiguration ic, RabbitMQFailureHandler onNack, RabbitMQAckHandler onAck) { - final String queueName = ic.getQueueName(); - final boolean isTracingEnabled = ic.getTracingEnabled(); - final String contentTypeOverride = ic.getContentTypeOverride().orElse(null); - final List attributeHeaders = Arrays.stream(ic.getTracingAttributeHeaders().split(",")) - .map(String::trim).collect(Collectors.toList()); - log.receiverListeningAddress(queueName); - return receiver.toMulti() - .map(m -> new IncomingRabbitMQMessage<>(m, holder, isTracingEnabled, onNack, onAck, contentTypeOverride)) - .map(m -> isTracingEnabled ? TracingUtils.addIncomingTrace(m, queueName, attributeHeaders) : m); + log.receiverListeningAddress(ic.getQueueName()); + + return receiver.toMulti().map(m -> new IncomingRabbitMQMessage<>(m, holder, onNack, onAck, ic)); } /** diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMessageConverter.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMessageConverter.java index 8b765c8685..0df45d3768 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMessageConverter.java +++ b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMessageConverter.java @@ -14,6 +14,8 @@ import com.rabbitmq.client.BasicProperties; import io.netty.handler.codec.http.HttpHeaderValues; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.smallrye.reactive.messaging.rabbitmq.tracing.RabbitMQTrace; import io.smallrye.reactive.messaging.rabbitmq.tracing.TracingUtils; import io.vertx.core.json.Json; import io.vertx.core.json.JsonArray; @@ -50,17 +52,15 @@ private RabbitMQMessageConverter() { * @param exchange the destination exchange * @param defaultRoutingKey the fallback routing key to use * @param isTracingEnabled whether tracing is enabled - * @param attributeHeaders a list (possibly empty) of message header names whose values should be - * included as span attributes * @return an {@link OutgoingRabbitMQMessage} */ public static OutgoingRabbitMQMessage convert( + final Instrumenter instrumenter, final Message message, final String exchange, final String defaultRoutingKey, final Optional defaultTtl, - final boolean isTracingEnabled, - final List attributeHeaders) { + final boolean isTracingEnabled) { final Optional rabbitMQMessage = getRabbitMQMessage(message); final String routingKey = getRoutingKey(message).orElse(defaultRoutingKey); @@ -78,8 +78,7 @@ public static OutgoingRabbitMQMessage convert( if (isTracingEnabled) { // Create a new span for the outbound message and record updated tracing information in // the headers; this has to be done before we build the properties below - TracingUtils.createOutgoingTrace(message, sourceHeaders, exchange, routingKey, - attributeHeaders); + TracingUtils.createOutgoingTrace(instrumenter, message, sourceHeaders, exchange); } // Reconstruct the properties from the source, except with the (possibly) modified headers; @@ -121,8 +120,7 @@ public static OutgoingRabbitMQMessage convert( if (isTracingEnabled) { // Create a new span for the outbound message and record updated tracing information in // the message headers; this has to be done before we build the properties below - TracingUtils.createOutgoingTrace(message, metadata.getHeaders(), exchange, routingKey, - attributeHeaders); + TracingUtils.createOutgoingTrace(instrumenter, message, metadata.getHeaders(), exchange); } final Date timestamp = (metadata.getTimestamp() != null) ? Date.from(metadata.getTimestamp().toInstant()) : null; diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMessageSender.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMessageSender.java index b8234bc18d..a151f4f653 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMessageSender.java +++ b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMessageSender.java @@ -3,10 +3,8 @@ import static io.smallrye.reactive.messaging.rabbitmq.i18n.RabbitMQExceptions.ex; import static java.time.Duration.ofSeconds; -import java.util.Arrays; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; import org.eclipse.microprofile.reactive.messaging.Message; import org.reactivestreams.Processor; @@ -14,11 +12,21 @@ import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessageOperation; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesGetter; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingSpanNameExtractor; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.helpers.Subscriptions; import io.smallrye.mutiny.tuples.Tuple2; import io.smallrye.reactive.messaging.rabbitmq.i18n.RabbitMQExceptions; import io.smallrye.reactive.messaging.rabbitmq.i18n.RabbitMQLogging; +import io.smallrye.reactive.messaging.rabbitmq.tracing.RabbitMQTrace; +import io.smallrye.reactive.messaging.rabbitmq.tracing.RabbitMQTraceAttributesExtractor; +import io.smallrye.reactive.messaging.rabbitmq.tracing.RabbitMQTraceTextMapSetter; import io.vertx.mutiny.rabbitmq.RabbitMQPublisher; /** @@ -38,6 +46,8 @@ public class RabbitMQMessageSender implements Processor, Message>, private final long inflights; private final Optional defaultTtl; + private final Instrumenter instrumenter; + /** * Constructor. * @@ -61,6 +71,17 @@ public RabbitMQMessageSender( if (defaultTtl.isPresent() && defaultTtl.get() < 0) { throw ex.illegalArgumentInvalidDefaultTtl(); } + + RabbitMQTraceAttributesExtractor rabbitMQAttributesExtractor = new RabbitMQTraceAttributesExtractor(); + MessagingAttributesGetter messagingAttributesGetter = rabbitMQAttributesExtractor + .getMessagingAttributesGetter(); + InstrumenterBuilder builder = Instrumenter.builder(GlobalOpenTelemetry.get(), + "io.smallrye.reactive.messaging", + MessagingSpanNameExtractor.create(messagingAttributesGetter, MessageOperation.SEND)); + + instrumenter = builder.addAttributesExtractor(rabbitMQAttributesExtractor) + .addAttributesExtractor(MessagingAttributesExtractor.create(messagingAttributesGetter, MessageOperation.SEND)) + .buildProducerInstrumenter(RabbitMQTraceTextMapSetter.INSTANCE); } /* ----------------------------------------------------- */ @@ -248,10 +269,8 @@ private Uni> send( final int retryInterval = configuration.getReconnectInterval(); final String defaultRoutingKey = configuration.getDefaultRoutingKey(); - final RabbitMQMessageConverter.OutgoingRabbitMQMessage outgoingRabbitMQMessage = RabbitMQMessageConverter.convert(msg, - exchange, defaultRoutingKey, defaultTtl, isTracingEnabled, - Arrays.stream(configuration.getTracingAttributeHeaders().split(",")) - .map(String::trim).collect(Collectors.toList())); + final RabbitMQMessageConverter.OutgoingRabbitMQMessage outgoingRabbitMQMessage = RabbitMQMessageConverter + .convert(instrumenter, msg, exchange, defaultRoutingKey, defaultTtl, isTracingEnabled); RabbitMQLogging.log.sendingMessageToExchange(exchange, outgoingRabbitMQMessage.getRoutingKey()); return publisher.publish(exchange, outgoingRabbitMQMessage.getRoutingKey(), outgoingRabbitMQMessage.getProperties(), diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/HeadersMapExtractAdapter.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/HeadersMapExtractAdapter.java deleted file mode 100644 index 7832e7dd60..0000000000 --- a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/HeadersMapExtractAdapter.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.smallrye.reactive.messaging.rabbitmq.tracing; - -import java.util.ArrayList; -import java.util.Map; - -import io.opentelemetry.context.propagation.TextMapGetter; - -/** - * A {@link TextMapGetter} that is sourced from a string-keyed Map. - */ -public class HeadersMapExtractAdapter implements TextMapGetter> { - - public static final HeadersMapExtractAdapter GETTER = new HeadersMapExtractAdapter(); - - /** - * Constructor. - */ - private HeadersMapExtractAdapter() { - } - - @Override - public Iterable keys(final Map carrier) { - return new ArrayList<>(carrier.keySet()); - } - - @Override - public String get(final Map carrier, final String key) { - return (carrier != null) ? String.valueOf(carrier.get(key)) : null; - } -} diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/HeadersMapInjectAdapter.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/HeadersMapInjectAdapter.java deleted file mode 100644 index 7c25aa6b06..0000000000 --- a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/HeadersMapInjectAdapter.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.smallrye.reactive.messaging.rabbitmq.tracing; - -import java.util.Map; - -import io.opentelemetry.context.propagation.TextMapSetter; - -/** - * A {@link TextMapSetter} that targets a string-keyed Map. - */ -class HeadersMapInjectAdapter implements TextMapSetter> { - - public static final HeadersMapInjectAdapter SETTER = new HeadersMapInjectAdapter(); - - /** - * Private constructor to prevent instantiation. - */ - private HeadersMapInjectAdapter() { - } - - @Override - public void set(final Map carrier, final String key, final String value) { - carrier.put(key, value); - } -} diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTrace.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTrace.java new file mode 100644 index 0000000000..d270ae5e30 --- /dev/null +++ b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTrace.java @@ -0,0 +1,27 @@ +package io.smallrye.reactive.messaging.rabbitmq.tracing; + +import java.util.Map; + +public class RabbitMQTrace { + private final String destination; + private final Map headers; + + private RabbitMQTrace(final String destination, final Map headers) { + this.destination = destination; + this.headers = headers; + } + + public String getDestination() { + return destination; + } + + public Map getHeaders() { + return headers; + } + + public static RabbitMQTrace trace( + final String destination, + final Map headers) { + return new RabbitMQTrace(destination, headers); + } +} diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTraceAttributesExtractor.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTraceAttributesExtractor.java new file mode 100644 index 0000000000..598dd41b7e --- /dev/null +++ b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTraceAttributesExtractor.java @@ -0,0 +1,92 @@ +package io.smallrye.reactive.messaging.rabbitmq.tracing; + +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesGetter; +import io.smallrye.reactive.messaging.rabbitmq.RabbitMQConnector; + +public class RabbitMQTraceAttributesExtractor implements AttributesExtractor { + private final MessagingAttributesGetter messagingAttributesGetter; + + public RabbitMQTraceAttributesExtractor() { + this.messagingAttributesGetter = new RabbitMQMessagingAttributesGetter(); + } + + public MessagingAttributesGetter getMessagingAttributesGetter() { + return messagingAttributesGetter; + } + + @Override + public void onStart( + final AttributesBuilder attributes, + final Context parentContext, final RabbitMQTrace rabbitMQTrace) { + // TODO - radcortez - What to add here? + // attributes.put(MESSAGING_CONSUMER_ID, null); + // attributes.put(MESSAGING_RABBITMQ_ROUTING_KEY, null); + } + + @Override + public void onEnd( + final AttributesBuilder attributes, + final Context context, + final RabbitMQTrace rabbitMQTrace, final Void unused, final Throwable error) { + } + + private final static class RabbitMQMessagingAttributesGetter implements MessagingAttributesGetter { + @Override + public String system(final RabbitMQTrace rabbitMQTrace) { + return RabbitMQConnector.CONNECTOR_NAME; + } + + @Override + public String destinationKind(final RabbitMQTrace rabbitMQTrace) { + return "queue"; + } + + @Override + public String destination(final RabbitMQTrace rabbitMQTrace) { + return rabbitMQTrace.getDestination(); + } + + @Override + public boolean temporaryDestination(final RabbitMQTrace rabbitMQTrace) { + return false; + } + + @Override + public String protocol(final RabbitMQTrace rabbitMQTrace) { + return "AMQP"; + } + + @Override + public String protocolVersion(final RabbitMQTrace rabbitMQTrace) { + return "1.0"; + } + + @Override + public String url(final RabbitMQTrace rabbitMQTrace) { + return null; + } + + @Override + public String conversationId(final RabbitMQTrace rabbitMQTrace) { + return null; + } + + @Override + public Long messagePayloadSize(final RabbitMQTrace rabbitMQTrace) { + return null; + } + + @Override + public Long messagePayloadCompressedSize(final RabbitMQTrace rabbitMQTrace) { + return null; + } + + @Override + public String messageId(final RabbitMQTrace rabbitMQTrace, final Void unused) { + return null; + } + } +} diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTraceTextMapGetter.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTraceTextMapGetter.java new file mode 100644 index 0000000000..7a71beafe4 --- /dev/null +++ b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTraceTextMapGetter.java @@ -0,0 +1,33 @@ +package io.smallrye.reactive.messaging.rabbitmq.tracing; + +import java.util.Collections; +import java.util.Map; + +import io.opentelemetry.context.propagation.TextMapGetter; + +public enum RabbitMQTraceTextMapGetter implements TextMapGetter { + INSTANCE; + + @Override + public Iterable keys(final RabbitMQTrace carrier) { + Map headers = carrier.getHeaders(); + if (headers != null) { + return headers.keySet(); + } + return Collections.emptyList(); + } + + @Override + public String get(final RabbitMQTrace carrier, final String key) { + if (carrier != null) { + Map headers = carrier.getHeaders(); + if (headers != null) { + Object value = headers.get(key); + if (value != null) { + return value.toString(); + } + } + } + return null; + } +} diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTraceTextMapSetter.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTraceTextMapSetter.java new file mode 100644 index 0000000000..f0ef1bf0d2 --- /dev/null +++ b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTraceTextMapSetter.java @@ -0,0 +1,19 @@ +package io.smallrye.reactive.messaging.rabbitmq.tracing; + +import java.util.Map; + +import io.opentelemetry.context.propagation.TextMapSetter; + +public enum RabbitMQTraceTextMapSetter implements TextMapSetter { + INSTANCE; + + @Override + public void set(final RabbitMQTrace carrier, final String key, final String value) { + if (carrier != null) { + Map headers = carrier.getHeaders(); + if (headers != null) { + headers.put(key, value); + } + } + } +} diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTracingSubscriberDecorator.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTracingSubscriberDecorator.java new file mode 100644 index 0000000000..1953d29c58 --- /dev/null +++ b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTracingSubscriberDecorator.java @@ -0,0 +1,87 @@ +package io.smallrye.reactive.messaging.rabbitmq.tracing; + +import static io.opentelemetry.instrumentation.api.instrumenter.messaging.MessageOperation.RECEIVE; + +import java.util.List; +import java.util.Optional; + +import javax.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.reactive.messaging.Message; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesGetter; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingSpanNameExtractor; +import io.smallrye.mutiny.Multi; +import io.smallrye.reactive.messaging.SubscriberDecorator; +import io.smallrye.reactive.messaging.TracingMetadata; +import io.smallrye.reactive.messaging.rabbitmq.IncomingRabbitMQMetadata; + +@ApplicationScoped +public class RabbitMQTracingSubscriberDecorator implements SubscriberDecorator { + private final Instrumenter instrumenter; + + public RabbitMQTracingSubscriberDecorator() { + RabbitMQTraceAttributesExtractor rabbitMQAttributesExtractor = new RabbitMQTraceAttributesExtractor(); + MessagingAttributesGetter messagingAttributesGetter = rabbitMQAttributesExtractor + .getMessagingAttributesGetter(); + InstrumenterBuilder builder = Instrumenter.builder(GlobalOpenTelemetry.get(), + "io.smallrye.reactive.messaging", MessagingSpanNameExtractor.create(messagingAttributesGetter, RECEIVE)); + + instrumenter = builder.addAttributesExtractor(rabbitMQAttributesExtractor) + .addAttributesExtractor(MessagingAttributesExtractor.create(messagingAttributesGetter, RECEIVE)) + .buildConsumerInstrumenter(RabbitMQTraceTextMapGetter.INSTANCE); + } + + @Override + public Multi> decorate( + final Multi> toBeSubscribed, + final List channelName, + final boolean isConnector) { + return toBeSubscribed.onItem().transform(this::traceMessage); + } + + private Message traceMessage(final Message message) { + Optional incomingRabbitMQMetadata = message.getMetadata().get(IncomingRabbitMQMetadata.class); + if (incomingRabbitMQMetadata.isEmpty()) { + return message; + } + + IncomingRabbitMQMetadata metadata = incomingRabbitMQMetadata.get(); + if (!metadata.isTracingEnabled()) { + // TODO - We can optimize this and not even create the bean? + return message; + } + + TracingMetadata tracingMetadata = TracingMetadata.fromMessage(message).orElse(TracingMetadata.empty()); + RabbitMQTrace trace = RabbitMQTrace.trace(metadata.getQueueName(), metadata.getHeaders()); + + Context parentContext = tracingMetadata.getPreviousContext(); + if (parentContext == null) { + parentContext = Context.current(); + } + Context spanContext; + Scope scope = null; + + boolean shouldStart = instrumenter.shouldStart(parentContext, trace); + if (shouldStart) { + try { + spanContext = instrumenter.start(parentContext, trace); + scope = spanContext.makeCurrent(); + instrumenter.end(spanContext, trace, null, null); + return message.addMetadata(TracingMetadata.with(spanContext, parentContext)); + } finally { + if (scope != null) { + scope.close(); + } + } + } + + return message; + } +} diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/TracingUtils.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/TracingUtils.java index 9c5f23aa3f..cfd8a48c69 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/TracingUtils.java +++ b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/TracingUtils.java @@ -1,236 +1,49 @@ package io.smallrye.reactive.messaging.rabbitmq.tracing; -import java.util.Collections; -import java.util.List; import java.util.Map; -import java.util.stream.Collectors; +import java.util.Optional; import org.eclipse.microprofile.reactive.messaging.Message; -import com.rabbitmq.client.Envelope; - -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanBuilder; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.smallrye.reactive.messaging.TracingMetadata; -import io.smallrye.reactive.messaging.rabbitmq.IncomingRabbitMQMessage; -import io.smallrye.reactive.messaging.rabbitmq.OutgoingRabbitMQMetadata; /** * Utility methods to manage spans for incoming and outgoing messages. */ public abstract class TracingUtils { - - private static final String SYSTEM_RABBITMQ = "rabbitmq"; - private static final String DESTINATION_EXCHANGE = "exchange"; - private static final String DESTINATION_QUEUE = "queue"; - - // A shared tracer for use throughout the connector - private static Tracer tracer; - - public static void initialise() { - tracer = GlobalOpenTelemetry.getTracerProvider().get("io.smallrye.reactive.messaging.rabbitmq"); - } - /** * Private constructor to prevent instantiation. */ private TracingUtils() { } - /** - * Extract tracing metadata from a received RabbitMQ message and return - * it as {@link TracingMetadata}. - * - * @param msg the incoming RabbitMQ message - * @return a {@link TracingMetadata} instance, possibly empty but never null - */ - public static TracingMetadata getTracingMetaData( - final io.vertx.rabbitmq.RabbitMQMessage msg) { - // Default - TracingMetadata tracingMetadata = TracingMetadata.empty(); - - if (msg.properties().getHeaders() != null) { - // Extract the tracing headers from the incoming RabbitMQ message into a tracing context - final Context context = GlobalOpenTelemetry.getPropagators().getTextMapPropagator() - .extract(io.opentelemetry.context.Context.root(), msg.properties().getHeaders(), - HeadersMapExtractAdapter.GETTER); - - // create tracing metadata based on that tracing context - tracingMetadata = TracingMetadata.withPrevious(context); - } - - return tracingMetadata; - } - - /** - * Creates a span based on any tracing metadata in the incoming message. - * - * @param msg the incoming message - * @param attributeHeaders a list (possibly empty) of header names whose values (if present) - * should be used as span attributes - * @param the message body type - * @return the message, with injected tracing metadata - */ - public static Message addIncomingTrace( - final IncomingRabbitMQMessage msg, - final String queue, - final List attributeHeaders) { - final TracingMetadata tracingMetadata = TracingMetadata.fromMessage(msg).orElse(TracingMetadata.empty()); - final Envelope envelope = msg.getRabbitMQMessage().envelope(); - - final SpanBuilder spanBuilder = tracer.spanBuilder(envelope.getExchange() + " receive") - .setSpanKind(SpanKind.CONSUMER); - - // Handle possible parent span - final Context parentSpanContext = tracingMetadata.getPreviousContext(); - if (parentSpanContext != null) { - spanBuilder.setParent(parentSpanContext); - } else { - spanBuilder.setNoParent(); - } - - final Span span = spanBuilder.startSpan(); - - // Filter the incoming message headers to just those that should map to span tags - final Map headerValues = msg.getHeaders().entrySet().stream() - .filter(e -> attributeHeaders.contains(e.getKey())) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - try { - setIncomingSpanAttributes(span, queue, envelope.getRoutingKey(), headerValues); - - // Make available as parent for subsequent spans inside message processing - span.makeCurrent(); - - msg.injectTracingMetadata(tracingMetadata.withSpan(span)); - } finally { - span.end(); - } - - return msg; - } - - /** - * Adds "standard" RabbitMQ semantic tracing attributes to the span associated with an incoming message - * - * @param span the span - * @param queue the source queue - * @param routingKey the routing key - * @param attributeHeaders a map (possibly empty, but never null) of headers and their values that should - * be mapped to span attributes. Only non-null values of type Long, String, Boolean or Double will be mapped. - */ - private static void setIncomingSpanAttributes( - final Span span, - final String queue, - final String routingKey, - final Map attributeHeaders) { - span.setAttribute(SemanticAttributes.MESSAGING_RABBITMQ_ROUTING_KEY, routingKey); - span.setAttribute(SemanticAttributes.MESSAGING_SYSTEM, SYSTEM_RABBITMQ); - span.setAttribute(SemanticAttributes.MESSAGING_DESTINATION, queue); - span.setAttribute(SemanticAttributes.MESSAGING_DESTINATION_KIND, DESTINATION_QUEUE); - setHeaderSpanAttributes(span, attributeHeaders); - } - - /** - * Adds "standard" RabbitMQ semantic tracing attributes to the span associated with an outgoing message - * - * @param span the span - * @param exchange the destination exchange - * @param routingKey the routing key - * @param attributeHeaders a map (possibly empty, but never null) of headers and their values that should - * be mapped to span attributes. Only non-null values of type Long, String, Boolean or Double will be mapped. - */ - private static void setOutgoingSpanAttributes( - final Span span, - final String exchange, - final String routingKey, - final Map attributeHeaders) { - span.setAttribute(SemanticAttributes.MESSAGING_RABBITMQ_ROUTING_KEY, routingKey); - span.setAttribute(SemanticAttributes.MESSAGING_SYSTEM, SYSTEM_RABBITMQ); - span.setAttribute(SemanticAttributes.MESSAGING_DESTINATION, exchange); - span.setAttribute(SemanticAttributes.MESSAGING_DESTINATION_KIND, DESTINATION_EXCHANGE); - setHeaderSpanAttributes(span, attributeHeaders); - } - - /** - * Adds RabbitMQ header-sourced tracing attributes to the span. - * - * @param span the span - * @param attributeHeaders a map (possibly empty, but never null) of headers and their values that should - * be mapped to span attributes. Only non-null values of type Long, String, Boolean or Double will be mapped. - */ - private static void setHeaderSpanAttributes(final Span span, final Map attributeHeaders) { - // For any of the specified headers that have a non-null value of a type - // supported by opentelemetry, create a span attribute for that header that contains the value - attributeHeaders.forEach((header, value) -> { - if (value instanceof Long) { - span.setAttribute(header, (long) value); - } else if (value instanceof String) { - span.setAttribute(header, (String) value); - } else if (value instanceof Boolean) { - span.setAttribute(header, (Boolean) value); - } else if (value instanceof Double) { - span.setAttribute(header, (Double) value); - } - }); - } - - /** - * Creates a new outgoing message span message, and ensures span metadata is added to the - * message headers. - * - * @param message the source message - * @param headers the outgoing headers, must be mutable - * @param exchange the target exchange - * @param routingKey the routing key - * @param attributeHeaders a list (possibly empty) of header names whose values (if present) - * should be used as span attributes - */ public static void createOutgoingTrace( - final Message message, + final Instrumenter instrumenter, + final Message msg, final Map headers, - final String exchange, - final String routingKey, - final List attributeHeaders) { - // Extract tracing metadata from the source message itself - final TracingMetadata tracingMetadata = TracingMetadata.fromMessage(message).orElse(null); - - final SpanBuilder spanBuilder = tracer.spanBuilder(exchange + " send") - .setSpanKind(SpanKind.PRODUCER); - - if (tracingMetadata != null) { - // Handle possible parent span - final Context parentSpanContext = tracingMetadata.getPreviousContext(); - - if (parentSpanContext != null) { - spanBuilder.setParent(parentSpanContext); - } else { - spanBuilder.setNoParent(); + final String exchange) { + + Optional tracingMetadata = TracingMetadata.fromMessage(msg); + RabbitMQTrace message = RabbitMQTrace.trace(exchange, headers); + + Context parentContext = tracingMetadata.map(TracingMetadata::getCurrentContext).orElse(Context.current()); + Context spanContext; + Scope scope = null; + + boolean shouldStart = instrumenter.shouldStart(parentContext, message); + if (shouldStart) { + try { + spanContext = instrumenter.start(parentContext, message); + scope = spanContext.makeCurrent(); + instrumenter.end(spanContext, message, null, null); + } finally { + if (scope != null) { + scope.close(); + } } - } else { - spanBuilder.setNoParent(); } - - final Span span = spanBuilder.startSpan(); - final Context sendingContext = Context.current().with(span); - - // Filter the outgoing message headers to just those that should map to span tags - final Map headerValues = message.getMetadata(OutgoingRabbitMQMetadata.class) - .map(md -> md.getHeaders().entrySet().stream() - .filter(e -> attributeHeaders.contains(e.getKey())) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))) - .orElse(Collections.emptyMap()); - - setOutgoingSpanAttributes(span, exchange, routingKey, headerValues); - - // Set span onto outgoing headers - GlobalOpenTelemetry.getPropagators().getTextMapPropagator() - .inject(sendingContext, headers, HeadersMapInjectAdapter.SETTER); - span.end(); } } diff --git a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMessageTest.java b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMessageTest.java index 7b6f80a0aa..aceceb1b01 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMessageTest.java +++ b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMessageTest.java @@ -5,6 +5,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -45,12 +46,13 @@ public void testDoubleAckBehavior() { when(mockMsg.envelope()).thenReturn(new Envelope(13456, false, "test", "test")); RabbitMQMessage msg = RabbitMQMessage.newInstance(mockMsg); + RabbitMQConnectorIncomingConfiguration incomingConfiguration = mock(RabbitMQConnectorIncomingConfiguration.class); + when(incomingConfiguration.getContentTypeOverride()).thenReturn(Optional.of("text/plain")); + Exception nackReason = new Exception("test"); - IncomingRabbitMQMessage ackMsg = new IncomingRabbitMQMessage<>(msg, mock(ConnectionHolder.class), false, - doNothingNack, - doNothingAck, - "text/plain"); + IncomingRabbitMQMessage ackMsg = new IncomingRabbitMQMessage<>(msg, mock(ConnectionHolder.class), doNothingNack, + doNothingAck, incomingConfiguration); assertDoesNotThrow(() -> ackMsg.ack().toCompletableFuture().get()); assertDoesNotThrow(() -> ackMsg.ack().toCompletableFuture().get()); @@ -65,12 +67,13 @@ public void testDoubleNackBehavior() { when(mockMsg.envelope()).thenReturn(new Envelope(13456, false, "test", "test")); RabbitMQMessage msg = RabbitMQMessage.newInstance(mockMsg); + RabbitMQConnectorIncomingConfiguration incomingConfiguration = mock(RabbitMQConnectorIncomingConfiguration.class); + when(incomingConfiguration.getContentTypeOverride()).thenReturn(Optional.of("text/plain")); + Exception nackReason = new Exception("test"); - IncomingRabbitMQMessage nackMsg = new IncomingRabbitMQMessage<>(msg, mock(ConnectionHolder.class), false, - doNothingNack, - doNothingAck, - "text/plain"); + IncomingRabbitMQMessage nackMsg = new IncomingRabbitMQMessage<>(msg, mock(ConnectionHolder.class), + doNothingNack, doNothingAck, incomingConfiguration); assertDoesNotThrow(() -> nackMsg.nack(nackReason).toCompletableFuture().get()); assertDoesNotThrow(() -> nackMsg.nack(nackReason).toCompletableFuture().get()); @@ -85,9 +88,11 @@ void testConvertPayload() { when(mockMsg.envelope()).thenReturn(new Envelope(13456, false, "test", "test")); RabbitMQMessage msg = RabbitMQMessage.newInstance(mockMsg); + RabbitMQConnectorIncomingConfiguration incomingConfiguration = mock(RabbitMQConnectorIncomingConfiguration.class); + when(incomingConfiguration.getContentTypeOverride()).thenReturn(Optional.of("text/plain")); + IncomingRabbitMQMessage incomingRabbitMQMessage = new IncomingRabbitMQMessage<>(msg, - mock(ConnectionHolder.class), false, - doNothingNack, doNothingAck, "text/plain"); + mock(ConnectionHolder.class), doNothingNack, doNothingAck, incomingConfiguration); assertThat(incomingRabbitMQMessage.getPayload()).isEqualTo("payload"); } @@ -102,9 +107,11 @@ void testConvertPayloadJsonObject() { when(mockMsg.envelope()).thenReturn(new Envelope(13456, false, "test", "test")); RabbitMQMessage msg = RabbitMQMessage.newInstance(mockMsg); + RabbitMQConnectorIncomingConfiguration incomingConfiguration = mock(RabbitMQConnectorIncomingConfiguration.class); + when(incomingConfiguration.getContentTypeOverride()).thenReturn(Optional.empty()); + IncomingRabbitMQMessage incomingRabbitMQMessage = new IncomingRabbitMQMessage<>(msg, - mock(ConnectionHolder.class), false, - doNothingNack, doNothingAck, null); + mock(ConnectionHolder.class), doNothingNack, doNothingAck, incomingConfiguration); assertThat(incomingRabbitMQMessage.getPayload()).isEqualTo(payload); } @@ -118,9 +125,12 @@ void testConvertPayloadFallback() { when(mockMsg.envelope()).thenReturn(new Envelope(13456, false, "test", "test")); RabbitMQMessage msg = RabbitMQMessage.newInstance(mockMsg); + RabbitMQConnectorIncomingConfiguration incomingConfiguration = mock(RabbitMQConnectorIncomingConfiguration.class); + when(incomingConfiguration.getContentTypeOverride()).thenReturn(Optional.empty()); + IncomingRabbitMQMessage incomingRabbitMQMessage = new IncomingRabbitMQMessage<>(msg, - mock(ConnectionHolder.class), false, - doNothingNack, doNothingAck, null); + mock(ConnectionHolder.class), + doNothingNack, doNothingAck, incomingConfiguration); assertThat(((Message) ((Message) incomingRabbitMQMessage)).getPayload()).isEqualTo(payloadBuffer.getBytes()); } diff --git a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMetadataTest.java b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMetadataTest.java index 9faa2a87c4..b9e9ef2279 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMetadataTest.java +++ b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMetadataTest.java @@ -1,10 +1,12 @@ package io.smallrye.reactive.messaging.rabbitmq; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + import java.util.Date; import java.util.HashMap; import java.util.Map; -import org.junit.Assert; import org.junit.jupiter.api.Test; import com.rabbitmq.client.BasicProperties; @@ -23,15 +25,15 @@ public void testHeaderWithNullValue() { DummyRabbitMQMessage message = new DummyRabbitMQMessage(new DummyBasicProperties(properties)); - IncomingRabbitMQMetadata incomingRabbitMQMetadata = new IncomingRabbitMQMetadata(message); + IncomingRabbitMQMetadata incomingRabbitMQMetadata = new IncomingRabbitMQMetadata(message, null); - Assert.assertEquals("value1", incomingRabbitMQMetadata.getHeaders().get("header1")); - Assert.assertTrue(incomingRabbitMQMetadata.getHeaders().containsKey("header2")); - Assert.assertNull(incomingRabbitMQMetadata.getHeaders().get("header2")); + assertEquals("value1", incomingRabbitMQMetadata.getHeaders().get("header1")); + assertTrue(incomingRabbitMQMetadata.getHeaders().containsKey("header2")); + assertNull(incomingRabbitMQMetadata.getHeaders().get("header2")); } - class DummyRabbitMQMessage implements RabbitMQMessage { + static class DummyRabbitMQMessage implements RabbitMQMessage { protected BasicProperties properties; DummyRabbitMQMessage(BasicProperties properties) { @@ -64,7 +66,7 @@ public Integer messageCount() { } } - class DummyBasicProperties implements BasicProperties { + static class DummyBasicProperties implements BasicProperties { protected Map headers; DummyBasicProperties(Map headers) { diff --git a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMetadataTest.java b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMetadataTest.java index aa9b58c7bb..5be9b0f7ed 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMetadataTest.java +++ b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMetadataTest.java @@ -1,7 +1,6 @@ package io.smallrye.reactive.messaging.rabbitmq; import static java.time.temporal.ChronoUnit.MILLIS; -import static java.util.Collections.emptyList; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import java.time.ZonedDateTime; @@ -15,6 +14,8 @@ import com.rabbitmq.client.AMQP.BasicProperties; import com.rabbitmq.client.Envelope; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.smallrye.reactive.messaging.rabbitmq.RabbitMQMessageConverter.OutgoingRabbitMQMessage; import io.vertx.core.buffer.Buffer; import io.vertx.rabbitmq.RabbitMQMessage; @@ -65,7 +66,7 @@ public Integer messageCount() { } }; - IncomingRabbitMQMetadata incoming = new IncomingRabbitMQMetadata(message); + IncomingRabbitMQMetadata incoming = new IncomingRabbitMQMetadata(message, null); assertThat(incoming.getUserId()).isEqualTo(Optional.of("test-user")); assertThat(incoming.getAppId()).isEqualTo(Optional.of("tests")); assertThat(incoming.getContentType()).isEqualTo(Optional.of("text/plain")); @@ -100,12 +101,12 @@ void testOutgoingMetadata() { .build(); OutgoingRabbitMQMessage message = RabbitMQMessageConverter.convert( + (Instrumenter) Instrumenter.builder(OpenTelemetry.noop(), "noop", o -> "noop").buildInstrumenter(), Message.of("", Metadata.of(metadata)), "test", "#", Optional.empty(), - false, - emptyList()); + false); com.rabbitmq.client.BasicProperties props = message.getProperties(); diff --git a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/tracing/IncomingTracingTest.java b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/TracingTest.java similarity index 88% rename from smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/tracing/IncomingTracingTest.java rename to smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/TracingTest.java index 5a6c714784..c5d7d5e012 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/tracing/IncomingTracingTest.java +++ b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/TracingTest.java @@ -1,5 +1,10 @@ -package io.smallrye.reactive.messaging.rabbitmq.tracing; +package io.smallrye.reactive.messaging.rabbitmq; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_DESTINATION; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_DESTINATION_KIND; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_PROTOCOL; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_PROTOCOL_VERSION; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_SYSTEM; import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; @@ -42,10 +47,9 @@ import io.opentelemetry.sdk.trace.samplers.Sampler; import io.smallrye.reactive.messaging.providers.connectors.InMemoryConnector; import io.smallrye.reactive.messaging.providers.connectors.InMemorySource; -import io.smallrye.reactive.messaging.rabbitmq.RabbitMQConnector; -import io.smallrye.reactive.messaging.rabbitmq.WeldTestBase; +import io.smallrye.reactive.messaging.rabbitmq.tracing.RabbitMQTracingSubscriberDecorator; -public class IncomingTracingTest extends WeldTestBase { +public class TracingTest extends WeldTestBase { private SdkTracerProvider tracerProvider; private InMemorySpanExporter spanExporter; @@ -65,6 +69,8 @@ public void openTelemetry() { .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) .setTracerProvider(tracerProvider) .buildAndRegisterGlobal(); + + addBeans(RabbitMQTracingSubscriberDecorator.class); } @Test @@ -87,6 +93,15 @@ void incoming() { List spans = spanExporter.getFinishedSpanItems(); assertEquals(5, spans.size()); assertEquals(5, spans.stream().map(SpanData::getTraceId).collect(toSet()).size()); + + SpanData consumer = spans.get(0); + assertEquals(SpanKind.CONSUMER, consumer.getKind()); + assertEquals("smallrye-rabbitmq", consumer.getAttributes().get(MESSAGING_SYSTEM)); + assertEquals("AMQP", consumer.getAttributes().get(MESSAGING_PROTOCOL)); + assertEquals("1.0", consumer.getAttributes().get(MESSAGING_PROTOCOL_VERSION)); + assertEquals("queue", consumer.getAttributes().get(MESSAGING_DESTINATION_KIND)); + assertEquals(queue, consumer.getAttributes().get(MESSAGING_DESTINATION)); + assertEquals(queue + " receive", consumer.getName()); }); } diff --git a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/WeldTestBase.java b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/WeldTestBase.java index 8fd733c7e6..79953f3084 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/WeldTestBase.java +++ b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/WeldTestBase.java @@ -14,6 +14,7 @@ import io.smallrye.config.inject.ConfigExtension; import io.smallrye.reactive.messaging.providers.MediatorFactory; +import io.smallrye.reactive.messaging.providers.OutgoingInterceptorDecorator; import io.smallrye.reactive.messaging.providers.connectors.ExecutionHolder; import io.smallrye.reactive.messaging.providers.connectors.WorkerPoolRegistry; import io.smallrye.reactive.messaging.providers.extension.ChannelProducer; @@ -69,6 +70,7 @@ public void initWeld() { weld.addBeanClass(MetricDecorator.class); weld.addBeanClass(MicrometerDecorator.class); weld.addBeanClass(ContextDecorator.class); + weld.addBeanClass(OutgoingInterceptorDecorator.class); weld.disableDiscovery(); } From 11f89b396017cc2b4a8bcdaa4a3e84c5e63bf24d Mon Sep 17 00:00:00 2001 From: Ozan Gunalp Date: Tue, 8 Nov 2022 15:54:56 +0000 Subject: [PATCH 2/2] Introduce smallrye-reactive-messaging-otel module for common OTel instrumenter code. --- pom.xml | 1 + smallrye-reactive-messaging-amqp/pom.xml | 22 ++--- .../messaging/amqp/AmqpConnector.java | 33 +------ .../messaging/amqp/AmqpCreditBasedSender.java | 29 +------ .../reactive/messaging/amqp/AmqpMessage.java | 9 +- .../messaging/amqp/TracingAmqpToAppTest.java | 3 +- .../amqp/TracingAmqpToAppToAmqpTest.java | 1 + .../messaging/amqp/TracingAppToAmqpTest.java | 7 +- smallrye-reactive-messaging-kafka/pom.xml | 22 ++--- smallrye-reactive-messaging-kafka/revapi.json | 20 +++++ .../messaging/kafka/IncomingKafkaRecord.java | 4 +- .../messaging/kafka/impl/KafkaSink.java | 28 +----- .../messaging/kafka/impl/KafkaSource.java | 29 +------ smallrye-reactive-messaging-otel/pom.xml | 56 ++++++++++++ .../messaging/tracing/TracingUtils.java | 80 +++++++++++++++++ .../providers/MetadataInjectableMessage.java | 18 ++++ smallrye-reactive-messaging-rabbitmq/pom.xml | 22 ++--- .../rabbitmq/IncomingRabbitMQMessage.java | 36 ++++---- .../rabbitmq/IncomingRabbitMQMetadata.java | 13 +-- .../messaging/rabbitmq/RabbitMQConnector.java | 48 +++++++++- .../rabbitmq/RabbitMQMessageConverter.java | 6 +- .../RabbitMQTracingSubscriberDecorator.java | 87 ------------------- .../rabbitmq/tracing/TracingUtils.java | 49 ----------- .../messaging/rabbitmq/ConsumptionBean.java | 2 +- .../rabbitmq/IncomingRabbitMQMessageTest.java | 34 +++----- .../IncomingRabbitMQMetadataTest.java | 16 ++-- .../rabbitmq/RabbitMQMetadataTest.java | 2 +- .../messaging/rabbitmq/TracingTest.java | 3 - .../messaging/rabbitmq/WeldTestBase.java | 2 - 29 files changed, 302 insertions(+), 380 deletions(-) create mode 100644 smallrye-reactive-messaging-otel/pom.xml create mode 100644 smallrye-reactive-messaging-otel/src/main/java/io/smallrye/reactive/messaging/tracing/TracingUtils.java create mode 100644 smallrye-reactive-messaging-provider/src/main/java/io/smallrye/reactive/messaging/providers/MetadataInjectableMessage.java delete mode 100644 smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTracingSubscriberDecorator.java delete mode 100644 smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/TracingUtils.java diff --git a/pom.xml b/pom.xml index e422226a46..b1d67fd091 100644 --- a/pom.xml +++ b/pom.xml @@ -125,6 +125,7 @@ api smallrye-reactive-messaging-provider smallrye-reactive-messaging-in-memory + smallrye-reactive-messaging-otel smallrye-reactive-messaging-kafka smallrye-reactive-messaging-kafka-api smallrye-reactive-messaging-kafka-test-companion diff --git a/smallrye-reactive-messaging-amqp/pom.xml b/smallrye-reactive-messaging-amqp/pom.xml index 52adbfd661..5ce5df0a75 100644 --- a/smallrye-reactive-messaging-amqp/pom.xml +++ b/smallrye-reactive-messaging-amqp/pom.xml @@ -29,25 +29,13 @@ ${smallrye-vertx-mutiny-clients.version} - io.vertx - vertx-amqp-client - - - - io.opentelemetry - opentelemetry-api - - - io.opentelemetry - opentelemetry-semconv - - - io.opentelemetry.instrumentation - opentelemetry-instrumentation-api + io.smallrye.reactive + smallrye-reactive-messaging-otel + ${project.version} - io.opentelemetry.instrumentation - opentelemetry-instrumentation-api-semconv + io.vertx + vertx-amqp-client diff --git a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpConnector.java b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpConnector.java index 22ac717db3..f34a1554d2 100644 --- a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpConnector.java +++ b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpConnector.java @@ -38,8 +38,6 @@ import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessageOperation; @@ -49,7 +47,6 @@ import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor; -import io.smallrye.reactive.messaging.TracingMetadata; import io.smallrye.reactive.messaging.amqp.fault.AmqpAccept; import io.smallrye.reactive.messaging.amqp.fault.AmqpFailStop; import io.smallrye.reactive.messaging.amqp.fault.AmqpFailureHandler; @@ -63,6 +60,7 @@ import io.smallrye.reactive.messaging.health.HealthReport; import io.smallrye.reactive.messaging.health.HealthReporter; import io.smallrye.reactive.messaging.providers.connectors.ExecutionHolder; +import io.smallrye.reactive.messaging.tracing.TracingUtils; import io.vertx.amqp.AmqpClientOptions; import io.vertx.amqp.AmqpReceiverOptions; import io.vertx.amqp.AmqpSenderOptions; @@ -203,7 +201,7 @@ private Multi> getStreamOfMessages(AmqpReceiver receiver, return Multi.createFrom().deferred( () -> { - Multi> stream = receiver.toMulti() + Multi> stream = receiver.toMulti() .onItem().transformToUniAndConcatenate(m -> { try { return Uni.createFrom().item(new AmqpMessage<>(m, holder.getContext(), onNack, @@ -215,7 +213,8 @@ private Multi> getStreamOfMessages(AmqpReceiver receiver, }); if (tracingEnabled) { - stream = stream.onItem().invoke(this::incomingTrace); + stream = stream.onItem() + .transform(m -> TracingUtils.traceIncoming(instrumenter, m, (AmqpMessage) m)); } return Multi.createBy().merging().streams(stream, processor); @@ -462,28 +461,4 @@ public void reportFailure(String channel, Throwable reason) { terminate(null); } - private void incomingTrace(AmqpMessage message) { - TracingMetadata tracingMetadata = TracingMetadata.fromMessage(message).orElse(TracingMetadata.empty()); - - Context parentContext = tracingMetadata.getPreviousContext(); - if (parentContext == null) { - parentContext = Context.current(); - } - Context spanContext; - Scope scope = null; - - boolean shouldStart = instrumenter.shouldStart(parentContext, message); - if (shouldStart) { - try { - spanContext = instrumenter.start(parentContext, message); - scope = spanContext.makeCurrent(); - message.injectTracingMetadata(TracingMetadata.with(spanContext, parentContext)); - instrumenter.end(spanContext, message, null, null); - } finally { - if (scope != null) { - scope.close(); - } - } - } - } } diff --git a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpCreditBasedSender.java b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpCreditBasedSender.java index b7c8c49833..4869e136ec 100644 --- a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpCreditBasedSender.java +++ b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpCreditBasedSender.java @@ -16,8 +16,6 @@ import org.reactivestreams.Subscription; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessageOperation; @@ -28,11 +26,11 @@ import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.helpers.Subscriptions; import io.smallrye.mutiny.tuples.Tuple2; -import io.smallrye.reactive.messaging.TracingMetadata; import io.smallrye.reactive.messaging.amqp.ce.AmqpCloudEventHelper; import io.smallrye.reactive.messaging.amqp.tracing.AmqpAttributesExtractor; import io.smallrye.reactive.messaging.amqp.tracing.AmqpMessageTextMapSetter; import io.smallrye.reactive.messaging.ce.OutgoingCloudEventMetadata; +import io.smallrye.reactive.messaging.tracing.TracingUtils; import io.vertx.amqp.impl.AmqpMessageImpl; import io.vertx.mutiny.amqp.AmqpSender; @@ -326,7 +324,7 @@ private Uni> send(AmqpSender sender, Message msg, boolean durable, } if (tracingEnabled) { - createOutgoingTrace(msg, amqp); + TracingUtils.traceOutgoing(instrumenter, msg, new AmqpMessage<>(amqp, null, null, false, true)); } log.sendingMessageToAddress(actualAddress); @@ -342,29 +340,6 @@ private Uni> send(AmqpSender sender, Message msg, boolean durable, .onItem().transform(x -> msg); } - private void createOutgoingTrace(Message msg, io.vertx.mutiny.amqp.AmqpMessage amqp) { - Optional tracingMetadata = TracingMetadata.fromMessage(msg); - AmqpMessage message = new AmqpMessage<>(amqp, null, null, false, true); - - Context parentContext = tracingMetadata.map(TracingMetadata::getCurrentContext).orElse(Context.current()); - Context spanContext; - Scope scope = null; - - boolean shouldStart = instrumenter.shouldStart(parentContext, message); - if (shouldStart) { - try { - spanContext = instrumenter.start(parentContext, message); - scope = spanContext.makeCurrent(); - message.injectTracingMetadata(TracingMetadata.with(spanContext, parentContext)); - instrumenter.end(spanContext, message, null, null); - } finally { - if (scope != null) { - scope.close(); - } - } - } - } - private String getActualAddress(Message message, io.vertx.mutiny.amqp.AmqpMessage amqp, String configuredAddress, boolean isAnonymousSender) { String address = amqp.address(); diff --git a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpMessage.java b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpMessage.java index 57f90c35ed..a006275533 100644 --- a/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpMessage.java +++ b/smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpMessage.java @@ -16,17 +16,17 @@ import org.apache.qpid.proton.message.MessageError; import org.eclipse.microprofile.reactive.messaging.Metadata; -import io.smallrye.reactive.messaging.TracingMetadata; import io.smallrye.reactive.messaging.amqp.ce.AmqpCloudEventHelper; import io.smallrye.reactive.messaging.amqp.fault.AmqpFailureHandler; import io.smallrye.reactive.messaging.ce.CloudEventMetadata; +import io.smallrye.reactive.messaging.providers.MetadataInjectableMessage; import io.smallrye.reactive.messaging.providers.helpers.VertxContext; import io.smallrye.reactive.messaging.providers.locals.ContextAwareMessage; import io.vertx.core.json.JsonObject; import io.vertx.mutiny.core.Context; import io.vertx.mutiny.core.buffer.Buffer; -public class AmqpMessage implements org.eclipse.microprofile.reactive.messaging.Message, ContextAwareMessage { +public class AmqpMessage implements ContextAwareMessage, MetadataInjectableMessage { protected static final String APPLICATION_JSON = "application/json"; protected final io.vertx.amqp.AmqpMessage message; @@ -238,8 +238,9 @@ public Function> getNack() { return this::nack; } - public synchronized void injectTracingMetadata(TracingMetadata tracingMetadata) { - metadata = metadata.with(tracingMetadata); + @Override + public synchronized void injectMetadata(Object metadataObject) { + metadata = metadata.with(metadataObject); } } diff --git a/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppTest.java b/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppTest.java index 67fffec6d8..f78dee10ac 100644 --- a/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppTest.java +++ b/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppTest.java @@ -19,7 +19,6 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import io.opentelemetry.api.GlobalOpenTelemetry; @@ -37,7 +36,6 @@ import io.smallrye.reactive.messaging.test.common.config.MapBasedConfig; import io.vertx.mutiny.amqp.AmqpMessage; -@Disabled("See https://github.com/smallrye/smallrye-reactive-messaging/issues/1268") public class TracingAmqpToAppTest extends AmqpBrokerTestBase { private SdkTracerProvider tracerProvider; private InMemorySpanExporter spanExporter; @@ -91,6 +89,7 @@ public void testFromAmqpToAppWithParentSpan() { weld.addBeanClass(MyAppReceivingData.class); container = weld.initialize(); await().until(() -> isAmqpConnectorReady(container)); + await().until(() -> isAmqpConnectorAlive(container)); MyAppReceivingData bean = container.getBeanManager().createInstance().select(MyAppReceivingData.class).get(); diff --git a/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppToAmqpTest.java b/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppToAmqpTest.java index c01d741ce8..f1394423ac 100644 --- a/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppToAmqpTest.java +++ b/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAmqpToAppToAmqpTest.java @@ -98,6 +98,7 @@ public void testFromAmqpToAppToAmqp() { weld.addBeanClass(MyAppProcessingData.class); container = weld.initialize(); await().until(() -> isAmqpConnectorReady(container)); + await().until(() -> isAmqpConnectorAlive(container)); List payloads = new CopyOnWriteArrayList<>(); usage.consumeIntegers("result-topic", payloads::add); diff --git a/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAppToAmqpTest.java b/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAppToAmqpTest.java index 580ab047fd..500381bece 100644 --- a/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAppToAmqpTest.java +++ b/smallrye-reactive-messaging-amqp/src/test/java/io/smallrye/reactive/messaging/amqp/TracingAppToAmqpTest.java @@ -85,12 +85,13 @@ public void testFromAppToAmqp() { .with("amqp-password", password) .write(); + List payloads = new CopyOnWriteArrayList<>(); + usage.consumeIntegers("amqp", payloads::add); + weld.addBeanClass(MyAppGeneratingData.class); container = weld.initialize(); await().until(() -> isAmqpConnectorReady(container)); - - List payloads = new CopyOnWriteArrayList<>(); - usage.consumeIntegers("amqp", payloads::add); + await().until(() -> isAmqpConnectorAlive(container)); await().until(() -> payloads.size() >= 10); assertThat(payloads).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); diff --git a/smallrye-reactive-messaging-kafka/pom.xml b/smallrye-reactive-messaging-kafka/pom.xml index ba0a511e0c..cf6f7627cd 100644 --- a/smallrye-reactive-messaging-kafka/pom.xml +++ b/smallrye-reactive-messaging-kafka/pom.xml @@ -28,6 +28,11 @@ smallrye-reactive-messaging-kafka-api ${project.version} + + io.smallrye.reactive + smallrye-reactive-messaging-otel + ${project.version} + org.apache.kafka @@ -35,23 +40,6 @@ ${kafka.version} - - io.opentelemetry - opentelemetry-api - - - io.opentelemetry - opentelemetry-semconv - - - io.opentelemetry.instrumentation - opentelemetry-instrumentation-api - - - io.opentelemetry.instrumentation - opentelemetry-instrumentation-api-semconv - - io.smallrye.config smallrye-config diff --git a/smallrye-reactive-messaging-kafka/revapi.json b/smallrye-reactive-messaging-kafka/revapi.json index 9de2b8bcba..74eb15d976 100644 --- a/smallrye-reactive-messaging-kafka/revapi.json +++ b/smallrye-reactive-messaging-kafka/revapi.json @@ -31,6 +31,26 @@ "old": "class io.smallrye.reactive.messaging.kafka.commit.KafkaCheckpointCommit.CheckpointState", "new": "class io.smallrye.reactive.messaging.kafka.commit.KafkaCheckpointCommit.CheckpointState", "justification": "New checkpointing API" + }, + { + "code": "java.method.removed", + "old": "method void io.smallrye.reactive.messaging.kafka.impl.KafkaRecordHelper::createOutgoingTrace(org.eclipse.microprofile.reactive.messaging.Message, java.lang.String, java.lang.Integer, org.apache.kafka.common.header.Headers)", + "justification": "Tracing methods moved to separate otel module" + }, + { + "code": "java.method.removed", + "old": "method java.util.Set io.smallrye.reactive.messaging.kafka.impl.KafkaSource::getSubscribedTopics()", + "justification": "Removed unused method" + }, + { + "code": "java.class.removed", + "old": "class io.smallrye.reactive.messaging.kafka.tracing.HeaderExtractAdapter", + "justification": "Replaced by otel instrumentation api" + }, + { + "code": "java.class.removed", + "old": "class io.smallrye.reactive.messaging.kafka.tracing.HeaderInjectAdapter", + "justification": "Replaced by otel instrumentation api" } ] } diff --git a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/IncomingKafkaRecord.java b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/IncomingKafkaRecord.java index d88cb6fb27..e9bace8928 100644 --- a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/IncomingKafkaRecord.java +++ b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/IncomingKafkaRecord.java @@ -14,9 +14,10 @@ import io.smallrye.reactive.messaging.kafka.commit.KafkaCommitHandler; import io.smallrye.reactive.messaging.kafka.fault.KafkaFailureHandler; import io.smallrye.reactive.messaging.kafka.impl.ce.KafkaCloudEventHelper; +import io.smallrye.reactive.messaging.providers.MetadataInjectableMessage; import io.smallrye.reactive.messaging.providers.locals.ContextAwareMessage; -public class IncomingKafkaRecord implements KafkaRecord { +public class IncomingKafkaRecord implements KafkaRecord, MetadataInjectableMessage { private Metadata metadata; // TODO add as a normal import once we have removed IncomingKafkaRecordMetadata in this package @@ -131,6 +132,7 @@ public CompletionStage nack(Throwable reason, Metadata metadata) { return onNack.handle(this, reason, metadata).subscribeAsCompletionStage(); } + @Override public synchronized void injectMetadata(Object metadata) { this.metadata = this.metadata.with(metadata); } diff --git a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaSink.java b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaSink.java index 6c54035404..4dee89d737 100644 --- a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaSink.java +++ b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaSink.java @@ -32,8 +32,6 @@ import org.reactivestreams.Subscriber; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessageOperation; @@ -42,7 +40,6 @@ import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingSpanNameExtractor; import io.smallrye.mutiny.Uni; import io.smallrye.reactive.messaging.OutgoingMessageMetadata; -import io.smallrye.reactive.messaging.TracingMetadata; import io.smallrye.reactive.messaging.ce.OutgoingCloudEventMetadata; import io.smallrye.reactive.messaging.health.HealthReport; import io.smallrye.reactive.messaging.kafka.KafkaCDIEvents; @@ -57,6 +54,8 @@ import io.smallrye.reactive.messaging.kafka.tracing.KafkaAttributesExtractor; import io.smallrye.reactive.messaging.kafka.tracing.KafkaTrace; import io.smallrye.reactive.messaging.kafka.tracing.KafkaTraceTextMapSetter; +import io.smallrye.reactive.messaging.providers.helpers.MultiUtils; +import io.smallrye.reactive.messaging.tracing.TracingUtils; @SuppressWarnings("jol") public class KafkaSink { @@ -220,32 +219,13 @@ record = getProducerRecord(message, outgoingMetadata, incomingMetadata, actualTo } if (isTracingEnabled) { - KafkaTrace kafkaTrace = new KafkaTrace.Builder() + TracingUtils.traceOutgoing(instrumenter, message, new KafkaTrace.Builder() .withPartition(record.partition() != null ? record.partition() : -1) .withTopic(record.topic()) .withHeaders(record.headers()) .withGroupId(client.get(ConsumerConfig.GROUP_ID_CONFIG)) .withClientId(client.get(ConsumerConfig.CLIENT_ID_CONFIG)) - .build(); - - Optional tracingMetadata = TracingMetadata.fromMessage(message); - - Context parentContext = tracingMetadata.map(TracingMetadata::getCurrentContext).orElse(Context.current()); - Context spanContext; - Scope scope = null; - - boolean shouldStart = instrumenter.shouldStart(parentContext, kafkaTrace); - if (shouldStart) { - try { - spanContext = instrumenter.start(parentContext, kafkaTrace); - scope = spanContext.makeCurrent(); - instrumenter.end(spanContext, kafkaTrace, null, null); - } finally { - if (scope != null) { - scope.close(); - } - } - } + .build()); } log.sendingMessageToTopic(message, actualTopic); diff --git a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaSource.java b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaSource.java index 67bbfcc7d3..06f479038a 100644 --- a/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaSource.java +++ b/smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/impl/KafkaSource.java @@ -22,7 +22,6 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessageOperation; @@ -32,7 +31,6 @@ import io.smallrye.common.annotation.Identifier; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; -import io.smallrye.reactive.messaging.TracingMetadata; import io.smallrye.reactive.messaging.health.HealthReport; import io.smallrye.reactive.messaging.kafka.DeserializationFailureHandler; import io.smallrye.reactive.messaging.kafka.IncomingKafkaRecord; @@ -48,6 +46,7 @@ import io.smallrye.reactive.messaging.kafka.tracing.KafkaAttributesExtractor; import io.smallrye.reactive.messaging.kafka.tracing.KafkaTrace; import io.smallrye.reactive.messaging.kafka.tracing.KafkaTraceTextMapGetter; +import io.smallrye.reactive.messaging.tracing.TracingUtils; import io.vertx.core.impl.EventLoopContext; import io.vertx.core.impl.VertxInternal; import io.vertx.mutiny.core.Vertx; @@ -279,31 +278,7 @@ public void incomingTrace(IncomingKafkaRecord kafkaRecord, boolean insideB .withClientId(client.get(ConsumerConfig.CLIENT_ID_CONFIG)) .build(); - TracingMetadata tracingMetadata = TracingMetadata.fromMessage(kafkaRecord).orElse(TracingMetadata.empty()); - Context parentContext = tracingMetadata.getPreviousContext(); - if (parentContext == null) { - parentContext = Context.current(); - } - Context spanContext; - Scope scope = null; - boolean shouldStart = instrumenter.shouldStart(parentContext, kafkaTrace); - - if (shouldStart) { - spanContext = instrumenter.start(parentContext, kafkaTrace); - if (!insideBatch) { - scope = spanContext.makeCurrent(); - } - - kafkaRecord.injectMetadata(TracingMetadata.with(spanContext, parentContext)); - - try { - instrumenter.end(spanContext, kafkaTrace, null, null); - } finally { - if (scope != null) { - scope.close(); - } - } - } + TracingUtils.traceIncoming(instrumenter, kafkaRecord, kafkaTrace, !insideBatch); } } diff --git a/smallrye-reactive-messaging-otel/pom.xml b/smallrye-reactive-messaging-otel/pom.xml new file mode 100644 index 0000000000..3458c38ade --- /dev/null +++ b/smallrye-reactive-messaging-otel/pom.xml @@ -0,0 +1,56 @@ + + + + smallrye-reactive-messaging + io.smallrye.reactive + 3.23.0-SNAPSHOT + + 4.0.0 + + smallrye-reactive-messaging-otel + + SmallRye Reactive Messaging : Open Telemetry Instrumenter + + + + ${project.groupId} + smallrye-reactive-messaging-provider + ${project.version} + + + io.opentelemetry + opentelemetry-api + + + io.opentelemetry + opentelemetry-semconv + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-api + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-api-semconv + + + + + + coverage + + @{jacocoArgLine} + + + + + org.jacoco + jacoco-maven-plugin + + + + + + diff --git a/smallrye-reactive-messaging-otel/src/main/java/io/smallrye/reactive/messaging/tracing/TracingUtils.java b/smallrye-reactive-messaging-otel/src/main/java/io/smallrye/reactive/messaging/tracing/TracingUtils.java new file mode 100644 index 0000000000..74bc791822 --- /dev/null +++ b/smallrye-reactive-messaging-otel/src/main/java/io/smallrye/reactive/messaging/tracing/TracingUtils.java @@ -0,0 +1,80 @@ +package io.smallrye.reactive.messaging.tracing; + +import java.util.Optional; + +import org.eclipse.microprofile.reactive.messaging.Message; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.smallrye.reactive.messaging.TracingMetadata; +import io.smallrye.reactive.messaging.providers.MetadataInjectableMessage; + +public class TracingUtils { + + private TracingUtils() { + } + + public static void traceOutgoing(Instrumenter instrumenter, Message message, T trace) { + Optional tracingMetadata = TracingMetadata.fromMessage(message); + + Context parentContext = tracingMetadata.map(TracingMetadata::getCurrentContext).orElse(Context.current()); + Context spanContext; + Scope scope = null; + + boolean shouldStart = instrumenter.shouldStart(parentContext, trace); + if (shouldStart) { + try { + spanContext = instrumenter.start(parentContext, trace); + scope = spanContext.makeCurrent(); + instrumenter.end(spanContext, trace, null, null); + } finally { + if (scope != null) { + scope.close(); + } + } + } + } + + public static Message traceIncoming(Instrumenter instrumenter, Message msg, T trace) { + return traceIncoming(instrumenter, msg, trace, true); + } + + public static Message traceIncoming(Instrumenter instrumenter, Message msg, T trace, + boolean makeCurrent) { + TracingMetadata tracingMetadata = TracingMetadata.fromMessage(msg).orElse(TracingMetadata.empty()); + Context parentContext = tracingMetadata.getPreviousContext(); + if (parentContext == null) { + parentContext = Context.current(); + } + Context spanContext; + Scope scope = null; + boolean shouldStart = instrumenter.shouldStart(parentContext, trace); + + if (shouldStart) { + spanContext = instrumenter.start(parentContext, trace); + if (makeCurrent) { + scope = spanContext.makeCurrent(); + } + + Message message; + TracingMetadata newTracingMetadata = TracingMetadata.with(spanContext, parentContext); + if (msg instanceof MetadataInjectableMessage) { + ((MetadataInjectableMessage) msg).injectMetadata(newTracingMetadata); + message = msg; + } else { + message = msg.addMetadata(newTracingMetadata); + } + + try { + instrumenter.end(spanContext, trace, null, null); + } finally { + if (scope != null) { + scope.close(); + } + } + return message; + } + return msg; + } +} diff --git a/smallrye-reactive-messaging-provider/src/main/java/io/smallrye/reactive/messaging/providers/MetadataInjectableMessage.java b/smallrye-reactive-messaging-provider/src/main/java/io/smallrye/reactive/messaging/providers/MetadataInjectableMessage.java new file mode 100644 index 0000000000..cd11aac06c --- /dev/null +++ b/smallrye-reactive-messaging-provider/src/main/java/io/smallrye/reactive/messaging/providers/MetadataInjectableMessage.java @@ -0,0 +1,18 @@ +package io.smallrye.reactive.messaging.providers; + +import org.eclipse.microprofile.reactive.messaging.Message; + +/** + * Message type which enables injecting new metadata without creating a new {@link Message} instance + * + * @param type of payload + */ +public interface MetadataInjectableMessage extends Message { + + /** + * Inject the given metadata object + * + * @param metadataObject metadata object + */ + void injectMetadata(Object metadataObject); +} diff --git a/smallrye-reactive-messaging-rabbitmq/pom.xml b/smallrye-reactive-messaging-rabbitmq/pom.xml index a5ff4035d1..1075518e94 100644 --- a/smallrye-reactive-messaging-rabbitmq/pom.xml +++ b/smallrye-reactive-messaging-rabbitmq/pom.xml @@ -28,6 +28,11 @@ smallrye-mutiny-vertx-rabbitmq-client ${smallrye-vertx-mutiny-clients.version} + + io.smallrye.reactive + smallrye-reactive-messaging-otel + ${project.version} + io.vertx vertx-rabbitmq-client @@ -51,23 +56,6 @@ provided - - io.opentelemetry - opentelemetry-api - - - io.opentelemetry - opentelemetry-semconv - - - io.opentelemetry.instrumentation - opentelemetry-instrumentation-api - - - io.opentelemetry.instrumentation - opentelemetry-instrumentation-api-semconv - - org.testcontainers testcontainers diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMessage.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMessage.java index 78626dabae..176924ee52 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMessage.java +++ b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMessage.java @@ -22,6 +22,7 @@ import io.smallrye.reactive.messaging.rabbitmq.fault.RabbitMQFailureHandler; import io.vertx.core.buffer.Buffer; import io.vertx.mutiny.core.Context; +import io.vertx.mutiny.rabbitmq.RabbitMQMessage; /** * An implementation of {@link Message} suitable for incoming RabbitMQ messages. @@ -56,32 +57,25 @@ public CompletionStage handle(IncomingRabbitMQMessage message, Cont private final long deliveryTag; private RabbitMQFailureHandler onNack; private RabbitMQAckHandler onAck; - private final RabbitMQConnectorIncomingConfiguration incomingConfiguration; + private final String contentTypeOverride; private final T payload; - IncomingRabbitMQMessage( - final io.vertx.mutiny.rabbitmq.RabbitMQMessage delegate, - final ConnectionHolder holder, - final RabbitMQFailureHandler onNack, - final RabbitMQAckHandler onAck, - final RabbitMQConnectorIncomingConfiguration incomingConfiguration) { - this(delegate.getDelegate(), holder, onNack, onAck, incomingConfiguration); - } - - IncomingRabbitMQMessage( - final io.vertx.rabbitmq.RabbitMQMessage msg, - final ConnectionHolder holder, - final RabbitMQFailureHandler onNack, - final RabbitMQAckHandler onAck, - final RabbitMQConnectorIncomingConfiguration incomingConfiguration) { + IncomingRabbitMQMessage(RabbitMQMessage delegate, ConnectionHolder holder, + RabbitMQFailureHandler onNack, + RabbitMQAckHandler onAck, String contentTypeOverride) { + this(delegate.getDelegate(), holder, onNack, onAck, contentTypeOverride); + } + + IncomingRabbitMQMessage(io.vertx.rabbitmq.RabbitMQMessage msg, ConnectionHolder holder, + RabbitMQFailureHandler onNack, RabbitMQAckHandler onAck, String contentTypeOverride) { this.message = msg; this.deliveryTag = msg.envelope().getDeliveryTag(); this.holder = holder; this.context = holder.getContext(); - this.rabbitMQMetadata = new IncomingRabbitMQMetadata(message, incomingConfiguration); + this.contentTypeOverride = contentTypeOverride; + this.rabbitMQMetadata = new IncomingRabbitMQMetadata(this.message); this.onNack = onNack; this.onAck = onAck; - this.incomingConfiguration = incomingConfiguration; this.metadata = captureContextMetadata(rabbitMQMetadata); //noinspection unchecked this.payload = (T) convertPayload(message); @@ -155,10 +149,14 @@ public Metadata getMetadata() { private Object convertPayload(io.vertx.rabbitmq.RabbitMQMessage msg) { // Neither of these are guaranteed to be non-null - final String contentType = incomingConfiguration.getContentTypeOverride().orElse(msg.properties().getContentType()); + String contentType = msg.properties().getContentType(); final String contentEncoding = msg.properties().getContentEncoding(); final Buffer body = msg.body(); + if (this.contentTypeOverride != null) { + contentType = contentTypeOverride; + } + // If there is a content encoding specified, we don't try to unwrap if (contentEncoding == null) { try { diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMetadata.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMetadata.java index 7166807bb0..e619d478c8 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMetadata.java +++ b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMetadata.java @@ -21,7 +21,6 @@ public class IncomingRabbitMQMetadata { private final RabbitMQMessage message; - private final RabbitMQConnectorIncomingConfiguration incomingConfiguration; private final Map headers; /** @@ -29,10 +28,8 @@ public class IncomingRabbitMQMetadata { * * @param message the underlying {@link RabbitMQMessage} */ - IncomingRabbitMQMetadata(final RabbitMQMessage message, - final RabbitMQConnectorIncomingConfiguration incomingConfiguration) { + IncomingRabbitMQMetadata(RabbitMQMessage message) { this.message = message; - this.incomingConfiguration = incomingConfiguration; // Ensure the message headers are cast appropriately final Map incomingHeaders = message.properties().getHeaders(); @@ -278,10 +275,6 @@ public Optional getId() { return Optional.ofNullable(message.properties().getMessageId()); } - public String getQueueName() { - return incomingConfiguration.getQueueName(); - } - /** * The exchange the message was delivered to. *

@@ -316,8 +309,4 @@ public String getRoutingKey() { public boolean isRedeliver() { return message.envelope().isRedeliver(); } - - public boolean isTracingEnabled() { - return incomingConfiguration.getTracingEnabled(); - } } diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQConnector.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQConnector.java index 12ce13663a..0057d07e61 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQConnector.java +++ b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQConnector.java @@ -1,5 +1,6 @@ package io.smallrye.reactive.messaging.rabbitmq; +import static io.opentelemetry.instrumentation.api.instrumenter.messaging.MessageOperation.RECEIVE; import static io.smallrye.reactive.messaging.annotations.ConnectorAttribute.Direction.INCOMING; import static io.smallrye.reactive.messaging.annotations.ConnectorAttribute.Direction.INCOMING_AND_OUTGOING; import static io.smallrye.reactive.messaging.annotations.ConnectorAttribute.Direction.OUTGOING; @@ -12,6 +13,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; +import javax.annotation.PostConstruct; import javax.annotation.Priority; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.BeforeDestroyed; @@ -34,6 +36,12 @@ import com.rabbitmq.client.AMQP; import com.rabbitmq.client.impl.CredentialsProvider; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesGetter; +import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingSpanNameExtractor; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.tuples.Tuple2; @@ -48,6 +56,10 @@ import io.smallrye.reactive.messaging.rabbitmq.fault.RabbitMQFailStop; import io.smallrye.reactive.messaging.rabbitmq.fault.RabbitMQFailureHandler; import io.smallrye.reactive.messaging.rabbitmq.fault.RabbitMQReject; +import io.smallrye.reactive.messaging.rabbitmq.tracing.RabbitMQTrace; +import io.smallrye.reactive.messaging.rabbitmq.tracing.RabbitMQTraceAttributesExtractor; +import io.smallrye.reactive.messaging.rabbitmq.tracing.RabbitMQTraceTextMapGetter; +import io.smallrye.reactive.messaging.tracing.TracingUtils; import io.vertx.core.json.JsonObject; import io.vertx.mutiny.core.Vertx; import io.vertx.mutiny.rabbitmq.RabbitMQClient; @@ -141,7 +153,7 @@ public class RabbitMQConnector implements IncomingConnectorFactory, OutgoingConn private enum ChannelStatus { CONNECTED, NOT_CONNECTED, - INITIALISING + INITIALISING; } // The list of RabbitMQClient's currently managed by this connector @@ -166,6 +178,8 @@ private enum ChannelStatus { @Any Instance credentialsProviders; + private Instrumenter instrumenter; + RabbitMQConnector() { // used for proxies } @@ -174,6 +188,19 @@ public static String getExchangeName(final RabbitMQConnectorCommonConfiguration return config.getExchangeName().map(s -> "\"\"".equals(s) ? "" : s).orElse(config.getChannel()); } + @PostConstruct + void init() { + RabbitMQTraceAttributesExtractor rabbitMQAttributesExtractor = new RabbitMQTraceAttributesExtractor(); + MessagingAttributesGetter messagingAttributesGetter = rabbitMQAttributesExtractor + .getMessagingAttributesGetter(); + InstrumenterBuilder builder = Instrumenter.builder(GlobalOpenTelemetry.get(), + "io.smallrye.reactive.messaging", MessagingSpanNameExtractor.create(messagingAttributesGetter, RECEIVE)); + + instrumenter = builder.addAttributesExtractor(rabbitMQAttributesExtractor) + .addAttributesExtractor(MessagingAttributesExtractor.create(messagingAttributesGetter, RECEIVE)) + .buildConsumerInstrumenter(RabbitMQTraceTextMapGetter.INSTANCE); + } + private Multi> getStreamOfMessages( RabbitMQConsumer receiver, ConnectionHolder holder, @@ -181,9 +208,22 @@ private Multi> getStreamOfMessages( RabbitMQFailureHandler onNack, RabbitMQAckHandler onAck) { - log.receiverListeningAddress(ic.getQueueName()); - - return receiver.toMulti().map(m -> new IncomingRabbitMQMessage<>(m, holder, onNack, onAck, ic)); + final String queueName = ic.getQueueName(); + final boolean isTracingEnabled = ic.getTracingEnabled(); + final String contentTypeOverride = ic.getContentTypeOverride().orElse(null); + log.receiverListeningAddress(queueName); + + return receiver.toMulti() + .map(m -> new IncomingRabbitMQMessage<>(m, holder, onNack, onAck, contentTypeOverride)) + .plug(m -> { + if (isTracingEnabled) { + return m.map(msg -> { + TracingUtils.traceIncoming(instrumenter, msg, RabbitMQTrace.trace(queueName, msg.getHeaders())); + return msg; + }); + } + return m; + }); } /** diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMessageConverter.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMessageConverter.java index 0df45d3768..6e34fa7c41 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMessageConverter.java +++ b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMessageConverter.java @@ -16,7 +16,7 @@ import io.netty.handler.codec.http.HttpHeaderValues; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.smallrye.reactive.messaging.rabbitmq.tracing.RabbitMQTrace; -import io.smallrye.reactive.messaging.rabbitmq.tracing.TracingUtils; +import io.smallrye.reactive.messaging.tracing.TracingUtils; import io.vertx.core.json.Json; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; @@ -78,7 +78,7 @@ public static OutgoingRabbitMQMessage convert( if (isTracingEnabled) { // Create a new span for the outbound message and record updated tracing information in // the headers; this has to be done before we build the properties below - TracingUtils.createOutgoingTrace(instrumenter, message, sourceHeaders, exchange); + TracingUtils.traceOutgoing(instrumenter, message, RabbitMQTrace.trace(exchange, sourceHeaders)); } // Reconstruct the properties from the source, except with the (possibly) modified headers; @@ -120,7 +120,7 @@ public static OutgoingRabbitMQMessage convert( if (isTracingEnabled) { // Create a new span for the outbound message and record updated tracing information in // the message headers; this has to be done before we build the properties below - TracingUtils.createOutgoingTrace(instrumenter, message, metadata.getHeaders(), exchange); + TracingUtils.traceOutgoing(instrumenter, message, RabbitMQTrace.trace(exchange, metadata.getHeaders())); } final Date timestamp = (metadata.getTimestamp() != null) ? Date.from(metadata.getTimestamp().toInstant()) : null; diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTracingSubscriberDecorator.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTracingSubscriberDecorator.java deleted file mode 100644 index 1953d29c58..0000000000 --- a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/RabbitMQTracingSubscriberDecorator.java +++ /dev/null @@ -1,87 +0,0 @@ -package io.smallrye.reactive.messaging.rabbitmq.tracing; - -import static io.opentelemetry.instrumentation.api.instrumenter.messaging.MessageOperation.RECEIVE; - -import java.util.List; -import java.util.Optional; - -import javax.enterprise.context.ApplicationScoped; - -import org.eclipse.microprofile.reactive.messaging.Message; - -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; -import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; -import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesGetter; -import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingSpanNameExtractor; -import io.smallrye.mutiny.Multi; -import io.smallrye.reactive.messaging.SubscriberDecorator; -import io.smallrye.reactive.messaging.TracingMetadata; -import io.smallrye.reactive.messaging.rabbitmq.IncomingRabbitMQMetadata; - -@ApplicationScoped -public class RabbitMQTracingSubscriberDecorator implements SubscriberDecorator { - private final Instrumenter instrumenter; - - public RabbitMQTracingSubscriberDecorator() { - RabbitMQTraceAttributesExtractor rabbitMQAttributesExtractor = new RabbitMQTraceAttributesExtractor(); - MessagingAttributesGetter messagingAttributesGetter = rabbitMQAttributesExtractor - .getMessagingAttributesGetter(); - InstrumenterBuilder builder = Instrumenter.builder(GlobalOpenTelemetry.get(), - "io.smallrye.reactive.messaging", MessagingSpanNameExtractor.create(messagingAttributesGetter, RECEIVE)); - - instrumenter = builder.addAttributesExtractor(rabbitMQAttributesExtractor) - .addAttributesExtractor(MessagingAttributesExtractor.create(messagingAttributesGetter, RECEIVE)) - .buildConsumerInstrumenter(RabbitMQTraceTextMapGetter.INSTANCE); - } - - @Override - public Multi> decorate( - final Multi> toBeSubscribed, - final List channelName, - final boolean isConnector) { - return toBeSubscribed.onItem().transform(this::traceMessage); - } - - private Message traceMessage(final Message message) { - Optional incomingRabbitMQMetadata = message.getMetadata().get(IncomingRabbitMQMetadata.class); - if (incomingRabbitMQMetadata.isEmpty()) { - return message; - } - - IncomingRabbitMQMetadata metadata = incomingRabbitMQMetadata.get(); - if (!metadata.isTracingEnabled()) { - // TODO - We can optimize this and not even create the bean? - return message; - } - - TracingMetadata tracingMetadata = TracingMetadata.fromMessage(message).orElse(TracingMetadata.empty()); - RabbitMQTrace trace = RabbitMQTrace.trace(metadata.getQueueName(), metadata.getHeaders()); - - Context parentContext = tracingMetadata.getPreviousContext(); - if (parentContext == null) { - parentContext = Context.current(); - } - Context spanContext; - Scope scope = null; - - boolean shouldStart = instrumenter.shouldStart(parentContext, trace); - if (shouldStart) { - try { - spanContext = instrumenter.start(parentContext, trace); - scope = spanContext.makeCurrent(); - instrumenter.end(spanContext, trace, null, null); - return message.addMetadata(TracingMetadata.with(spanContext, parentContext)); - } finally { - if (scope != null) { - scope.close(); - } - } - } - - return message; - } -} diff --git a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/TracingUtils.java b/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/TracingUtils.java deleted file mode 100644 index cfd8a48c69..0000000000 --- a/smallrye-reactive-messaging-rabbitmq/src/main/java/io/smallrye/reactive/messaging/rabbitmq/tracing/TracingUtils.java +++ /dev/null @@ -1,49 +0,0 @@ -package io.smallrye.reactive.messaging.rabbitmq.tracing; - -import java.util.Map; -import java.util.Optional; - -import org.eclipse.microprofile.reactive.messaging.Message; - -import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; -import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.smallrye.reactive.messaging.TracingMetadata; - -/** - * Utility methods to manage spans for incoming and outgoing messages. - */ -public abstract class TracingUtils { - /** - * Private constructor to prevent instantiation. - */ - private TracingUtils() { - } - - public static void createOutgoingTrace( - final Instrumenter instrumenter, - final Message msg, - final Map headers, - final String exchange) { - - Optional tracingMetadata = TracingMetadata.fromMessage(msg); - RabbitMQTrace message = RabbitMQTrace.trace(exchange, headers); - - Context parentContext = tracingMetadata.map(TracingMetadata::getCurrentContext).orElse(Context.current()); - Context spanContext; - Scope scope = null; - - boolean shouldStart = instrumenter.shouldStart(parentContext, message); - if (shouldStart) { - try { - spanContext = instrumenter.start(parentContext, message); - scope = spanContext.makeCurrent(); - instrumenter.end(spanContext, message, null, null); - } finally { - if (scope != null) { - scope.close(); - } - } - } - } -} diff --git a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/ConsumptionBean.java b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/ConsumptionBean.java index a4acb56f20..71b19771f0 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/ConsumptionBean.java +++ b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/ConsumptionBean.java @@ -25,7 +25,7 @@ public class ConsumptionBean { @Incoming("data") @Outgoing("sink") @Acknowledgment(Acknowledgment.Strategy.MANUAL) - public Message process(IncomingRabbitMQMessage input) { + public Message process(Message input) { int value = -1; try { value = Integer.parseInt(input.getPayload()); diff --git a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMessageTest.java b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMessageTest.java index aceceb1b01..976b6922fd 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMessageTest.java +++ b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMessageTest.java @@ -5,7 +5,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -46,13 +45,12 @@ public void testDoubleAckBehavior() { when(mockMsg.envelope()).thenReturn(new Envelope(13456, false, "test", "test")); RabbitMQMessage msg = RabbitMQMessage.newInstance(mockMsg); - RabbitMQConnectorIncomingConfiguration incomingConfiguration = mock(RabbitMQConnectorIncomingConfiguration.class); - when(incomingConfiguration.getContentTypeOverride()).thenReturn(Optional.of("text/plain")); - Exception nackReason = new Exception("test"); - IncomingRabbitMQMessage ackMsg = new IncomingRabbitMQMessage<>(msg, mock(ConnectionHolder.class), doNothingNack, - doNothingAck, incomingConfiguration); + IncomingRabbitMQMessage ackMsg = new IncomingRabbitMQMessage<>(msg, mock(ConnectionHolder.class), + doNothingNack, + doNothingAck, + "text/plain"); assertDoesNotThrow(() -> ackMsg.ack().toCompletableFuture().get()); assertDoesNotThrow(() -> ackMsg.ack().toCompletableFuture().get()); @@ -67,13 +65,12 @@ public void testDoubleNackBehavior() { when(mockMsg.envelope()).thenReturn(new Envelope(13456, false, "test", "test")); RabbitMQMessage msg = RabbitMQMessage.newInstance(mockMsg); - RabbitMQConnectorIncomingConfiguration incomingConfiguration = mock(RabbitMQConnectorIncomingConfiguration.class); - when(incomingConfiguration.getContentTypeOverride()).thenReturn(Optional.of("text/plain")); - Exception nackReason = new Exception("test"); IncomingRabbitMQMessage nackMsg = new IncomingRabbitMQMessage<>(msg, mock(ConnectionHolder.class), - doNothingNack, doNothingAck, incomingConfiguration); + doNothingNack, + doNothingAck, + "text/plain"); assertDoesNotThrow(() -> nackMsg.nack(nackReason).toCompletableFuture().get()); assertDoesNotThrow(() -> nackMsg.nack(nackReason).toCompletableFuture().get()); @@ -88,11 +85,9 @@ void testConvertPayload() { when(mockMsg.envelope()).thenReturn(new Envelope(13456, false, "test", "test")); RabbitMQMessage msg = RabbitMQMessage.newInstance(mockMsg); - RabbitMQConnectorIncomingConfiguration incomingConfiguration = mock(RabbitMQConnectorIncomingConfiguration.class); - when(incomingConfiguration.getContentTypeOverride()).thenReturn(Optional.of("text/plain")); - IncomingRabbitMQMessage incomingRabbitMQMessage = new IncomingRabbitMQMessage<>(msg, - mock(ConnectionHolder.class), doNothingNack, doNothingAck, incomingConfiguration); + mock(ConnectionHolder.class), + doNothingNack, doNothingAck, "text/plain"); assertThat(incomingRabbitMQMessage.getPayload()).isEqualTo("payload"); } @@ -107,11 +102,9 @@ void testConvertPayloadJsonObject() { when(mockMsg.envelope()).thenReturn(new Envelope(13456, false, "test", "test")); RabbitMQMessage msg = RabbitMQMessage.newInstance(mockMsg); - RabbitMQConnectorIncomingConfiguration incomingConfiguration = mock(RabbitMQConnectorIncomingConfiguration.class); - when(incomingConfiguration.getContentTypeOverride()).thenReturn(Optional.empty()); - IncomingRabbitMQMessage incomingRabbitMQMessage = new IncomingRabbitMQMessage<>(msg, - mock(ConnectionHolder.class), doNothingNack, doNothingAck, incomingConfiguration); + mock(ConnectionHolder.class), + doNothingNack, doNothingAck, null); assertThat(incomingRabbitMQMessage.getPayload()).isEqualTo(payload); } @@ -125,12 +118,9 @@ void testConvertPayloadFallback() { when(mockMsg.envelope()).thenReturn(new Envelope(13456, false, "test", "test")); RabbitMQMessage msg = RabbitMQMessage.newInstance(mockMsg); - RabbitMQConnectorIncomingConfiguration incomingConfiguration = mock(RabbitMQConnectorIncomingConfiguration.class); - when(incomingConfiguration.getContentTypeOverride()).thenReturn(Optional.empty()); - IncomingRabbitMQMessage incomingRabbitMQMessage = new IncomingRabbitMQMessage<>(msg, mock(ConnectionHolder.class), - doNothingNack, doNothingAck, incomingConfiguration); + doNothingNack, doNothingAck, null); assertThat(((Message) ((Message) incomingRabbitMQMessage)).getPayload()).isEqualTo(payloadBuffer.getBytes()); } diff --git a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMetadataTest.java b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMetadataTest.java index b9e9ef2279..9faa2a87c4 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMetadataTest.java +++ b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/IncomingRabbitMQMetadataTest.java @@ -1,12 +1,10 @@ package io.smallrye.reactive.messaging.rabbitmq; -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertEquals; - import java.util.Date; import java.util.HashMap; import java.util.Map; +import org.junit.Assert; import org.junit.jupiter.api.Test; import com.rabbitmq.client.BasicProperties; @@ -25,15 +23,15 @@ public void testHeaderWithNullValue() { DummyRabbitMQMessage message = new DummyRabbitMQMessage(new DummyBasicProperties(properties)); - IncomingRabbitMQMetadata incomingRabbitMQMetadata = new IncomingRabbitMQMetadata(message, null); + IncomingRabbitMQMetadata incomingRabbitMQMetadata = new IncomingRabbitMQMetadata(message); - assertEquals("value1", incomingRabbitMQMetadata.getHeaders().get("header1")); - assertTrue(incomingRabbitMQMetadata.getHeaders().containsKey("header2")); - assertNull(incomingRabbitMQMetadata.getHeaders().get("header2")); + Assert.assertEquals("value1", incomingRabbitMQMetadata.getHeaders().get("header1")); + Assert.assertTrue(incomingRabbitMQMetadata.getHeaders().containsKey("header2")); + Assert.assertNull(incomingRabbitMQMetadata.getHeaders().get("header2")); } - static class DummyRabbitMQMessage implements RabbitMQMessage { + class DummyRabbitMQMessage implements RabbitMQMessage { protected BasicProperties properties; DummyRabbitMQMessage(BasicProperties properties) { @@ -66,7 +64,7 @@ public Integer messageCount() { } } - static class DummyBasicProperties implements BasicProperties { + class DummyBasicProperties implements BasicProperties { protected Map headers; DummyBasicProperties(Map headers) { diff --git a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMetadataTest.java b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMetadataTest.java index 5be9b0f7ed..37c3daccbc 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMetadataTest.java +++ b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/RabbitMQMetadataTest.java @@ -66,7 +66,7 @@ public Integer messageCount() { } }; - IncomingRabbitMQMetadata incoming = new IncomingRabbitMQMetadata(message, null); + IncomingRabbitMQMetadata incoming = new IncomingRabbitMQMetadata(message); assertThat(incoming.getUserId()).isEqualTo(Optional.of("test-user")); assertThat(incoming.getAppId()).isEqualTo(Optional.of("tests")); assertThat(incoming.getContentType()).isEqualTo(Optional.of("text/plain")); diff --git a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/TracingTest.java b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/TracingTest.java index c5d7d5e012..25d88134b4 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/TracingTest.java +++ b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/TracingTest.java @@ -47,7 +47,6 @@ import io.opentelemetry.sdk.trace.samplers.Sampler; import io.smallrye.reactive.messaging.providers.connectors.InMemoryConnector; import io.smallrye.reactive.messaging.providers.connectors.InMemorySource; -import io.smallrye.reactive.messaging.rabbitmq.tracing.RabbitMQTracingSubscriberDecorator; public class TracingTest extends WeldTestBase { private SdkTracerProvider tracerProvider; @@ -69,8 +68,6 @@ public void openTelemetry() { .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) .setTracerProvider(tracerProvider) .buildAndRegisterGlobal(); - - addBeans(RabbitMQTracingSubscriberDecorator.class); } @Test diff --git a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/WeldTestBase.java b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/WeldTestBase.java index 79953f3084..8fd733c7e6 100644 --- a/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/WeldTestBase.java +++ b/smallrye-reactive-messaging-rabbitmq/src/test/java/io/smallrye/reactive/messaging/rabbitmq/WeldTestBase.java @@ -14,7 +14,6 @@ import io.smallrye.config.inject.ConfigExtension; import io.smallrye.reactive.messaging.providers.MediatorFactory; -import io.smallrye.reactive.messaging.providers.OutgoingInterceptorDecorator; import io.smallrye.reactive.messaging.providers.connectors.ExecutionHolder; import io.smallrye.reactive.messaging.providers.connectors.WorkerPoolRegistry; import io.smallrye.reactive.messaging.providers.extension.ChannelProducer; @@ -70,7 +69,6 @@ public void initWeld() { weld.addBeanClass(MetricDecorator.class); weld.addBeanClass(MicrometerDecorator.class); weld.addBeanClass(ContextDecorator.class); - weld.addBeanClass(OutgoingInterceptorDecorator.class); weld.disableDiscovery(); }