diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/ConsumerRecordsInstrumentation.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/ConsumerRecordsInstrumentation.java index fa39f33a87..1de5e1e4b4 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/ConsumerRecordsInstrumentation.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/ConsumerRecordsInstrumentation.java @@ -21,6 +21,7 @@ import java.util.List; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; +import org.apache.kafka.clients.consumer.Consumer; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; @@ -59,6 +60,7 @@ public class ConsumerRecordsInstrumentation implements TypeInstrumentation { @SuppressWarnings("unused") public static class IterableAdvice { + @SuppressWarnings("unchecked") @Advice.OnMethodExit(suppress = Throwable.class) public static void wrap( @Advice.This ConsumerRecords records, @@ -69,13 +71,16 @@ public class ConsumerRecordsInstrumentation implements TypeInstrumentation { // case it's important to overwrite the leaked span instead of suppressing the correct span // (https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/1947) Context receiveContext = VirtualField.find(ConsumerRecords.class, Context.class).get(records); - iterable = TracingIterable.wrap(iterable, receiveContext); + Consumer consumer = + VirtualField.find(ConsumerRecords.class, Consumer.class).get(records); + iterable = TracingIterable.wrap(iterable, receiveContext, consumer); } } @SuppressWarnings("unused") public static class ListAdvice { + @SuppressWarnings("unchecked") @Advice.OnMethodExit(suppress = Throwable.class) public static void wrap( @Advice.This ConsumerRecords records, @@ -86,13 +91,16 @@ public class ConsumerRecordsInstrumentation implements TypeInstrumentation { // case it's important to overwrite the leaked span instead of suppressing the correct span // (https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/1947) Context receiveContext = VirtualField.find(ConsumerRecords.class, Context.class).get(records); - list = TracingList.wrap(list, receiveContext); + Consumer consumer = + VirtualField.find(ConsumerRecords.class, Consumer.class).get(records); + list = TracingList.wrap(list, receiveContext, consumer); } } @SuppressWarnings("unused") public static class IteratorAdvice { + @SuppressWarnings("unchecked") @Advice.OnMethodExit(suppress = Throwable.class) public static void wrap( @Advice.This ConsumerRecords records, @@ -103,7 +111,9 @@ public class ConsumerRecordsInstrumentation implements TypeInstrumentation { // case it's important to overwrite the leaked span instead of suppressing the correct span // (https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/1947) Context receiveContext = VirtualField.find(ConsumerRecords.class, Context.class).get(records); - iterator = TracingIterator.wrap(iterator, receiveContext); + Consumer consumer = + VirtualField.find(ConsumerRecords.class, Consumer.class).get(records); + iterator = TracingIterator.wrap(iterator, receiveContext, consumer); } } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaConsumerInstrumentation.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaConsumerInstrumentation.java index 8e76eb0d7b..7090267725 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaConsumerInstrumentation.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaConsumerInstrumentation.java @@ -18,6 +18,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.instrumentation.kafka.internal.ConsumerAndRecord; import io.opentelemetry.instrumentation.kafka.internal.Timer; import io.opentelemetry.javaagent.bootstrap.kafka.KafkaClientsConsumerProcessTracing; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; @@ -28,6 +29,7 @@ import java.util.Properties; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; +import org.apache.kafka.clients.consumer.Consumer; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; @@ -83,6 +85,7 @@ public class KafkaConsumerInstrumentation implements TypeInstrumentation { @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class) public static void onExit( @Advice.Enter Timer timer, + @Advice.This Consumer consumer, @Advice.Return ConsumerRecords records, @Advice.Thrown Throwable error) { @@ -91,8 +94,17 @@ public class KafkaConsumerInstrumentation implements TypeInstrumentation { return; } + // we're attaching the consumer to the records to be able to retrieve things like consumer + // group or clientId later + VirtualField, Consumer> consumerRecordsConsumer = + VirtualField.find(ConsumerRecords.class, Consumer.class); + consumerRecordsConsumer.set(records, consumer); + Context parentContext = currentContext(); - if (consumerReceiveInstrumenter().shouldStart(parentContext, records)) { + ConsumerAndRecord> request = + ConsumerAndRecord.create(consumer, records); + + if (consumerReceiveInstrumenter().shouldStart(parentContext, request)) { // disable process tracing and store the receive span for each individual record too boolean previousValue = KafkaClientsConsumerProcessTracing.setEnabled(false); try { @@ -100,15 +112,14 @@ public class KafkaConsumerInstrumentation implements TypeInstrumentation { InstrumenterUtil.startAndEnd( consumerReceiveInstrumenter(), parentContext, - records, + request, null, error, timer.startTime(), timer.now()); // we're storing the context of the receive span so that process spans can use it as - // parent - // context even though the span has ended + // parent context even though the span has ended // this is the suggested behavior according to the spec batch receive scenario: // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/messaging.md#batch-receiving VirtualField, Context> consumerRecordsContext = diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaSingletons.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaSingletons.java index 3133db5742..72df89de0c 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaSingletons.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaSingletons.java @@ -7,6 +7,7 @@ package io.opentelemetry.javaagent.instrumentation.kafkaclients.v0_11; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.kafka.internal.ConsumerAndRecord; import io.opentelemetry.instrumentation.kafka.internal.KafkaInstrumenterFactory; import io.opentelemetry.instrumentation.kafka.internal.OpenTelemetryMetricsReporter; import io.opentelemetry.instrumentation.kafka.internal.OpenTelemetrySupplier; @@ -34,8 +35,10 @@ public final class KafkaSingletons { .getBoolean("otel.instrumentation.kafka.metric-reporter.enabled", true); private static final Instrumenter, RecordMetadata> PRODUCER_INSTRUMENTER; - private static final Instrumenter, Void> CONSUMER_RECEIVE_INSTRUMENTER; - private static final Instrumenter, Void> CONSUMER_PROCESS_INSTRUMENTER; + private static final Instrumenter>, Void> + CONSUMER_RECEIVE_INSTRUMENTER; + private static final Instrumenter>, Void> + CONSUMER_PROCESS_INSTRUMENTER; static { KafkaInstrumenterFactory instrumenterFactory = @@ -59,11 +62,13 @@ public final class KafkaSingletons { return PRODUCER_INSTRUMENTER; } - public static Instrumenter, Void> consumerReceiveInstrumenter() { + public static Instrumenter>, Void> + consumerReceiveInstrumenter() { return CONSUMER_RECEIVE_INSTRUMENTER; } - public static Instrumenter, Void> consumerProcessInstrumenter() { + public static Instrumenter>, Void> + consumerProcessInstrumenter() { return CONSUMER_PROCESS_INSTRUMENTER; } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/TracingIterable.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/TracingIterable.java index 1ce84406c6..4529475848 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/TracingIterable.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/TracingIterable.java @@ -9,23 +9,30 @@ import io.opentelemetry.context.Context; import io.opentelemetry.javaagent.bootstrap.kafka.KafkaClientsConsumerProcessTracing; import java.util.Iterator; import javax.annotation.Nullable; +import org.apache.kafka.clients.consumer.Consumer; import org.apache.kafka.clients.consumer.ConsumerRecord; public class TracingIterable implements Iterable> { private final Iterable> delegate; @Nullable private final Context receiveContext; + private final Consumer consumer; private boolean firstIterator = true; protected TracingIterable( - Iterable> delegate, @Nullable Context receiveContext) { + Iterable> delegate, + @Nullable Context receiveContext, + Consumer consumer) { this.delegate = delegate; this.receiveContext = receiveContext; + this.consumer = consumer; } public static Iterable> wrap( - Iterable> delegate, @Nullable Context receiveContext) { + Iterable> delegate, + @Nullable Context receiveContext, + Consumer consumer) { if (KafkaClientsConsumerProcessTracing.wrappingEnabled()) { - return new TracingIterable<>(delegate, receiveContext); + return new TracingIterable<>(delegate, receiveContext, consumer); } return delegate; } @@ -37,7 +44,7 @@ public class TracingIterable implements Iterable> { // However, this is not thread-safe, but usually the first (hopefully only) traversal of // ConsumerRecords is performed in the same thread that called poll() if (firstIterator) { - it = TracingIterator.wrap(delegate.iterator(), receiveContext); + it = TracingIterator.wrap(delegate.iterator(), receiveContext, consumer); firstIterator = false; } else { it = delegate.iterator(); diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/TracingIterator.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/TracingIterator.java index f6cec590fb..222a500979 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/TracingIterator.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/TracingIterator.java @@ -9,35 +9,44 @@ import static io.opentelemetry.javaagent.instrumentation.kafkaclients.v0_11.Kafk import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.kafka.internal.ConsumerAndRecord; import io.opentelemetry.javaagent.bootstrap.kafka.KafkaClientsConsumerProcessTracing; import java.util.Iterator; import javax.annotation.Nullable; +import org.apache.kafka.clients.consumer.Consumer; import org.apache.kafka.clients.consumer.ConsumerRecord; public class TracingIterator implements Iterator> { + private final Iterator> delegateIterator; private final Context parentContext; + private final Consumer consumer; /* * Note: this may potentially create problems if this iterator is used from different threads. But * at the moment we cannot do much about this. */ - @Nullable private ConsumerRecord currentRequest; + @Nullable private ConsumerAndRecord> currentRequest; @Nullable private Context currentContext; @Nullable private Scope currentScope; private TracingIterator( - Iterator> delegateIterator, @Nullable Context receiveContext) { + Iterator> delegateIterator, + @Nullable Context receiveContext, + Consumer consumer) { this.delegateIterator = delegateIterator; // use the receive CONSUMER as parent if it's available this.parentContext = receiveContext != null ? receiveContext : Context.current(); + this.consumer = consumer; } public static Iterator> wrap( - Iterator> delegateIterator, @Nullable Context receiveContext) { + Iterator> delegateIterator, + @Nullable Context receiveContext, + Consumer consumer) { if (KafkaClientsConsumerProcessTracing.wrappingEnabled()) { - return new TracingIterator<>(delegateIterator, receiveContext); + return new TracingIterator<>(delegateIterator, receiveContext, consumer); } return delegateIterator; } @@ -60,7 +69,7 @@ public class TracingIterator implements Iterator> { // (https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/1947) ConsumerRecord next = delegateIterator.next(); if (next != null && KafkaClientsConsumerProcessTracing.wrappingEnabled()) { - currentRequest = next; + currentRequest = ConsumerAndRecord.create(consumer, next); currentContext = consumerProcessInstrumenter().start(parentContext, currentRequest); currentScope = currentContext.makeCurrent(); } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/TracingList.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/TracingList.java index 6c77d7c1bb..bf295cd5f9 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/TracingList.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/TracingList.java @@ -11,20 +11,26 @@ import java.util.Collection; import java.util.List; import java.util.ListIterator; import javax.annotation.Nullable; +import org.apache.kafka.clients.consumer.Consumer; import org.apache.kafka.clients.consumer.ConsumerRecord; public class TracingList extends TracingIterable implements List> { private final List> delegate; - private TracingList(List> delegate, @Nullable Context receiveContext) { - super(delegate, receiveContext); + private TracingList( + List> delegate, + @Nullable Context receiveContext, + Consumer consumer) { + super(delegate, receiveContext, consumer); this.delegate = delegate; } public static List> wrap( - List> delegate, @Nullable Context receiveContext) { + List> delegate, + @Nullable Context receiveContext, + Consumer consumer) { if (KafkaClientsConsumerProcessTracing.wrappingEnabled()) { - return new TracingList<>(delegate, receiveContext); + return new TracingList<>(delegate, receiveContext, consumer); } return delegate; } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java index e80bcd5c2b..093d6ea2e8 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java @@ -6,11 +6,8 @@ package io.opentelemetry.javaagent.instrumentation.kafkaclients.v0_11; import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanKind; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.instrumentation.kafka.internal.KafkaClientBaseTest; import io.opentelemetry.instrumentation.kafka.internal.KafkaClientPropagationBaseTest; @@ -18,10 +15,8 @@ import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtens import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.sdk.trace.data.LinkData; import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -30,7 +25,6 @@ import java.util.concurrent.atomic.AtomicReference; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.producer.ProducerRecord; -import org.assertj.core.api.AbstractLongAssert; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -51,7 +45,7 @@ class KafkaClientDefaultTest extends KafkaClientPropagationBaseTest { "parent", () -> { ProducerRecord producerRecord = - new ProducerRecord<>(SHARED_TOPIC, greeting); + new ProducerRecord<>(SHARED_TOPIC, 10, greeting); if (testHeaders) { producerRecord .headers() @@ -80,8 +74,8 @@ class KafkaClientDefaultTest extends KafkaClientPropagationBaseTest { testing.runWithSpan( "processing", () -> { + assertThat(record.key()).isEqualTo(10); assertThat(record.value()).isEqualTo(greeting); - assertThat(record.key()).isNull(); }); } AtomicReference producerSpan = new AtomicReference<>(); @@ -89,85 +83,32 @@ class KafkaClientDefaultTest extends KafkaClientPropagationBaseTest { orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CONSUMER), trace -> { trace.hasSpansSatisfyingExactly( - span -> { - span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(); - }, - span -> { - span.hasName(SHARED_TOPIC + " send") - .hasKind(SpanKind.PRODUCER) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfying( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)); - if (testHeaders) { - span.hasAttributesSatisfying( - equalTo( - AttributeKey.stringArrayKey("messaging.header.test_message_header"), - Collections.singletonList("test"))); - } - }, - span -> { - span.hasName("producer callback") - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)); - }); + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(SHARED_TOPIC + " send") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly(sendAttributes("10", greeting, testHeaders)), + span -> + span.hasName("producer callback") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0))); producerSpan.set(trace.getSpan(1)); }, trace -> trace.hasSpansSatisfyingExactly( - span -> { - span.hasName(SHARED_TOPIC + " receive") - .hasKind(SpanKind.CONSUMER) - .hasNoParent() - .hasAttributesSatisfying( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive")); - if (testHeaders) { - span.hasAttributesSatisfying( - equalTo( - AttributeKey.stringArrayKey("messaging.header.test_message_header"), - Collections.singletonList("test"))); - } - }, - span -> { - span.hasName(SHARED_TOPIC + " process") - .hasKind(SpanKind.CONSUMER) - .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfying( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), - equalTo( - SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, - greeting.getBytes(StandardCharsets.UTF_8).length), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("kafka.record.queue_time_ms"), - AbstractLongAssert::isNotNegative)); - - if (testHeaders) { - span.hasAttributesSatisfying( - equalTo( - AttributeKey.stringArrayKey("messaging.header.test_message_header"), - Collections.singletonList("test"))); - } - }, + span -> + span.hasName(SHARED_TOPIC + " receive") + .hasKind(SpanKind.CONSUMER) + .hasNoParent() + .hasAttributesSatisfyingExactly(receiveAttributes(testHeaders)), + span -> + span.hasName(SHARED_TOPIC + " process") + .hasKind(SpanKind.CONSUMER) + .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + processAttributes("10", greeting, testHeaders)), span -> span.hasName("processing").hasParent(trace.getSpan(1)))); } @@ -192,59 +133,26 @@ class KafkaClientDefaultTest extends KafkaClientPropagationBaseTest { orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CONSUMER), trace -> { trace.hasSpansSatisfyingExactly( - span -> { - span.hasName(SHARED_TOPIC + " send") - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasAttributesSatisfying( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_TOMBSTONE, true), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)); - }); + span -> + span.hasName(SHARED_TOPIC + " send") + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasAttributesSatisfyingExactly(sendAttributes(null, null, false))); producerSpan.set(trace.getSpan(0)); }, - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - span.hasName(SHARED_TOPIC + " receive") - .hasKind(SpanKind.CONSUMER) - .hasNoParent() - .hasAttributesSatisfying( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive")); - }, - span -> { - span.hasName(SHARED_TOPIC + " process") - .hasKind(SpanKind.CONSUMER) - .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfying( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), - equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_TOMBSTONE, true), - equalTo(SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, -1L), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("kafka.record.queue_time_ms"), - AbstractLongAssert::isNotNegative)); - }); - }); + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(SHARED_TOPIC + " receive") + .hasKind(SpanKind.CONSUMER) + .hasNoParent() + .hasAttributesSatisfyingExactly(receiveAttributes(false)), + span -> + span.hasName(SHARED_TOPIC + " process") + .hasKind(SpanKind.CONSUMER) + .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly(processAttributes(null, null, false)))); } @DisplayName("test records(TopicPartition) kafka consume") @@ -276,55 +184,25 @@ class KafkaClientDefaultTest extends KafkaClientPropagationBaseTest { orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CONSUMER), trace -> { trace.hasSpansSatisfyingExactly( - span -> { - span.hasName(SHARED_TOPIC + " send") - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasAttributesSatisfying( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo( - SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, partition), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)); - }); + span -> + span.hasName(SHARED_TOPIC + " send") + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasAttributesSatisfyingExactly(sendAttributes(null, greeting, false))); producerSpan.set(trace.getSpan(0)); }, - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - span.hasName(SHARED_TOPIC + " receive") - .hasKind(SpanKind.CONSUMER) - .hasNoParent() - .hasAttributesSatisfying( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive")); - }, - span -> { - span.hasName(SHARED_TOPIC + " process") - .hasKind(SpanKind.CONSUMER) - .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfying( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), - equalTo( - SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, - greeting.getBytes(StandardCharsets.UTF_8).length), - equalTo(SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, partition), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("kafka.record.queue_time_ms"), - AbstractLongAssert::isNotNegative)); - }); - }); + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(SHARED_TOPIC + " receive") + .hasKind(SpanKind.CONSUMER) + .hasNoParent() + .hasAttributesSatisfyingExactly(receiveAttributes(false)), + span -> + span.hasName(SHARED_TOPIC + " process") + .hasKind(SpanKind.CONSUMER) + .hasLinks(LinkData.create(producerSpan.get().getSpanContext())) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly(processAttributes(null, greeting, false)))); } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientPropagationDisabledTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientPropagationDisabledTest.java index 1366d6cd43..e465a4737b 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientPropagationDisabledTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientPropagationDisabledTest.java @@ -5,23 +5,17 @@ package io.opentelemetry.javaagent.instrumentation.kafkaclients.v0_11; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.instrumentation.kafka.internal.KafkaClientPropagationBaseTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Collections; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.producer.ProducerRecord; -import org.assertj.core.api.AbstractLongAssert; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -37,24 +31,13 @@ class KafkaClientPropagationDisabledTest extends KafkaClientPropagationBaseTest producer.send(new ProducerRecord<>(SHARED_TOPIC, message)); testing.waitAndAssertTraces( - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - span.hasName(SHARED_TOPIC + " send") - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasAttributesSatisfying( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)); - }); - }); + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(SHARED_TOPIC + " send") + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasAttributesSatisfyingExactly(sendAttributes(null, message, false)))); awaitUntilConsumerIsReady(); @@ -68,49 +51,20 @@ class KafkaClientPropagationDisabledTest extends KafkaClientPropagationBaseTest } testing.waitAndAssertTraces( - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - span.hasName(SHARED_TOPIC + " send") - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasAttributesSatisfying( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)); - }); - }, - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - span.hasName(SHARED_TOPIC + " process") - .hasKind(SpanKind.CONSUMER) - .hasLinks(Collections.emptyList()) - .hasAttributesSatisfying( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), - equalTo( - SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, - message.getBytes(StandardCharsets.UTF_8).length), - equalTo(SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, partition), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("kafka.record.queue_time_ms"), - AbstractLongAssert::isNotNegative)); - }, - span -> { - span.hasName("processing").hasParent(trace.getSpan(0)); - }); - }); + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(SHARED_TOPIC + " send") + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasAttributesSatisfyingExactly(sendAttributes(null, message, false))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(SHARED_TOPIC + " process") + .hasKind(SpanKind.CONSUMER) + .hasLinks(Collections.emptyList()) + .hasAttributesSatisfyingExactly(processAttributes(null, message, false)), + span -> span.hasName("processing").hasParent(trace.getSpan(0)))); } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java index 8fda8424b6..f2b951b695 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java @@ -5,19 +5,13 @@ package io.opentelemetry.javaagent.instrumentation.kafkaclients.v0_11; -import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanKind; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.instrumentation.kafka.internal.KafkaClientBaseTest; import io.opentelemetry.instrumentation.kafka.internal.KafkaClientPropagationBaseTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.List; import java.util.concurrent.ExecutionException; @@ -26,7 +20,6 @@ import java.util.concurrent.TimeoutException; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.producer.ProducerRecord; -import org.assertj.core.api.AbstractLongAssert; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -41,7 +34,7 @@ class KafkaClientSuppressReceiveSpansTest extends KafkaClientPropagationBaseTest "parent", () -> { producer.send( - new ProducerRecord<>(SHARED_TOPIC, greeting), + new ProducerRecord<>(SHARED_TOPIC, 10, greeting), (meta, ex) -> { if (ex == null) { testing.runWithSpan("producer callback", () -> {}); @@ -59,63 +52,33 @@ class KafkaClientSuppressReceiveSpansTest extends KafkaClientPropagationBaseTest testing.runWithSpan( "processing", () -> { + assertThat(record.key()).isEqualTo(10); assertThat(record.value()).isEqualTo(greeting); - assertThat(record.key()).isNull(); }); } testing.waitAndAssertTraces( - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(); - }, - span -> { - span.hasName(SHARED_TOPIC + " send") - .hasKind(SpanKind.PRODUCER) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfying( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)); - }, - span -> { - span.hasName(SHARED_TOPIC + " process") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(1)) - .hasAttributesSatisfying( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), - equalTo( - SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, - greeting.getBytes(StandardCharsets.UTF_8).length), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("kafka.record.queue_time_ms"), - AbstractLongAssert::isNotNegative)); - }, - span -> { - span.hasName("processing").hasKind(SpanKind.INTERNAL).hasParent(trace.getSpan(2)); - }, - span -> { - span.hasName("producer callback") - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)); - }); - }); + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(SHARED_TOPIC + " send") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly(sendAttributes("10", greeting, false)), + span -> + span.hasName(SHARED_TOPIC + " process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly(processAttributes("10", greeting, false)), + span -> + span.hasName("processing") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(2)), + span -> + span.hasName("producer callback") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)))); } @Test @@ -133,48 +96,19 @@ class KafkaClientSuppressReceiveSpansTest extends KafkaClientPropagationBaseTest assertThat(record.key()).isNull(); } - testing.waitAndAssertSortedTraces( - orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CONSUMER), - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - span.hasName(SHARED_TOPIC + " send") - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasAttributesSatisfying( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_TOMBSTONE, true), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)); - }, - span -> { - span.hasName(SHARED_TOPIC + " process") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfying( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), - equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_TOMBSTONE, true), - equalTo(SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, -1L), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("kafka.record.queue_time_ms"), - AbstractLongAssert::isNotNegative)); - }); - }); + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(SHARED_TOPIC + " send") + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasAttributesSatisfyingExactly(sendAttributes(null, null, false)), + span -> + span.hasName(SHARED_TOPIC + " process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly(processAttributes(null, null, false)))); } @Test @@ -200,44 +134,18 @@ class KafkaClientSuppressReceiveSpansTest extends KafkaClientPropagationBaseTest assertThat(record.key()).isNull(); } - testing.waitAndAssertSortedTraces( - orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CONSUMER), - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - span.hasName(SHARED_TOPIC + " send") - .hasKind(SpanKind.PRODUCER) - .hasNoParent() - .hasAttributesSatisfying( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo( - SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, partition), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)); - }, - span -> { - span.hasName(SHARED_TOPIC + " process") - .hasKind(SpanKind.CONSUMER) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfying( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), - equalTo( - SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, - greeting.getBytes(StandardCharsets.UTF_8).length), - equalTo(SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, partition), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("kafka.record.queue_time_ms"), - AbstractLongAssert::isNotNegative)); - }); - }); + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(SHARED_TOPIC + " send") + .hasKind(SpanKind.PRODUCER) + .hasNoParent() + .hasAttributesSatisfyingExactly(sendAttributes(null, greeting, false)), + span -> + span.hasName(SHARED_TOPIC + " process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly(processAttributes(null, greeting, false)))); } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/testing/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaClientBaseTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/testing/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaClientBaseTest.java index 5705c59f74..935e840bc1 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/testing/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaClientBaseTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/testing/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaClientBaseTest.java @@ -5,10 +5,20 @@ package io.opentelemetry.instrumentation.kafka.internal; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -25,6 +35,7 @@ import org.apache.kafka.common.serialization.IntegerDeserializer; import org.apache.kafka.common.serialization.IntegerSerializer; import org.apache.kafka.common.serialization.StringDeserializer; import org.apache.kafka.common.serialization.StringSerializer; +import org.assertj.core.api.AbstractLongAssert; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInstance; @@ -138,4 +149,98 @@ public abstract class KafkaClientBaseTest { } consumer.seekToBeginning(Collections.emptyList()); } + + protected static List sendAttributes( + String messageKey, String messageValue, boolean testHeaders) { + List assertions = + new ArrayList<>( + Arrays.asList( + equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), + equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), + equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), + satisfies( + SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, + AbstractLongAssert::isNotNegative), + satisfies( + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative))); + if (messageKey != null) { + assertions.add(equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, messageKey)); + } + if (messageValue == null) { + assertions.add(equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_TOMBSTONE, true)); + } + if (testHeaders) { + assertions.add( + equalTo( + AttributeKey.stringArrayKey("messaging.header.test_message_header"), + Collections.singletonList("test"))); + } + return assertions; + } + + protected static List receiveAttributes(boolean testHeaders) { + List assertions = + new ArrayList<>( + Arrays.asList( + equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), + equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), + equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), + equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive"))); + // consumer group id is not available in version 0.11 + if (Boolean.getBoolean("testLatestDeps")) { + assertions.add(equalTo(SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, "test")); + } + if (testHeaders) { + assertions.add( + equalTo( + AttributeKey.stringArrayKey("messaging.header.test_message_header"), + Collections.singletonList("test"))); + } + return assertions; + } + + protected static List processAttributes( + String messageKey, String messageValue, boolean testHeaders) { + List assertions = + new ArrayList<>( + Arrays.asList( + equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), + equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, SHARED_TOPIC), + equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), + equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), + satisfies( + SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, + AbstractLongAssert::isNotNegative), + satisfies( + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative), + satisfies( + AttributeKey.longKey("kafka.record.queue_time_ms"), + AbstractLongAssert::isNotNegative))); + // consumer group id is not available in version 0.11 + if (Boolean.getBoolean("testLatestDeps")) { + assertions.add(equalTo(SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, "test")); + } + if (messageKey != null) { + assertions.add(equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, messageKey)); + } + if (messageValue == null) { + assertions.add(equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_TOMBSTONE, true)); + // TODO shouldn't set -1 in this case + assertions.add(equalTo(SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, -1L)); + } else { + assertions.add( + equalTo( + SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, + messageValue.getBytes(StandardCharsets.UTF_8).length)); + } + if (testHeaders) { + assertions.add( + equalTo( + AttributeKey.stringArrayKey("messaging.header.test_message_header"), + Collections.singletonList("test"))); + } + return assertions; + } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetry.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetry.java index cce6bdbf3a..73e85122bd 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetry.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetry.java @@ -8,14 +8,12 @@ package io.opentelemetry.instrumentation.kafkaclients.v2_6; import static java.util.logging.Level.WARNING; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.context.propagation.TextMapGetter; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.context.propagation.TextMapSetter; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.kafka.internal.KafkaConsumerRecordGetter; +import io.opentelemetry.instrumentation.kafka.internal.ConsumerAndRecord; import io.opentelemetry.instrumentation.kafka.internal.KafkaHeadersSetter; import io.opentelemetry.instrumentation.kafka.internal.OpenTelemetryMetricsReporter; import io.opentelemetry.instrumentation.kafka.internal.OpenTelemetrySupplier; @@ -43,20 +41,18 @@ import org.apache.kafka.common.metrics.MetricsReporter; public final class KafkaTelemetry { private static final Logger logger = Logger.getLogger(KafkaTelemetry.class.getName()); - private static final TextMapGetter> GETTER = - KafkaConsumerRecordGetter.INSTANCE; - private static final TextMapSetter SETTER = KafkaHeadersSetter.INSTANCE; private final OpenTelemetry openTelemetry; private final Instrumenter, RecordMetadata> producerInstrumenter; - private final Instrumenter, Void> consumerProcessInstrumenter; + private final Instrumenter>, Void> + consumerProcessInstrumenter; private final boolean producerPropagationEnabled; KafkaTelemetry( OpenTelemetry openTelemetry, Instrumenter, RecordMetadata> producerInstrumenter, - Instrumenter, Void> consumerProcessInstrumenter, + Instrumenter>, Void> consumerProcessInstrumenter, boolean producerPropagationEnabled) { this.openTelemetry = openTelemetry; this.producerInstrumenter = producerInstrumenter; @@ -126,7 +122,7 @@ public final class KafkaTelemetry { // ConsumerRecords poll(long timeout) // ConsumerRecords poll(Duration duration) if ("poll".equals(method.getName()) && result instanceof ConsumerRecords) { - buildAndFinishSpan((ConsumerRecords) result); + buildAndFinishSpan(consumer, (ConsumerRecords) result); } return result; }); @@ -220,18 +216,16 @@ public final class KafkaTelemetry { } } - void buildAndFinishSpan(ConsumerRecords records) { - Context currentContext = Context.current(); + void buildAndFinishSpan(Consumer consumer, ConsumerRecords records) { + Context parentContext = Context.current(); for (ConsumerRecord record : records) { - Context linkedContext = propagator().extract(currentContext, record, GETTER); - Context newContext = currentContext.with(Span.fromContext(linkedContext)); - - if (!consumerProcessInstrumenter.shouldStart(newContext, record)) { + ConsumerAndRecord> request = ConsumerAndRecord.create(consumer, record); + if (!consumerProcessInstrumenter.shouldStart(parentContext, request)) { continue; } - Context current = consumerProcessInstrumenter.start(newContext, record); - consumerProcessInstrumenter.end(current, record, null, null); + Context context = consumerProcessInstrumenter.start(parentContext, request); + consumerProcessInstrumenter.end(context, request, null, null); } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryBuilder.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryBuilder.java index d09c8ecada..5324b36065 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryBuilder.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryBuilder.java @@ -11,6 +11,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessageOperation; +import io.opentelemetry.instrumentation.kafka.internal.ConsumerAndRecord; import io.opentelemetry.instrumentation.kafka.internal.KafkaInstrumenterFactory; import java.util.ArrayList; import java.util.List; @@ -25,8 +26,8 @@ public final class KafkaTelemetryBuilder { private final OpenTelemetry openTelemetry; private final List, RecordMetadata>> producerAttributesExtractors = new ArrayList<>(); - private final List, Void>> consumerAttributesExtractors = - new ArrayList<>(); + private final List>, Void>> + consumerAttributesExtractors = new ArrayList<>(); private List capturedHeaders = emptyList(); private boolean captureExperimentalSpanAttributes = false; private boolean propagationEnabled = true; @@ -44,7 +45,7 @@ public final class KafkaTelemetryBuilder { @CanIgnoreReturnValue public KafkaTelemetryBuilder addConsumerAttributesExtractors( - AttributesExtractor, Void> extractor) { + AttributesExtractor>, Void> extractor) { consumerAttributesExtractors.add(extractor); return this; } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingConsumerInterceptor.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingConsumerInterceptor.java index 33055464a0..83519ca483 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingConsumerInterceptor.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingConsumerInterceptor.java @@ -23,7 +23,7 @@ public class TracingConsumerInterceptor implements ConsumerInterceptor onConsume(ConsumerRecords records) { - telemetry.buildAndFinishSpan(records); + telemetry.buildAndFinishSpan(null, records); return records; } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/ConsumerAndRecord.java b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/ConsumerAndRecord.java new file mode 100644 index 0000000000..3eeaef9b2b --- /dev/null +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/ConsumerAndRecord.java @@ -0,0 +1,73 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.kafka.internal; + +import com.google.auto.value.AutoValue; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import javax.annotation.Nullable; +import org.apache.kafka.clients.consumer.Consumer; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@AutoValue +public abstract class ConsumerAndRecord { + + public static ConsumerAndRecord create(@Nullable Consumer consumer, R record) { + return new AutoValue_ConsumerAndRecord<>(consumer, record); + } + + @Nullable + public abstract Consumer consumer(); + + public abstract R record(); + + private static final MethodHandle GET_GROUP_METADATA; + private static final MethodHandle GET_GROUP_ID; + + static { + MethodHandle getGroupMetadata; + MethodHandle getGroupId; + + try { + Class consumerGroupMetadata = + Class.forName("org.apache.kafka.clients.consumer.ConsumerGroupMetadata"); + + MethodHandles.Lookup lookup = MethodHandles.publicLookup(); + getGroupMetadata = + lookup.findVirtual( + Consumer.class, "groupMetadata", MethodType.methodType(consumerGroupMetadata)); + getGroupId = + lookup.findVirtual(consumerGroupMetadata, "groupId", MethodType.methodType(String.class)); + } catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException ignored) { + getGroupMetadata = null; + getGroupId = null; + } + + GET_GROUP_METADATA = getGroupMetadata; + GET_GROUP_ID = getGroupId; + } + + @Nullable + String consumerGroup() { + if (GET_GROUP_METADATA == null || GET_GROUP_ID == null) { + return null; + } + Consumer consumer = consumer(); + if (consumer == null) { + return null; + } + try { + Object metadata = GET_GROUP_METADATA.invoke(consumer); + return (String) GET_GROUP_ID.invoke(metadata); + } catch (Throwable e) { + return null; + } + } +} diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaBatchProcessAttributesGetter.java b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaBatchProcessAttributesGetter.java deleted file mode 100644 index 6d00909bf3..0000000000 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaBatchProcessAttributesGetter.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.kafka.internal; - -import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesGetter; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; -import javax.annotation.Nullable; -import org.apache.kafka.clients.consumer.ConsumerRecords; -import org.apache.kafka.common.TopicPartition; - -enum KafkaBatchProcessAttributesGetter - implements MessagingAttributesGetter, Void> { - INSTANCE; - - @Override - public String getSystem(ConsumerRecords records) { - return "kafka"; - } - - @Override - public String getDestinationKind(ConsumerRecords records) { - return SemanticAttributes.MessagingDestinationKindValues.TOPIC; - } - - @Nullable - @Override - public String getDestination(ConsumerRecords records) { - Set topics = - records.partitions().stream().map(TopicPartition::topic).collect(Collectors.toSet()); - // only return topic when there's exactly one in the batch - return topics.size() == 1 ? topics.iterator().next() : null; - } - - @Override - public boolean isTemporaryDestination(ConsumerRecords records) { - return false; - } - - @Nullable - @Override - public String getProtocol(ConsumerRecords records) { - return null; - } - - @Nullable - @Override - public String getProtocolVersion(ConsumerRecords records) { - return null; - } - - @Nullable - @Override - public String getUrl(ConsumerRecords records) { - return null; - } - - @Nullable - @Override - public String getConversationId(ConsumerRecords records) { - return null; - } - - @Nullable - @Override - public Long getMessagePayloadSize(ConsumerRecords records) { - return null; - } - - @Nullable - @Override - public Long getMessagePayloadCompressedSize(ConsumerRecords records) { - return null; - } - - @Nullable - @Override - public String getMessageId(ConsumerRecords records, @Nullable Void unused) { - return null; - } - - @Override - public List getMessageHeader(ConsumerRecords records, String name) { - return StreamSupport.stream(records.spliterator(), false) - .flatMap( - consumerRecord -> - StreamSupport.stream(consumerRecord.headers().headers(name).spliterator(), false)) - .map(header -> new String(header.value(), StandardCharsets.UTF_8)) - .collect(Collectors.toList()); - } -} diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaBatchProcessSpanLinksExtractor.java b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaBatchProcessSpanLinksExtractor.java index ce60593501..22b7130e83 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaBatchProcessSpanLinksExtractor.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaBatchProcessSpanLinksExtractor.java @@ -14,9 +14,10 @@ import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; final class KafkaBatchProcessSpanLinksExtractor - implements SpanLinksExtractor> { + implements SpanLinksExtractor>> { - private final SpanLinksExtractor> singleRecordLinkExtractor; + private final SpanLinksExtractor>> + singleRecordLinkExtractor; KafkaBatchProcessSpanLinksExtractor(TextMapPropagator propagator) { this.singleRecordLinkExtractor = @@ -25,12 +26,17 @@ final class KafkaBatchProcessSpanLinksExtractor @Override public void extract( - SpanLinksBuilder spanLinks, Context parentContext, ConsumerRecords records) { + SpanLinksBuilder spanLinks, + Context parentContext, + ConsumerAndRecord> consumerAndRecords) { - for (ConsumerRecord record : records) { + for (ConsumerRecord record : consumerAndRecords.record()) { // explicitly passing root to avoid situation where context propagation is turned off and the // parent (CONSUMER receive) span is linked - singleRecordLinkExtractor.extract(spanLinks, Context.root(), record); + singleRecordLinkExtractor.extract( + spanLinks, + Context.root(), + ConsumerAndRecord.create(consumerAndRecords.consumer(), record)); } } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaConsumerAdditionalAttributesExtractor.java b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaConsumerAdditionalAttributesExtractor.java deleted file mode 100644 index cdaf91deb6..0000000000 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaConsumerAdditionalAttributesExtractor.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.kafka.internal; - -import static io.opentelemetry.api.common.AttributeKey.longKey; - -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import javax.annotation.Nullable; -import org.apache.kafka.clients.consumer.ConsumerRecord; - -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -public final class KafkaConsumerAdditionalAttributesExtractor - implements AttributesExtractor, Void> { - - // TODO: remove this constant when this attribute appears in SemanticAttributes - private static final AttributeKey MESSAGING_KAFKA_MESSAGE_OFFSET = - longKey("messaging.kafka.message.offset"); - - @Override - public void onStart( - AttributesBuilder attributes, Context parentContext, ConsumerRecord consumerRecord) { - attributes.put( - SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, (long) consumerRecord.partition()); - attributes.put(MESSAGING_KAFKA_MESSAGE_OFFSET, consumerRecord.offset()); - if (consumerRecord.value() == null) { - attributes.put(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_TOMBSTONE, true); - } - } - - @Override - public void onEnd( - AttributesBuilder attributes, - Context context, - ConsumerRecord consumerRecord, - @Nullable Void unused, - @Nullable Throwable error) {} -} diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaConsumerAttributesExtractor.java b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaConsumerAttributesExtractor.java new file mode 100644 index 0000000000..4146e05777 --- /dev/null +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaConsumerAttributesExtractor.java @@ -0,0 +1,57 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.kafka.internal; + +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.nio.ByteBuffer; +import javax.annotation.Nullable; +import org.apache.kafka.clients.consumer.ConsumerRecord; + +final class KafkaConsumerAttributesExtractor + implements AttributesExtractor>, Void> { + + @Override + public void onStart( + AttributesBuilder attributes, + Context parentContext, + ConsumerAndRecord> consumerAndRecord) { + + ConsumerRecord record = consumerAndRecord.record(); + + attributes.put(SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, (long) record.partition()); + attributes.put(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, record.offset()); + + Object key = record.key(); + if (key != null && canSerialize(key.getClass())) { + attributes.put(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, key.toString()); + } + if (record.value() == null) { + attributes.put(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_TOMBSTONE, true); + } + + String consumerGroup = consumerAndRecord.consumerGroup(); + if (consumerGroup != null) { + attributes.put(SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, consumerGroup); + } + } + + private static boolean canSerialize(Class keyClass) { + // we make a simple assumption here that we can serialize keys by simply calling toString() + // and that does not work for byte[] or ByteBuffer + return !(keyClass.isArray() || keyClass == ByteBuffer.class); + } + + @Override + public void onEnd( + AttributesBuilder attributes, + Context context, + ConsumerAndRecord> consumerAndRecord, + @Nullable Void unused, + @Nullable Throwable error) {} +} diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaConsumerAttributesGetter.java b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaConsumerAttributesGetter.java index 4ce929b5b3..2bef3a4c44 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaConsumerAttributesGetter.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaConsumerAttributesGetter.java @@ -14,78 +14,78 @@ import java.util.stream.StreamSupport; import javax.annotation.Nullable; import org.apache.kafka.clients.consumer.ConsumerRecord; -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -public enum KafkaConsumerAttributesGetter - implements MessagingAttributesGetter, Void> { +enum KafkaConsumerAttributesGetter + implements MessagingAttributesGetter>, Void> { INSTANCE; @Override - public String getSystem(ConsumerRecord consumerRecord) { + public String getSystem(ConsumerAndRecord> consumerAndRecord) { return "kafka"; } @Override - public String getDestinationKind(ConsumerRecord consumerRecord) { + public String getDestinationKind(ConsumerAndRecord> consumerAndRecord) { return SemanticAttributes.MessagingDestinationKindValues.TOPIC; } @Override - public String getDestination(ConsumerRecord consumerRecord) { - return consumerRecord.topic(); + public String getDestination(ConsumerAndRecord> consumerAndRecord) { + return consumerAndRecord.record().topic(); } @Override - public boolean isTemporaryDestination(ConsumerRecord consumerRecord) { + public boolean isTemporaryDestination(ConsumerAndRecord> consumerAndRecord) { return false; } @Override @Nullable - public String getProtocol(ConsumerRecord consumerRecord) { + public String getProtocol(ConsumerAndRecord> consumerAndRecord) { return null; } @Override @Nullable - public String getProtocolVersion(ConsumerRecord consumerRecord) { + public String getProtocolVersion(ConsumerAndRecord> consumerAndRecord) { return null; } @Override @Nullable - public String getUrl(ConsumerRecord consumerRecord) { + public String getUrl(ConsumerAndRecord> consumerAndRecord) { return null; } @Override @Nullable - public String getConversationId(ConsumerRecord consumerRecord) { + public String getConversationId(ConsumerAndRecord> consumerAndRecord) { return null; } @Override - public Long getMessagePayloadSize(ConsumerRecord consumerRecord) { - return (long) consumerRecord.serializedValueSize(); + public Long getMessagePayloadSize(ConsumerAndRecord> consumerAndRecord) { + return (long) consumerAndRecord.record().serializedValueSize(); } @Override @Nullable - public Long getMessagePayloadCompressedSize(ConsumerRecord consumerRecord) { + public Long getMessagePayloadCompressedSize( + ConsumerAndRecord> consumerAndRecord) { return null; } @Override @Nullable - public String getMessageId(ConsumerRecord consumerRecord, @Nullable Void unused) { + public String getMessageId( + ConsumerAndRecord> consumerAndRecord, @Nullable Void unused) { return null; } @Override - public List getMessageHeader(ConsumerRecord consumerRecord, String name) { - return StreamSupport.stream(consumerRecord.headers().headers(name).spliterator(), false) + public List getMessageHeader( + ConsumerAndRecord> consumerAndRecord, String name) { + return StreamSupport.stream( + consumerAndRecord.record().headers().headers(name).spliterator(), false) .map(header -> new String(header.value(), StandardCharsets.UTF_8)) .collect(Collectors.toList()); } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaConsumerExperimentalAttributesExtractor.java b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaConsumerExperimentalAttributesExtractor.java index 90ca67d1b0..b87b07a42e 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaConsumerExperimentalAttributesExtractor.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaConsumerExperimentalAttributesExtractor.java @@ -15,23 +15,21 @@ import javax.annotation.Nullable; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.common.record.TimestampType; -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -public final class KafkaConsumerExperimentalAttributesExtractor - implements AttributesExtractor, Void> { +final class KafkaConsumerExperimentalAttributesExtractor + implements AttributesExtractor>, Void> { private static final AttributeKey KAFKA_RECORD_QUEUE_TIME_MS = longKey("kafka.record.queue_time_ms"); @Override public void onStart( - AttributesBuilder attributes, Context parentContext, ConsumerRecord consumerRecord) { + AttributesBuilder attributes, + Context parentContext, + ConsumerAndRecord> consumerAndRecord) { // don't record a duration if the message was sent from an old Kafka client - if (consumerRecord.timestampType() != TimestampType.NO_TIMESTAMP_TYPE) { - long produceTime = consumerRecord.timestamp(); + if (consumerAndRecord.record().timestampType() != TimestampType.NO_TIMESTAMP_TYPE) { + long produceTime = consumerAndRecord.record().timestamp(); // this attribute shows how much time elapsed between the producer and the consumer of this // message, which can be helpful for identifying queue bottlenecks attributes.put( @@ -43,7 +41,7 @@ public final class KafkaConsumerExperimentalAttributesExtractor public void onEnd( AttributesBuilder attributes, Context context, - ConsumerRecord consumerRecord, + ConsumerAndRecord> consumerAndRecord, @Nullable Void unused, @Nullable Throwable error) {} } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaConsumerRecordGetter.java b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaConsumerRecordGetter.java index e2e6b6e370..98ddecf483 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaConsumerRecordGetter.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaConsumerRecordGetter.java @@ -13,24 +13,20 @@ import javax.annotation.Nullable; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.common.header.Header; -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -public enum KafkaConsumerRecordGetter implements TextMapGetter> { +enum KafkaConsumerRecordGetter implements TextMapGetter>> { INSTANCE; @Override - public Iterable keys(ConsumerRecord carrier) { - return StreamSupport.stream(carrier.headers().spliterator(), false) + public Iterable keys(ConsumerAndRecord> carrier) { + return StreamSupport.stream(carrier.record().headers().spliterator(), false) .map(Header::key) .collect(Collectors.toList()); } @Nullable @Override - public String get(@Nullable ConsumerRecord carrier, String key) { - Header header = carrier.headers().lastHeader(key); + public String get(@Nullable ConsumerAndRecord> carrier, String key) { + Header header = carrier.record().headers().lastHeader(key); if (header == null) { return null; } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaHeadersGetter.java b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaHeadersGetter.java deleted file mode 100644 index 2967de292b..0000000000 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaHeadersGetter.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.kafka.internal; - -import io.opentelemetry.context.propagation.TextMapGetter; -import java.nio.charset.StandardCharsets; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; -import javax.annotation.Nullable; -import org.apache.kafka.common.header.Header; -import org.apache.kafka.common.header.Headers; - -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -public final class KafkaHeadersGetter implements TextMapGetter { - @Override - public Iterable keys(Headers carrier) { - return StreamSupport.stream(carrier.spliterator(), false) - .map(Header::key) - .collect(Collectors.toList()); - } - - @Nullable - @Override - public String get(@Nullable Headers carrier, String key) { - Header header = carrier.lastHeader(key); - if (header == null) { - return null; - } - byte[] value = header.value(); - if (value == null) { - return null; - } - return new String(value, StandardCharsets.UTF_8); - } -} diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaInstrumenterFactory.java b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaInstrumenterFactory.java index 773ab8c2f0..95391cd034 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaInstrumenterFactory.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaInstrumenterFactory.java @@ -98,44 +98,48 @@ public final class KafkaInstrumenterFactory { .addAttributesExtractor( buildMessagingAttributesExtractor(getter, operation, capturedHeaders)) .addAttributesExtractors(extractors) - .addAttributesExtractor(new KafkaProducerAdditionalAttributesExtractor()) + .addAttributesExtractor(new KafkaProducerAttributesExtractor()) .setErrorCauseExtractor(errorCauseExtractor) .buildInstrumenter(SpanKindExtractor.alwaysProducer()); } - public Instrumenter, Void> createConsumerReceiveInstrumenter() { + public Instrumenter>, Void> + createConsumerReceiveInstrumenter() { KafkaReceiveAttributesGetter getter = KafkaReceiveAttributesGetter.INSTANCE; MessageOperation operation = MessageOperation.RECEIVE; - return Instrumenter., Void>builder( + return Instrumenter.>, Void>builder( openTelemetry, instrumentationName, MessagingSpanNameExtractor.create(getter, operation)) .addAttributesExtractor( buildMessagingAttributesExtractor(getter, operation, capturedHeaders)) + .addAttributesExtractor(KafkaReceiveAttributesExtractor.INSTANCE) .setErrorCauseExtractor(errorCauseExtractor) .setEnabled(messagingReceiveInstrumentationEnabled) .buildInstrumenter(SpanKindExtractor.alwaysConsumer()); } - public Instrumenter, Void> createConsumerProcessInstrumenter() { + public Instrumenter>, Void> + createConsumerProcessInstrumenter() { return createConsumerOperationInstrumenter(MessageOperation.PROCESS, Collections.emptyList()); } - public Instrumenter, Void> createConsumerOperationInstrumenter( - MessageOperation operation, - Iterable, Void>> extractors) { + public Instrumenter>, Void> + createConsumerOperationInstrumenter( + MessageOperation operation, + Iterable>, Void>> extractors) { KafkaConsumerAttributesGetter getter = KafkaConsumerAttributesGetter.INSTANCE; - InstrumenterBuilder, Void> builder = - Instrumenter., Void>builder( + InstrumenterBuilder>, Void> builder = + Instrumenter.>, Void>builder( openTelemetry, instrumentationName, MessagingSpanNameExtractor.create(getter, operation)) .addAttributesExtractor( buildMessagingAttributesExtractor(getter, operation, capturedHeaders)) - .addAttributesExtractor(new KafkaConsumerAdditionalAttributesExtractor()) + .addAttributesExtractor(new KafkaConsumerAttributesExtractor()) .addAttributesExtractors(extractors) .setErrorCauseExtractor(errorCauseExtractor); if (captureExperimentalSpanAttributes) { @@ -144,7 +148,7 @@ public final class KafkaInstrumenterFactory { if (messagingReceiveInstrumentationEnabled) { builder.addSpanLinksExtractor( - new PropagatorBasedSpanLinksExtractor>( + new PropagatorBasedSpanLinksExtractor<>( openTelemetry.getPropagators().getTextMapPropagator(), KafkaConsumerRecordGetter.INSTANCE)); return builder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); @@ -153,16 +157,18 @@ public final class KafkaInstrumenterFactory { } } - public Instrumenter, Void> createBatchProcessInstrumenter() { - KafkaBatchProcessAttributesGetter getter = KafkaBatchProcessAttributesGetter.INSTANCE; + public Instrumenter>, Void> + createBatchProcessInstrumenter() { + KafkaReceiveAttributesGetter getter = KafkaReceiveAttributesGetter.INSTANCE; MessageOperation operation = MessageOperation.PROCESS; - return Instrumenter., Void>builder( + return Instrumenter.>, Void>builder( openTelemetry, instrumentationName, MessagingSpanNameExtractor.create(getter, operation)) .addAttributesExtractor( buildMessagingAttributesExtractor(getter, operation, capturedHeaders)) + .addAttributesExtractor(KafkaReceiveAttributesExtractor.INSTANCE) .addSpanLinksExtractor( new KafkaBatchProcessSpanLinksExtractor( openTelemetry.getPropagators().getTextMapPropagator())) diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaProducerAdditionalAttributesExtractor.java b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaProducerAttributesExtractor.java similarity index 63% rename from instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaProducerAdditionalAttributesExtractor.java rename to instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaProducerAttributesExtractor.java index 7f4cc17c2b..7b75ce94a8 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaProducerAdditionalAttributesExtractor.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaProducerAttributesExtractor.java @@ -5,36 +5,37 @@ package io.opentelemetry.instrumentation.kafka.internal; -import static io.opentelemetry.api.common.AttributeKey.longKey; - -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.nio.ByteBuffer; import javax.annotation.Nullable; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.RecordMetadata; -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -final class KafkaProducerAdditionalAttributesExtractor +final class KafkaProducerAttributesExtractor implements AttributesExtractor, RecordMetadata> { - // TODO: remove this constant when this attribute appears in SemanticAttributes - private static final AttributeKey MESSAGING_KAFKA_MESSAGE_OFFSET = - longKey("messaging.kafka.message.offset"); - @Override public void onStart( - AttributesBuilder attributes, Context parentContext, ProducerRecord producerRecord) { - if (producerRecord.value() == null) { + AttributesBuilder attributes, Context parentContext, ProducerRecord record) { + + Object key = record.key(); + if (key != null && canSerialize(key.getClass())) { + attributes.put(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, key.toString()); + } + if (record.value() == null) { attributes.put(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_TOMBSTONE, true); } } + private static boolean canSerialize(Class keyClass) { + // we make a simple assumption here that we can serialize keys by simply calling toString() + // and that does not work for byte[] or ByteBuffer + return !(keyClass.isArray() || keyClass == ByteBuffer.class); + } + @Override public void onEnd( AttributesBuilder attributes, @@ -46,7 +47,7 @@ final class KafkaProducerAdditionalAttributesExtractor if (recordMetadata != null) { attributes.put( SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, recordMetadata.partition()); - attributes.put(MESSAGING_KAFKA_MESSAGE_OFFSET, recordMetadata.offset()); + attributes.put(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, recordMetadata.offset()); } } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaReceiveAttributesExtractor.java b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaReceiveAttributesExtractor.java new file mode 100644 index 0000000000..baa9fb33f7 --- /dev/null +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaReceiveAttributesExtractor.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.kafka.internal; + +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import javax.annotation.Nullable; +import org.apache.kafka.clients.consumer.ConsumerRecords; + +enum KafkaReceiveAttributesExtractor + implements AttributesExtractor>, Void> { + INSTANCE; + + @Override + public void onStart( + AttributesBuilder attributes, + Context parentContext, + ConsumerAndRecord> consumerAndRecords) { + + String consumerGroup = consumerAndRecords.consumerGroup(); + if (consumerGroup != null) { + attributes.put(SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, consumerGroup); + } + } + + @Override + public void onEnd( + AttributesBuilder attributes, + Context context, + ConsumerAndRecord> request, + @Nullable Void unused, + @Nullable Throwable error) {} +} diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaReceiveAttributesGetter.java b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaReceiveAttributesGetter.java index 579df437a5..6a4d73bff1 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaReceiveAttributesGetter.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaReceiveAttributesGetter.java @@ -16,29 +16,25 @@ import javax.annotation.Nullable; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.common.TopicPartition; -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -public enum KafkaReceiveAttributesGetter - implements MessagingAttributesGetter, Void> { +enum KafkaReceiveAttributesGetter + implements MessagingAttributesGetter>, Void> { INSTANCE; @Override - public String getSystem(ConsumerRecords consumerRecords) { + public String getSystem(ConsumerAndRecord> consumerAndRecords) { return "kafka"; } @Override - public String getDestinationKind(ConsumerRecords consumerRecords) { + public String getDestinationKind(ConsumerAndRecord> consumerAndRecords) { return SemanticAttributes.MessagingDestinationKindValues.TOPIC; } @Override @Nullable - public String getDestination(ConsumerRecords consumerRecords) { + public String getDestination(ConsumerAndRecord> consumerAndRecords) { Set topics = - consumerRecords.partitions().stream() + consumerAndRecords.record().partitions().stream() .map(TopicPartition::topic) .collect(Collectors.toSet()); // only return topic when there's exactly one in the batch @@ -46,55 +42,59 @@ public enum KafkaReceiveAttributesGetter } @Override - public boolean isTemporaryDestination(ConsumerRecords consumerRecords) { + public boolean isTemporaryDestination( + ConsumerAndRecord> consumerAndRecords) { return false; } @Override @Nullable - public String getProtocol(ConsumerRecords consumerRecords) { + public String getProtocol(ConsumerAndRecord> consumerAndRecords) { return null; } @Override @Nullable - public String getProtocolVersion(ConsumerRecords consumerRecords) { + public String getProtocolVersion(ConsumerAndRecord> consumerAndRecords) { return null; } @Override @Nullable - public String getUrl(ConsumerRecords consumerRecords) { + public String getUrl(ConsumerAndRecord> consumerAndRecords) { return null; } @Override @Nullable - public String getConversationId(ConsumerRecords consumerRecords) { + public String getConversationId(ConsumerAndRecord> consumerAndRecords) { return null; } @Override @Nullable - public Long getMessagePayloadSize(ConsumerRecords consumerRecords) { + public Long getMessagePayloadSize(ConsumerAndRecord> consumerAndRecords) { return null; } @Override @Nullable - public Long getMessagePayloadCompressedSize(ConsumerRecords consumerRecords) { + public Long getMessagePayloadCompressedSize( + ConsumerAndRecord> consumerAndRecords) { return null; } @Override @Nullable - public String getMessageId(ConsumerRecords consumerRecords, @Nullable Void unused) { + public String getMessageId( + ConsumerAndRecord> consumerAndRecords, @Nullable Void unused) { return null; } @Override - public List getMessageHeader(ConsumerRecords records, String name) { - return StreamSupport.stream(records.spliterator(), false) + public List getMessageHeader( + ConsumerAndRecord> consumerAndRecords, String name) { + return StreamSupport.stream(consumerAndRecords.record().spliterator(), false) .flatMap( consumerRecord -> StreamSupport.stream(consumerRecord.headers().headers(name).spliterator(), false)) diff --git a/instrumentation/kafka/kafka-streams-0.11/javaagent/build.gradle.kts b/instrumentation/kafka/kafka-streams-0.11/javaagent/build.gradle.kts index cb7bbe948b..bac74fe62d 100644 --- a/instrumentation/kafka/kafka-streams-0.11/javaagent/build.gradle.kts +++ b/instrumentation/kafka/kafka-streams-0.11/javaagent/build.gradle.kts @@ -27,6 +27,8 @@ tasks { withType().configureEach { usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) + systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) + // TODO run tests both with and without experimental span attributes jvmArgs("-Dotel.instrumentation.kafka.experimental-span-attributes=true") } diff --git a/instrumentation/kafka/kafka-streams-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkastreams/KafkaStreamsSingletons.java b/instrumentation/kafka/kafka-streams-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkastreams/KafkaStreamsSingletons.java index ffd91f5182..98f05d4261 100644 --- a/instrumentation/kafka/kafka-streams-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkastreams/KafkaStreamsSingletons.java +++ b/instrumentation/kafka/kafka-streams-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkastreams/KafkaStreamsSingletons.java @@ -7,6 +7,7 @@ package io.opentelemetry.javaagent.instrumentation.kafkastreams; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.kafka.internal.ConsumerAndRecord; import io.opentelemetry.instrumentation.kafka.internal.KafkaInstrumenterFactory; import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig; import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig; @@ -16,7 +17,7 @@ public final class KafkaStreamsSingletons { private static final String INSTRUMENTATION_NAME = "io.opentelemetry.kafka-streams-0.11"; - private static final Instrumenter, Void> INSTRUMENTER = + private static final Instrumenter>, Void> INSTRUMENTER = new KafkaInstrumenterFactory(GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME) .setCapturedHeaders(ExperimentalConfig.get().getMessagingHeaders()) .setCaptureExperimentalSpanAttributes( @@ -26,7 +27,7 @@ public final class KafkaStreamsSingletons { ExperimentalConfig.get().messagingReceiveInstrumentationEnabled()) .createConsumerProcessInstrumenter(); - public static Instrumenter, Void> instrumenter() { + public static Instrumenter>, Void> instrumenter() { return INSTRUMENTER; } diff --git a/instrumentation/kafka/kafka-streams-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkastreams/PartitionGroupInstrumentation.java b/instrumentation/kafka/kafka-streams-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkastreams/PartitionGroupInstrumentation.java index 8ce270e6ab..4c1f9c3dab 100644 --- a/instrumentation/kafka/kafka-streams-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkastreams/PartitionGroupInstrumentation.java +++ b/instrumentation/kafka/kafka-streams-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkastreams/PartitionGroupInstrumentation.java @@ -15,6 +15,7 @@ import static net.bytebuddy.matcher.ElementMatchers.returns; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.instrumentation.kafka.internal.ConsumerAndRecord; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import net.bytebuddy.asm.Advice; @@ -61,11 +62,13 @@ public class PartitionGroupInstrumentation implements TypeInstrumentation { // use the receive CONSUMER span as parent if it's available Context parentContext = receiveContext != null ? receiveContext : currentContext(); + ConsumerAndRecord> request = + ConsumerAndRecord.create(null, record.value); - if (!instrumenter().shouldStart(parentContext, record.value)) { + if (!instrumenter().shouldStart(parentContext, request)) { return; } - Context context = instrumenter().start(parentContext, record.value); + Context context = instrumenter().start(parentContext, request); holder.set(record.value, context, context.makeCurrent()); } } diff --git a/instrumentation/kafka/kafka-streams-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkastreams/StreamTaskInstrumentation.java b/instrumentation/kafka/kafka-streams-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkastreams/StreamTaskInstrumentation.java index fdc5f7c7b8..eaca099fd2 100644 --- a/instrumentation/kafka/kafka-streams-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkastreams/StreamTaskInstrumentation.java +++ b/instrumentation/kafka/kafka-streams-0.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kafkastreams/StreamTaskInstrumentation.java @@ -11,6 +11,7 @@ import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.named; import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.kafka.internal.ConsumerAndRecord; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import net.bytebuddy.asm.Advice; @@ -51,7 +52,8 @@ public class StreamTaskInstrumentation implements TypeInstrumentation { Context context = holder.getContext(); if (context != null) { holder.closeScope(); - instrumenter().end(context, holder.getRecord(), null, throwable); + instrumenter() + .end(context, ConsumerAndRecord.create(null, holder.getRecord()), null, throwable); } } } diff --git a/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsDefaultTest.groovy b/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsDefaultTest.groovy index 9150e21be0..549f64a7ad 100644 --- a/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsDefaultTest.groovy +++ b/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsDefaultTest.groovy @@ -40,7 +40,7 @@ class KafkaStreamsDefaultTest extends KafkaStreamsBaseTest { } catch (ClassNotFoundException | NoClassDefFoundError e) { builder = Class.forName("org.apache.kafka.streams.StreamsBuilder").newInstance() } - KStream textLines = builder.stream(STREAM_PENDING) + KStream textLines = builder.stream(STREAM_PENDING) def values = textLines .mapValues(new ValueMapper() { @Override @@ -53,11 +53,11 @@ class KafkaStreamsDefaultTest extends KafkaStreamsBaseTest { KafkaStreams streams try { // Different api for test and latestDepTest. - values.to(Serdes.String(), Serdes.String(), STREAM_PROCESSED) + values.to(Serdes.Integer(), Serdes.String(), STREAM_PROCESSED) streams = new KafkaStreams(builder, config) } catch (MissingMethodException e) { def producer = Class.forName("org.apache.kafka.streams.kstream.Produced") - .with(Serdes.String(), Serdes.String()) + .with(Serdes.Integer(), Serdes.String()) values.to(STREAM_PROCESSED, producer) streams = new KafkaStreams(builder.build(), config) } @@ -65,7 +65,7 @@ class KafkaStreamsDefaultTest extends KafkaStreamsBaseTest { when: String greeting = "TESTING TESTING 123!" - KafkaStreamsBaseTest.producer.send(new ProducerRecord<>(STREAM_PENDING, greeting)) + KafkaStreamsBaseTest.producer.send(new ProducerRecord<>(STREAM_PENDING, 10, greeting)) then: awaitUntilConsumerIsReady() @@ -74,8 +74,8 @@ class KafkaStreamsDefaultTest extends KafkaStreamsBaseTest { for (record in records) { Span.current().setAttribute("testing", 123) + assert record.key() == 10 assert record.value() == greeting.toLowerCase() - assert record.key() == null if (receivedHeaders == null) { receivedHeaders = record.headers() @@ -101,7 +101,8 @@ class KafkaStreamsDefaultTest extends KafkaStreamsBaseTest { "$SemanticAttributes.MESSAGING_DESTINATION_NAME" STREAM_PENDING "$SemanticAttributes.MESSAGING_DESTINATION_KIND" "topic" "$SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION" { it >= 0 } - "messaging.kafka.message.offset" 0 + "$SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET" 0 + "$SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY" "10" } } @@ -118,6 +119,9 @@ class KafkaStreamsDefaultTest extends KafkaStreamsBaseTest { "$SemanticAttributes.MESSAGING_DESTINATION_NAME" STREAM_PENDING "$SemanticAttributes.MESSAGING_DESTINATION_KIND" "topic" "$SemanticAttributes.MESSAGING_OPERATION" "receive" + if (Boolean.getBoolean("testLatestDeps")) { + "$SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP" "test-application" + } } } // kafka-stream CONSUMER @@ -133,7 +137,8 @@ class KafkaStreamsDefaultTest extends KafkaStreamsBaseTest { "$SemanticAttributes.MESSAGING_OPERATION" "process" "$SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES" Long "$SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION" { it >= 0 } - "messaging.kafka.message.offset" 0 + "$SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET" 0 + "$SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY" "10" "kafka.record.queue_time_ms" { it >= 0 } "asdf" "testing" } @@ -148,7 +153,7 @@ class KafkaStreamsDefaultTest extends KafkaStreamsBaseTest { "$SemanticAttributes.MESSAGING_DESTINATION_NAME" STREAM_PROCESSED "$SemanticAttributes.MESSAGING_DESTINATION_KIND" "topic" "$SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION" { it >= 0 } - "messaging.kafka.message.offset" 0 + "$SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET" 0 } } @@ -165,6 +170,9 @@ class KafkaStreamsDefaultTest extends KafkaStreamsBaseTest { "$SemanticAttributes.MESSAGING_DESTINATION_NAME" STREAM_PROCESSED "$SemanticAttributes.MESSAGING_DESTINATION_KIND" "topic" "$SemanticAttributes.MESSAGING_OPERATION" "receive" + if (Boolean.getBoolean("testLatestDeps")) { + "$SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP" "test" + } } } // kafka-clients CONSUMER process @@ -180,7 +188,11 @@ class KafkaStreamsDefaultTest extends KafkaStreamsBaseTest { "$SemanticAttributes.MESSAGING_OPERATION" "process" "$SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES" Long "$SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION" { it >= 0 } - "messaging.kafka.message.offset" 0 + "$SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET" 0 + "$SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY" "10" + if (Boolean.getBoolean("testLatestDeps")) { + "$SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP" "test" + } "kafka.record.queue_time_ms" { it >= 0 } "testing" 123 } diff --git a/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsSuppressReceiveSpansTest.groovy b/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsSuppressReceiveSpansTest.groovy index bc5d9c7ccc..6c017fae97 100644 --- a/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsSuppressReceiveSpansTest.groovy +++ b/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsSuppressReceiveSpansTest.groovy @@ -40,7 +40,7 @@ class KafkaStreamsSuppressReceiveSpansTest extends KafkaStreamsBaseTest { } catch (ClassNotFoundException | NoClassDefFoundError e) { builder = Class.forName("org.apache.kafka.streams.StreamsBuilder").newInstance() } - KStream textLines = builder.stream(STREAM_PENDING) + KStream textLines = builder.stream(STREAM_PENDING) def values = textLines .mapValues(new ValueMapper() { @Override @@ -53,11 +53,11 @@ class KafkaStreamsSuppressReceiveSpansTest extends KafkaStreamsBaseTest { KafkaStreams streams try { // Different api for test and latestDepTest. - values.to(Serdes.String(), Serdes.String(), STREAM_PROCESSED) + values.to(Serdes.Integer(), Serdes.String(), STREAM_PROCESSED) streams = new KafkaStreams(builder, config) } catch (MissingMethodException e) { def producer = Class.forName("org.apache.kafka.streams.kstream.Produced") - .with(Serdes.String(), Serdes.String()) + .with(Serdes.Integer(), Serdes.String()) values.to(STREAM_PROCESSED, producer) streams = new KafkaStreams(builder.build(), config) } @@ -65,7 +65,7 @@ class KafkaStreamsSuppressReceiveSpansTest extends KafkaStreamsBaseTest { when: String greeting = "TESTING TESTING 123!" - KafkaStreamsBaseTest.producer.send(new ProducerRecord<>(STREAM_PENDING, greeting)) + KafkaStreamsBaseTest.producer.send(new ProducerRecord<>(STREAM_PENDING, 10, greeting)) then: // check that the message was received @@ -74,8 +74,8 @@ class KafkaStreamsSuppressReceiveSpansTest extends KafkaStreamsBaseTest { for (record in records) { Span.current().setAttribute("testing", 123) + assert record.key() == 10 assert record.value() == greeting.toLowerCase() - assert record.key() == null if (receivedHeaders == null) { receivedHeaders = record.headers() @@ -96,7 +96,8 @@ class KafkaStreamsSuppressReceiveSpansTest extends KafkaStreamsBaseTest { "$SemanticAttributes.MESSAGING_DESTINATION_NAME" STREAM_PENDING "$SemanticAttributes.MESSAGING_DESTINATION_KIND" "topic" "$SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION" { it >= 0 } - "messaging.kafka.message.offset" 0 + "$SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET" 0 + "$SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY" "10" } } // kafka-stream CONSUMER @@ -111,7 +112,8 @@ class KafkaStreamsSuppressReceiveSpansTest extends KafkaStreamsBaseTest { "$SemanticAttributes.MESSAGING_OPERATION" "process" "$SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES" Long "$SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION" { it >= 0 } - "messaging.kafka.message.offset" 0 + "$SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET" 0 + "$SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY" "10" "kafka.record.queue_time_ms" { it >= 0 } "asdf" "testing" } @@ -129,7 +131,7 @@ class KafkaStreamsSuppressReceiveSpansTest extends KafkaStreamsBaseTest { "$SemanticAttributes.MESSAGING_DESTINATION_NAME" STREAM_PROCESSED "$SemanticAttributes.MESSAGING_DESTINATION_KIND" "topic" "$SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION" { it >= 0 } - "messaging.kafka.message.offset" 0 + "$SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET" 0 } } // kafka-clients CONSUMER process @@ -144,7 +146,11 @@ class KafkaStreamsSuppressReceiveSpansTest extends KafkaStreamsBaseTest { "$SemanticAttributes.MESSAGING_OPERATION" "process" "$SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES" Long "$SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION" { it >= 0 } - "messaging.kafka.message.offset" 0 + "$SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET" 0 + "$SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY" "10" + if (Boolean.getBoolean("testLatestDeps")) { + "$SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP" "test" + } "kafka.record.queue_time_ms" { it >= 0 } "testing" 123 } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/kafka/KafkaIntegrationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/kafka/KafkaIntegrationTest.java index 811ce78fa9..0b1817e35a 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/kafka/KafkaIntegrationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/kafka/KafkaIntegrationTest.java @@ -5,7 +5,6 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.kafka; -import static io.opentelemetry.api.common.AttributeKey.longKey; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; @@ -47,7 +46,7 @@ class KafkaIntegrationTest { @BeforeAll static void setUpKafka() { kafka = - new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:5.4.3")) + new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:6.1.9")) .waitingFor(Wait.forLogMessage(".*started \\(kafka.server.KafkaServer\\).*", 1)) .withStartupTimeout(Duration.ofMinutes(1)); kafka.start(); @@ -112,8 +111,9 @@ class KafkaIntegrationTest { SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, AbstractLongAssert::isNotNegative), satisfies( - longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative), + equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10")), span -> span.hasName("testTopic process") .hasKind(SpanKind.CONSUMER) @@ -130,8 +130,11 @@ class KafkaIntegrationTest { SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, AbstractLongAssert::isNotNegative), satisfies( - longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative), + equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10"), + equalTo( + SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, "testListener")), span -> span.hasName("consumer").hasParent(trace.getSpan(2)))); } diff --git a/instrumentation/spring/spring-kafka-2.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/kafka/v2_7/SpringKafkaTest.java b/instrumentation/spring/spring-kafka-2.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/kafka/v2_7/SpringKafkaTest.java index 1dc83c991f..5d93215f23 100644 --- a/instrumentation/spring/spring-kafka-2.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/kafka/v2_7/SpringKafkaTest.java +++ b/instrumentation/spring/spring-kafka-2.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/kafka/v2_7/SpringKafkaTest.java @@ -73,8 +73,9 @@ class SpringKafkaTest extends AbstractSpringKafkaTest { SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, AbstractLongAssert::isNotNegative), satisfies( - longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative))); + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative), + equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10"))); producer.set(trace.getSpan(1)); }, @@ -89,7 +90,10 @@ class SpringKafkaTest extends AbstractSpringKafkaTest { equalTo( SemanticAttributes.MESSAGING_DESTINATION_NAME, "testSingleTopic"), equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive")), + equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive"), + equalTo( + SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, + "testSingleListener")), span -> span.hasName("testSingleTopic process") .hasKind(SpanKind.CONSUMER) @@ -108,8 +112,12 @@ class SpringKafkaTest extends AbstractSpringKafkaTest { SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, AbstractLongAssert::isNotNegative), satisfies( - longKey("messaging.kafka.message.offset"), + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative), + equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10"), + equalTo( + SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, + "testSingleListener"), satisfies( longKey("kafka.record.queue_time_ms"), AbstractLongAssert::isNotNegative)), @@ -147,8 +155,9 @@ class SpringKafkaTest extends AbstractSpringKafkaTest { SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, AbstractLongAssert::isNotNegative), satisfies( - longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative))); + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative), + equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10"))); producer.set(trace.getSpan(1)); }, @@ -163,7 +172,10 @@ class SpringKafkaTest extends AbstractSpringKafkaTest { equalTo( SemanticAttributes.MESSAGING_DESTINATION_NAME, "testSingleTopic"), equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive")), + equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive"), + equalTo( + SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, + "testSingleListener")), span -> span.hasName("testSingleTopic process") .hasKind(SpanKind.CONSUMER) @@ -184,8 +196,12 @@ class SpringKafkaTest extends AbstractSpringKafkaTest { SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, AbstractLongAssert::isNotNegative), satisfies( - longKey("messaging.kafka.message.offset"), + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, AbstractLongAssert::isNotNegative), + equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10"), + equalTo( + SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, + "testSingleListener"), satisfies( longKey("kafka.record.queue_time_ms"), AbstractLongAssert::isNotNegative)), @@ -205,7 +221,7 @@ class SpringKafkaTest extends AbstractSpringKafkaTest { testing.waitAndAssertSortedTraces( orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CONSUMER), trace -> { - trace.hasSpansSatisfyingExactly( + trace.hasSpansSatisfyingExactlyInAnyOrder( span -> span.hasName("producer"), span -> span.hasName("testBatchTopic send") @@ -219,8 +235,9 @@ class SpringKafkaTest extends AbstractSpringKafkaTest { SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, AbstractLongAssert::isNotNegative), satisfies( - longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative), + equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10")), span -> span.hasName("testBatchTopic send") .hasKind(SpanKind.PRODUCER) @@ -233,8 +250,9 @@ class SpringKafkaTest extends AbstractSpringKafkaTest { SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, AbstractLongAssert::isNotNegative), satisfies( - longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative))); + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative), + equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "20"))); producer1.set(trace.getSpan(1)); producer2.set(trace.getSpan(2)); @@ -250,7 +268,10 @@ class SpringKafkaTest extends AbstractSpringKafkaTest { equalTo( SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive")), + equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive"), + equalTo( + SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, + "testBatchListener")), span -> span.hasName("testBatchTopic process") .hasKind(SpanKind.CONSUMER) @@ -263,7 +284,10 @@ class SpringKafkaTest extends AbstractSpringKafkaTest { equalTo( SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process")), + equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), + equalTo( + SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, + "testBatchListener")), span -> span.hasName("consumer").hasParent(trace.getSpan(1)))); } @@ -298,8 +322,9 @@ class SpringKafkaTest extends AbstractSpringKafkaTest { SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, AbstractLongAssert::isNotNegative), satisfies( - longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative))); + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative), + equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10"))); producer.set(trace.getSpan(1)); }, @@ -314,7 +339,10 @@ class SpringKafkaTest extends AbstractSpringKafkaTest { equalTo( SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive")), + equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive"), + equalTo( + SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, + "testBatchListener")), span -> span.hasName("testBatchTopic process") .hasKind(SpanKind.CONSUMER) @@ -327,7 +355,10 @@ class SpringKafkaTest extends AbstractSpringKafkaTest { equalTo( SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process")), + equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), + equalTo( + SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, + "testBatchListener")), span -> span.hasName("consumer").hasParent(trace.getSpan(1)))); } } diff --git a/instrumentation/spring/spring-kafka-2.7/library/src/main/java/io/opentelemetry/instrumentation/spring/kafka/v2_7/InstrumentedBatchInterceptor.java b/instrumentation/spring/spring-kafka-2.7/library/src/main/java/io/opentelemetry/instrumentation/spring/kafka/v2_7/InstrumentedBatchInterceptor.java index a35cefd5f4..16b0274abf 100644 --- a/instrumentation/spring/spring-kafka-2.7/library/src/main/java/io/opentelemetry/instrumentation/spring/kafka/v2_7/InstrumentedBatchInterceptor.java +++ b/instrumentation/spring/spring-kafka-2.7/library/src/main/java/io/opentelemetry/instrumentation/spring/kafka/v2_7/InstrumentedBatchInterceptor.java @@ -9,6 +9,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.instrumentation.kafka.internal.ConsumerAndRecord; import javax.annotation.Nullable; import org.apache.kafka.clients.consumer.Consumer; import org.apache.kafka.clients.consumer.ConsumerRecords; @@ -18,14 +19,15 @@ final class InstrumentedBatchInterceptor implements BatchInterceptor private static final VirtualField, Context> receiveContextField = VirtualField.find(ConsumerRecords.class, Context.class); - private static final VirtualField, State>> - stateField = VirtualField.find(ConsumerRecords.class, State.class); + private static final VirtualField, State> stateField = + VirtualField.find(ConsumerRecords.class, State.class); - private final Instrumenter, Void> batchProcessInstrumenter; + private final Instrumenter>, Void> + batchProcessInstrumenter; @Nullable private final BatchInterceptor decorated; InstrumentedBatchInterceptor( - Instrumenter, Void> batchProcessInstrumenter, + Instrumenter>, Void> batchProcessInstrumenter, @Nullable BatchInterceptor decorated) { this.batchProcessInstrumenter = batchProcessInstrumenter; this.decorated = decorated; @@ -35,16 +37,17 @@ final class InstrumentedBatchInterceptor implements BatchInterceptor public ConsumerRecords intercept(ConsumerRecords records, Consumer consumer) { Context parentContext = getParentContext(records); - if (batchProcessInstrumenter.shouldStart(parentContext, records)) { - Context context = batchProcessInstrumenter.start(parentContext, records); + ConsumerAndRecord> request = ConsumerAndRecord.create(consumer, records); + if (batchProcessInstrumenter.shouldStart(parentContext, request)) { + Context context = batchProcessInstrumenter.start(parentContext, request); Scope scope = context.makeCurrent(); - stateField.set(records, State.create(records, context, scope)); + stateField.set(records, State.create(context, scope)); } return decorated == null ? records : decorated.intercept(records, consumer); } - private Context getParentContext(ConsumerRecords records) { + private static Context getParentContext(ConsumerRecords records) { Context receiveContext = receiveContextField.get(records); // use the receive CONSUMER span as parent if it's available @@ -53,7 +56,7 @@ final class InstrumentedBatchInterceptor implements BatchInterceptor @Override public void success(ConsumerRecords records, Consumer consumer) { - end(records, null); + end(ConsumerAndRecord.create(consumer, records), null); if (decorated != null) { decorated.success(records, consumer); } @@ -61,18 +64,20 @@ final class InstrumentedBatchInterceptor implements BatchInterceptor @Override public void failure(ConsumerRecords records, Exception exception, Consumer consumer) { - end(records, exception); + end(ConsumerAndRecord.create(consumer, records), exception); if (decorated != null) { decorated.failure(records, exception, consumer); } } - private void end(ConsumerRecords records, @Nullable Throwable error) { - State> state = stateField.get(records); + private void end( + ConsumerAndRecord> consumerAndRecord, @Nullable Throwable error) { + ConsumerRecords records = consumerAndRecord.record(); + State state = stateField.get(records); stateField.set(records, null); if (state != null) { state.scope().close(); - batchProcessInstrumenter.end(state.context(), state.request(), null, error); + batchProcessInstrumenter.end(state.context(), consumerAndRecord, null, error); } } } diff --git a/instrumentation/spring/spring-kafka-2.7/library/src/main/java/io/opentelemetry/instrumentation/spring/kafka/v2_7/InstrumentedRecordInterceptor.java b/instrumentation/spring/spring-kafka-2.7/library/src/main/java/io/opentelemetry/instrumentation/spring/kafka/v2_7/InstrumentedRecordInterceptor.java index ae4aab3abe..1cb16ceefe 100644 --- a/instrumentation/spring/spring-kafka-2.7/library/src/main/java/io/opentelemetry/instrumentation/spring/kafka/v2_7/InstrumentedRecordInterceptor.java +++ b/instrumentation/spring/spring-kafka-2.7/library/src/main/java/io/opentelemetry/instrumentation/spring/kafka/v2_7/InstrumentedRecordInterceptor.java @@ -9,6 +9,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.instrumentation.kafka.internal.ConsumerAndRecord; import io.opentelemetry.javaagent.tooling.muzzle.NoMuzzle; import javax.annotation.Nullable; import org.apache.kafka.clients.consumer.Consumer; @@ -19,14 +20,14 @@ final class InstrumentedRecordInterceptor implements RecordInterceptor, Context> receiveContextField = VirtualField.find(ConsumerRecord.class, Context.class); - private static final VirtualField, State>> stateField = + private static final VirtualField, State> stateField = VirtualField.find(ConsumerRecord.class, State.class); - private final Instrumenter, Void> processInstrumenter; + private final Instrumenter>, Void> processInstrumenter; @Nullable private final RecordInterceptor decorated; InstrumentedRecordInterceptor( - Instrumenter, Void> processInstrumenter, + Instrumenter>, Void> processInstrumenter, @Nullable RecordInterceptor decorated) { this.processInstrumenter = processInstrumenter; this.decorated = decorated; @@ -37,27 +38,28 @@ final class InstrumentedRecordInterceptor implements RecordInterceptor intercept(ConsumerRecord record) { - start(record); + start(ConsumerAndRecord.create(null, record)); return decorated == null ? record : decorated.intercept(record); } @Override public ConsumerRecord intercept(ConsumerRecord record, Consumer consumer) { - start(record); + start(ConsumerAndRecord.create(consumer, record)); return decorated == null ? record : decorated.intercept(record, consumer); } - private void start(ConsumerRecord record) { + private void start(ConsumerAndRecord> consumerAndRecord) { + ConsumerRecord record = consumerAndRecord.record(); Context parentContext = getParentContext(record); - if (processInstrumenter.shouldStart(parentContext, record)) { - Context context = processInstrumenter.start(parentContext, record); + if (processInstrumenter.shouldStart(parentContext, consumerAndRecord)) { + Context context = processInstrumenter.start(parentContext, consumerAndRecord); Scope scope = context.makeCurrent(); - stateField.set(record, State.create(record, context, scope)); + stateField.set(record, State.create(context, scope)); } } - private Context getParentContext(ConsumerRecord records) { + private static Context getParentContext(ConsumerRecord records) { Context receiveContext = receiveContextField.get(records); // use the receive CONSUMER span as parent if it's available @@ -66,7 +68,7 @@ final class InstrumentedRecordInterceptor implements RecordInterceptor record, Consumer consumer) { - end(record, null); + end(ConsumerAndRecord.create(consumer, record), null); if (decorated != null) { decorated.success(record, consumer); } @@ -74,18 +76,20 @@ final class InstrumentedRecordInterceptor implements RecordInterceptor record, Exception exception, Consumer consumer) { - end(record, exception); + end(ConsumerAndRecord.create(consumer, record), exception); if (decorated != null) { decorated.failure(record, exception, consumer); } } - private void end(ConsumerRecord record, @Nullable Throwable error) { - State> state = stateField.get(record); + private void end( + ConsumerAndRecord> consumerAndRecord, @Nullable Throwable error) { + ConsumerRecord record = consumerAndRecord.record(); + State state = stateField.get(record); stateField.set(record, null); if (state != null) { state.scope().close(); - processInstrumenter.end(state.context(), state.request(), null, error); + processInstrumenter.end(state.context(), consumerAndRecord, null, error); } } } diff --git a/instrumentation/spring/spring-kafka-2.7/library/src/main/java/io/opentelemetry/instrumentation/spring/kafka/v2_7/SpringKafkaTelemetry.java b/instrumentation/spring/spring-kafka-2.7/library/src/main/java/io/opentelemetry/instrumentation/spring/kafka/v2_7/SpringKafkaTelemetry.java index 085bc7b183..8e8a6b73f0 100644 --- a/instrumentation/spring/spring-kafka-2.7/library/src/main/java/io/opentelemetry/instrumentation/spring/kafka/v2_7/SpringKafkaTelemetry.java +++ b/instrumentation/spring/spring-kafka-2.7/library/src/main/java/io/opentelemetry/instrumentation/spring/kafka/v2_7/SpringKafkaTelemetry.java @@ -8,6 +8,7 @@ package io.opentelemetry.instrumentation.spring.kafka.v2_7; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.kafka.internal.ConsumerAndRecord; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.springframework.kafka.listener.AbstractMessageListenerContainer; @@ -30,12 +31,13 @@ public final class SpringKafkaTelemetry { return new SpringKafkaTelemetryBuilder(openTelemetry); } - private final Instrumenter, Void> processInstrumenter; - private final Instrumenter, Void> batchProcessInstrumenter; + private final Instrumenter>, Void> processInstrumenter; + private final Instrumenter>, Void> + batchProcessInstrumenter; SpringKafkaTelemetry( - Instrumenter, Void> processInstrumenter, - Instrumenter, Void> batchProcessInstrumenter) { + Instrumenter>, Void> processInstrumenter, + Instrumenter>, Void> batchProcessInstrumenter) { this.processInstrumenter = processInstrumenter; this.batchProcessInstrumenter = batchProcessInstrumenter; } diff --git a/instrumentation/spring/spring-kafka-2.7/library/src/main/java/io/opentelemetry/instrumentation/spring/kafka/v2_7/State.java b/instrumentation/spring/spring-kafka-2.7/library/src/main/java/io/opentelemetry/instrumentation/spring/kafka/v2_7/State.java index 47dbf0a375..aa9085cd97 100644 --- a/instrumentation/spring/spring-kafka-2.7/library/src/main/java/io/opentelemetry/instrumentation/spring/kafka/v2_7/State.java +++ b/instrumentation/spring/spring-kafka-2.7/library/src/main/java/io/opentelemetry/instrumentation/spring/kafka/v2_7/State.java @@ -10,14 +10,12 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; @AutoValue -abstract class State { +abstract class State { - static State create(REQUEST request, Context context, Scope scope) { - return new AutoValue_State<>(request, context, scope); + static State create(Context context, Scope scope) { + return new AutoValue_State(context, scope); } - abstract REQUEST request(); - abstract Context context(); abstract Scope scope(); diff --git a/instrumentation/spring/spring-kafka-2.7/testing/src/main/java/io/opentelemetry/testing/AbstractSpringKafkaNoReceiveTelemetryTest.java b/instrumentation/spring/spring-kafka-2.7/testing/src/main/java/io/opentelemetry/testing/AbstractSpringKafkaNoReceiveTelemetryTest.java index d002f5c3e3..2815aab69b 100644 --- a/instrumentation/spring/spring-kafka-2.7/testing/src/main/java/io/opentelemetry/testing/AbstractSpringKafkaNoReceiveTelemetryTest.java +++ b/instrumentation/spring/spring-kafka-2.7/testing/src/main/java/io/opentelemetry/testing/AbstractSpringKafkaNoReceiveTelemetryTest.java @@ -9,7 +9,6 @@ import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.or import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.data.StatusData; @@ -54,8 +53,9 @@ public abstract class AbstractSpringKafkaNoReceiveTelemetryTest extends Abstract SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, AbstractLongAssert::isNotNegative), satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative), + equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10")), span -> span.hasName("testSingleTopic process") .hasKind(SpanKind.CONSUMER) @@ -74,8 +74,12 @@ public abstract class AbstractSpringKafkaNoReceiveTelemetryTest extends Abstract SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, AbstractLongAssert::isNotNegative), satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative), + equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10"), + equalTo( + SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, + "testSingleListener")), span -> span.hasName("consumer").hasParent(trace.getSpan(2)))); } @@ -111,8 +115,9 @@ public abstract class AbstractSpringKafkaNoReceiveTelemetryTest extends Abstract SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, AbstractLongAssert::isNotNegative), satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative), + equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10")), span -> span.hasName("testSingleTopic process") .hasKind(SpanKind.CONSUMER) @@ -133,8 +138,12 @@ public abstract class AbstractSpringKafkaNoReceiveTelemetryTest extends Abstract SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, AbstractLongAssert::isNotNegative), satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative), + equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10"), + equalTo( + SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, + "testSingleListener")), span -> span.hasName("consumer").hasParent(trace.getSpan(2)))); } @@ -152,7 +161,7 @@ public abstract class AbstractSpringKafkaNoReceiveTelemetryTest extends Abstract .waitAndAssertSortedTraces( orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CONSUMER), trace -> { - trace.hasSpansSatisfyingExactly( + trace.hasSpansSatisfyingExactlyInAnyOrder( span -> span.hasName("producer"), span -> span.hasName("testBatchTopic send") @@ -167,8 +176,9 @@ public abstract class AbstractSpringKafkaNoReceiveTelemetryTest extends Abstract SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, AbstractLongAssert::isNotNegative), satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative), + equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10")), span -> span.hasName("testBatchTopic send") .hasKind(SpanKind.PRODUCER) @@ -182,8 +192,9 @@ public abstract class AbstractSpringKafkaNoReceiveTelemetryTest extends Abstract SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, AbstractLongAssert::isNotNegative), satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative))); + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative), + equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "20"))); producer1.set(trace.getSpan(1)); producer2.set(trace.getSpan(2)); @@ -204,7 +215,10 @@ public abstract class AbstractSpringKafkaNoReceiveTelemetryTest extends Abstract SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process")), + equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), + equalTo( + SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, + "testBatchListener")), span -> span.hasName("consumer").hasParent(trace.getSpan(0)))); } @@ -242,8 +256,9 @@ public abstract class AbstractSpringKafkaNoReceiveTelemetryTest extends Abstract SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, AbstractLongAssert::isNotNegative), satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative))); + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative), + equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10"))); producer.set(trace.getSpan(1)); }, @@ -262,7 +277,10 @@ public abstract class AbstractSpringKafkaNoReceiveTelemetryTest extends Abstract SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process")), + equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), + equalTo( + SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, + "testBatchListener")), span -> span.hasName("consumer").hasParent(trace.getSpan(0)))); } } diff --git a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/build.gradle.kts b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/build.gradle.kts index 83ffceaf3c..7bb1ac162f 100644 --- a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/build.gradle.kts +++ b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/build.gradle.kts @@ -24,13 +24,22 @@ dependencies { testInstrumentation(project(":instrumentation:kafka:kafka-clients:kafka-clients-0.11:javaagent")) } +val latestDepTest = findProperty("testLatestDeps") as Boolean + testing { suites { val testNoReceiveTelemetry by registering(JvmTestSuite::class) { dependencies { - implementation("io.vertx:vertx-kafka-client:3.6.0") - implementation("io.vertx:vertx-codegen:3.6.0") implementation(project(":instrumentation:vertx:vertx-kafka-client-3.6:testing")) + + // the "library" configuration is not recognized by the test suite plugin + if (latestDepTest) { + implementation("io.vertx:vertx-kafka-client:+") + implementation("io.vertx:vertx-codegen:+") + } else { + implementation("io.vertx:vertx-kafka-client:3.6.0") + implementation("io.vertx:vertx-codegen:3.6.0") + } } targets { @@ -38,6 +47,8 @@ testing { testTask.configure { usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) + systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) + jvmArgs("-Dotel.instrumentation.kafka.experimental-span-attributes=false") jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=false") } @@ -51,6 +62,8 @@ tasks { test { usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) + systemProperty("testLatestDeps", latestDepTest) + jvmArgs("-Dotel.instrumentation.kafka.experimental-span-attributes=true") jvmArgs("-Dotel.instrumentation.messaging.experimental.receive-telemetry.enabled=true") } diff --git a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/InstrumentedBatchRecordsHandler.java b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/InstrumentedBatchRecordsHandler.java index a4bb551a01..656f958905 100644 --- a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/InstrumentedBatchRecordsHandler.java +++ b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/InstrumentedBatchRecordsHandler.java @@ -10,28 +10,35 @@ import static io.opentelemetry.javaagent.instrumentation.vertx.kafka.v3_6.VertxK import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.instrumentation.kafka.internal.ConsumerAndRecord; import io.opentelemetry.javaagent.bootstrap.kafka.KafkaClientsConsumerProcessTracing; import io.vertx.core.Handler; import javax.annotation.Nullable; +import org.apache.kafka.clients.consumer.Consumer; import org.apache.kafka.clients.consumer.ConsumerRecords; public final class InstrumentedBatchRecordsHandler implements Handler> { private final VirtualField, Context> receiveContextField; + private final Consumer kafkaConsumer; @Nullable private final Handler> delegate; public InstrumentedBatchRecordsHandler( VirtualField, Context> receiveContextField, + Consumer kafkaConsumer, @Nullable Handler> delegate) { this.receiveContextField = receiveContextField; + this.kafkaConsumer = kafkaConsumer; this.delegate = delegate; } @Override public void handle(ConsumerRecords records) { Context parentContext = getParentContext(records); + ConsumerAndRecord> request = + ConsumerAndRecord.create(kafkaConsumer, records); - if (!batchProcessInstrumenter().shouldStart(parentContext, records)) { + if (!batchProcessInstrumenter().shouldStart(parentContext, request)) { callDelegateHandler(records); return; } @@ -39,7 +46,7 @@ public final class InstrumentedBatchRecordsHandler implements Handler implements Handler implements Handler> { private final VirtualField, Context> receiveContextField; + private final Consumer kafkaConsumer; @Nullable private final Handler> delegate; public InstrumentedSingleRecordHandler( VirtualField, Context> receiveContextField, + Consumer kafkaConsumer, @Nullable Handler> delegate) { this.receiveContextField = receiveContextField; + this.kafkaConsumer = kafkaConsumer; this.delegate = delegate; } @Override public void handle(ConsumerRecord record) { Context parentContext = getParentContext(record); + ConsumerAndRecord> request = + ConsumerAndRecord.create(kafkaConsumer, record); - if (!processInstrumenter().shouldStart(parentContext, record)) { + if (!processInstrumenter().shouldStart(parentContext, request)) { callDelegateHandler(record); return; } - Context context = processInstrumenter().start(parentContext, record); + Context context = processInstrumenter().start(parentContext, request); Throwable error = null; try (Scope ignored = context.makeCurrent()) { callDelegateHandler(record); @@ -43,7 +50,7 @@ public final class InstrumentedSingleRecordHandler implements Handler void onEnter( + @Advice.This KafkaReadStreamImpl readStream, @Advice.Argument(value = 0, readOnly = false) Handler> handler) { + Consumer consumer = readStream.unwrap(); VirtualField, Context> receiveContextField = VirtualField.find(ConsumerRecord.class, Context.class); - handler = new InstrumentedSingleRecordHandler<>(receiveContextField, handler); + handler = new InstrumentedSingleRecordHandler<>(receiveContextField, consumer, handler); } } @@ -63,11 +67,13 @@ public class KafkaReadStreamImplInstrumentation implements TypeInstrumentation { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( + @Advice.This KafkaReadStreamImpl readStream, @Advice.Argument(value = 0, readOnly = false) Handler> handler) { + Consumer consumer = readStream.unwrap(); VirtualField, Context> receiveContextField = VirtualField.find(ConsumerRecords.class, Context.class); - handler = new InstrumentedBatchRecordsHandler<>(receiveContextField, handler); + handler = new InstrumentedBatchRecordsHandler<>(receiveContextField, consumer, handler); } } diff --git a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/VertxKafkaSingletons.java b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/VertxKafkaSingletons.java index de92d856f2..0926f33515 100644 --- a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/VertxKafkaSingletons.java +++ b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/VertxKafkaSingletons.java @@ -7,6 +7,7 @@ package io.opentelemetry.javaagent.instrumentation.vertx.kafka.v3_6; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.kafka.internal.ConsumerAndRecord; import io.opentelemetry.instrumentation.kafka.internal.KafkaInstrumenterFactory; import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig; import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig; @@ -17,8 +18,10 @@ public final class VertxKafkaSingletons { private static final String INSTRUMENTATION_NAME = "io.opentelemetry.vertx-kafka-client-3.6"; - private static final Instrumenter, Void> BATCH_PROCESS_INSTRUMENTER; - private static final Instrumenter, Void> PROCESS_INSTRUMENTER; + private static final Instrumenter>, Void> + BATCH_PROCESS_INSTRUMENTER; + private static final Instrumenter>, Void> + PROCESS_INSTRUMENTER; static { KafkaInstrumenterFactory factory = @@ -33,11 +36,12 @@ public final class VertxKafkaSingletons { PROCESS_INSTRUMENTER = factory.createConsumerProcessInstrumenter(); } - public static Instrumenter, Void> batchProcessInstrumenter() { + public static Instrumenter>, Void> + batchProcessInstrumenter() { return BATCH_PROCESS_INSTRUMENTER; } - public static Instrumenter, Void> processInstrumenter() { + public static Instrumenter>, Void> processInstrumenter() { return PROCESS_INSTRUMENTER; } diff --git a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/BatchRecordsVertxKafkaTest.java b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/BatchRecordsVertxKafkaTest.java index ba125060f6..ebe96288d7 100644 --- a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/BatchRecordsVertxKafkaTest.java +++ b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/BatchRecordsVertxKafkaTest.java @@ -5,23 +5,17 @@ package io.opentelemetry.javaagent.instrumentation.vertx.kafka.v3_6; -import static io.opentelemetry.api.common.AttributeKey.longKey; import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanKind; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; import static org.junit.jupiter.api.Assertions.assertTrue; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.sdk.trace.data.LinkData; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.data.StatusData; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import io.vertx.kafka.client.producer.KafkaProducerRecord; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import org.assertj.core.api.AbstractLongAssert; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Order; @@ -51,9 +45,11 @@ class BatchRecordsVertxKafkaTest extends AbstractVertxKafkaTest { void shouldCreateSpansForBatchReceiveAndProcess() throws InterruptedException { assertTrue(consumerReady.await(30, TimeUnit.SECONDS)); - sendBatchMessages( - KafkaProducerRecord.create("testBatchTopic", "10", "testSpan1"), - KafkaProducerRecord.create("testBatchTopic", "20", "testSpan2")); + KafkaProducerRecord record1 = + KafkaProducerRecord.create("testBatchTopic", "10", "testSpan1"); + KafkaProducerRecord record2 = + KafkaProducerRecord.create("testBatchTopic", "20", "testSpan2"); + sendBatchMessages(record1, record2); AtomicReference producer1 = new AtomicReference<>(); AtomicReference producer2 = new AtomicReference<>(); @@ -61,52 +57,29 @@ class BatchRecordsVertxKafkaTest extends AbstractVertxKafkaTest { testing.waitAndAssertSortedTraces( orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CONSUMER), trace -> { - trace.hasSpansSatisfyingExactly( + trace.hasSpansSatisfyingExactlyInAnyOrder( span -> span.hasName("producer"), span -> span.hasName("testBatchTopic send") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + .hasAttributesSatisfyingExactly(sendAttributes(record1)), span -> span.hasName("testBatchTopic send") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative))); + .hasAttributesSatisfyingExactly(sendAttributes(record2))); producer1.set(trace.getSpan(1)); producer2.set(trace.getSpan(2)); }, trace -> - trace.hasSpansSatisfyingExactly( + trace.hasSpansSatisfyingExactlyInAnyOrder( span -> span.hasName("testBatchTopic receive") .hasKind(SpanKind.CONSUMER) .hasNoParent() - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive")), + .hasAttributesSatisfyingExactly(receiveAttributes("testBatchTopic")), // batch consumer span -> @@ -116,12 +89,7 @@ class BatchRecordsVertxKafkaTest extends AbstractVertxKafkaTest { .hasLinks( LinkData.create(producer1.get().getSpanContext()), LinkData.create(producer2.get().getSpanContext())) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process")), + .hasAttributesSatisfyingExactly(batchProcessAttributes("testBatchTopic")), span -> span.hasName("batch consumer").hasParent(trace.getSpan(1)), // single consumer 1 @@ -130,24 +98,7 @@ class BatchRecordsVertxKafkaTest extends AbstractVertxKafkaTest { .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(0)) .hasLinks(LinkData.create(producer1.get().getSpanContext())) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), - satisfies( - SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, - AbstractLongAssert::isNotNegative), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative), - satisfies( - longKey("kafka.record.queue_time_ms"), - AbstractLongAssert::isNotNegative)), + .hasAttributesSatisfyingExactly(processAttributes(record1)), span -> span.hasName("process testSpan1").hasParent(trace.getSpan(3)), // single consumer 2 @@ -156,24 +107,7 @@ class BatchRecordsVertxKafkaTest extends AbstractVertxKafkaTest { .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(0)) .hasLinks(LinkData.create(producer2.get().getSpanContext())) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), - satisfies( - SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, - AbstractLongAssert::isNotNegative), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative), - satisfies( - longKey("kafka.record.queue_time_ms"), - AbstractLongAssert::isNotNegative)), + .hasAttributesSatisfyingExactly(processAttributes(record2)), span -> span.hasName("process testSpan2").hasParent(trace.getSpan(5)))); } @@ -182,7 +116,9 @@ class BatchRecordsVertxKafkaTest extends AbstractVertxKafkaTest { void shouldHandleFailureInKafkaBatchListener() throws InterruptedException { assertTrue(consumerReady.await(30, TimeUnit.SECONDS)); - sendBatchMessages(KafkaProducerRecord.create("testBatchTopic", "10", "error")); + KafkaProducerRecord record = + KafkaProducerRecord.create("testBatchTopic", "10", "error"); + sendBatchMessages(record); // make sure that the consumer eats up any leftover records kafkaConsumer.resume(); @@ -198,16 +134,7 @@ class BatchRecordsVertxKafkaTest extends AbstractVertxKafkaTest { span.hasName("testBatchTopic send") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative))); + .hasAttributesSatisfyingExactly(sendAttributes(record))); producer.set(trace.getSpan(1)); }, @@ -217,12 +144,7 @@ class BatchRecordsVertxKafkaTest extends AbstractVertxKafkaTest { span.hasName("testBatchTopic receive") .hasKind(SpanKind.CONSUMER) .hasNoParent() - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive")), + .hasAttributesSatisfyingExactly(receiveAttributes("testBatchTopic")), // batch consumer span -> @@ -232,12 +154,7 @@ class BatchRecordsVertxKafkaTest extends AbstractVertxKafkaTest { .hasLinks(LinkData.create(producer.get().getSpanContext())) .hasStatus(StatusData.error()) .hasException(new IllegalArgumentException("boom")) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process")), + .hasAttributesSatisfyingExactly(batchProcessAttributes("testBatchTopic")), span -> span.hasName("batch consumer").hasParent(trace.getSpan(1)), // single consumer @@ -245,24 +162,7 @@ class BatchRecordsVertxKafkaTest extends AbstractVertxKafkaTest { span.hasName("testBatchTopic process") .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), - satisfies( - SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, - AbstractLongAssert::isNotNegative), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative), - satisfies( - longKey("kafka.record.queue_time_ms"), - AbstractLongAssert::isNotNegative)), + .hasAttributesSatisfyingExactly(processAttributes(record)), span -> span.hasName("process error").hasParent(trace.getSpan(3)))); } } diff --git a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/SingleRecordVertxKafkaTest.java b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/SingleRecordVertxKafkaTest.java index e848d430f0..1cf2d31767 100644 --- a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/SingleRecordVertxKafkaTest.java +++ b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/SingleRecordVertxKafkaTest.java @@ -5,23 +5,17 @@ package io.opentelemetry.javaagent.instrumentation.vertx.kafka.v3_6; -import static io.opentelemetry.api.common.AttributeKey.longKey; import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanKind; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; import static org.junit.jupiter.api.Assertions.assertTrue; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.sdk.trace.data.LinkData; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.data.StatusData; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import io.vertx.kafka.client.producer.KafkaProducerRecord; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import org.assertj.core.api.AbstractLongAssert; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -47,13 +41,10 @@ class SingleRecordVertxKafkaTest extends AbstractVertxKafkaTest { void shouldCreateSpansForSingleRecordProcess() throws InterruptedException { assertTrue(consumerReady.await(30, TimeUnit.SECONDS)); + KafkaProducerRecord record = + KafkaProducerRecord.create("testSingleTopic", "10", "testSpan"); CountDownLatch sent = new CountDownLatch(1); - testing.runWithSpan( - "producer", - () -> - sendRecord( - KafkaProducerRecord.create("testSingleTopic", "10", "testSpan"), - result -> sent.countDown())); + testing.runWithSpan("producer", () -> sendRecord(record, result -> sent.countDown())); assertTrue(sent.await(30, TimeUnit.SECONDS)); AtomicReference producer = new AtomicReference<>(); @@ -67,16 +58,7 @@ class SingleRecordVertxKafkaTest extends AbstractVertxKafkaTest { span.hasName("testSingleTopic send") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, "testSingleTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative))); + .hasAttributesSatisfyingExactly(sendAttributes(record))); producer.set(trace.getSpan(1)); }, @@ -86,35 +68,13 @@ class SingleRecordVertxKafkaTest extends AbstractVertxKafkaTest { span.hasName("testSingleTopic receive") .hasKind(SpanKind.CONSUMER) .hasNoParent() - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - SemanticAttributes.MESSAGING_DESTINATION_NAME, "testSingleTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive")), + .hasAttributesSatisfyingExactly(receiveAttributes("testSingleTopic")), span -> span.hasName("testSingleTopic process") .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(0)) .hasLinks(LinkData.create(producer.get().getSpanContext())) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - SemanticAttributes.MESSAGING_DESTINATION_NAME, "testSingleTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), - satisfies( - SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, - AbstractLongAssert::isNotNegative), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative), - satisfies( - longKey("kafka.record.queue_time_ms"), - AbstractLongAssert::isNotNegative)), + .hasAttributesSatisfyingExactly(processAttributes(record)), span -> span.hasName("consumer").hasParent(trace.getSpan(1)))); } @@ -122,13 +82,10 @@ class SingleRecordVertxKafkaTest extends AbstractVertxKafkaTest { void shouldHandleFailureInSingleRecordHandler() throws InterruptedException { assertTrue(consumerReady.await(30, TimeUnit.SECONDS)); + KafkaProducerRecord record = + KafkaProducerRecord.create("testSingleTopic", "10", "error"); CountDownLatch sent = new CountDownLatch(1); - testing.runWithSpan( - "producer", - () -> - sendRecord( - KafkaProducerRecord.create("testSingleTopic", "10", "error"), - result -> sent.countDown())); + testing.runWithSpan("producer", () -> sendRecord(record, result -> sent.countDown())); assertTrue(sent.await(30, TimeUnit.SECONDS)); AtomicReference producer = new AtomicReference<>(); @@ -142,16 +99,7 @@ class SingleRecordVertxKafkaTest extends AbstractVertxKafkaTest { span.hasName("testSingleTopic send") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, "testSingleTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative))); + .hasAttributesSatisfyingExactly(sendAttributes(record))); producer.set(trace.getSpan(1)); }, @@ -161,12 +109,7 @@ class SingleRecordVertxKafkaTest extends AbstractVertxKafkaTest { span.hasName("testSingleTopic receive") .hasKind(SpanKind.CONSUMER) .hasNoParent() - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - SemanticAttributes.MESSAGING_DESTINATION_NAME, "testSingleTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive")), + .hasAttributesSatisfyingExactly(receiveAttributes("testSingleTopic")), span -> span.hasName("testSingleTopic process") .hasKind(SpanKind.CONSUMER) @@ -174,24 +117,7 @@ class SingleRecordVertxKafkaTest extends AbstractVertxKafkaTest { .hasLinks(LinkData.create(producer.get().getSpanContext())) .hasStatus(StatusData.error()) .hasException(new IllegalArgumentException("boom")) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - SemanticAttributes.MESSAGING_DESTINATION_NAME, "testSingleTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), - satisfies( - SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, - AbstractLongAssert::isNotNegative), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative), - satisfies( - longKey("kafka.record.queue_time_ms"), - AbstractLongAssert::isNotNegative)), + .hasAttributesSatisfyingExactly(processAttributes(record)), span -> span.hasName("consumer").hasParent(trace.getSpan(1)))); } } diff --git a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetryBatchRecordsVertxKafkaTest.java b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetryBatchRecordsVertxKafkaTest.java index bcb3cf1fbb..f13fa40d8c 100644 --- a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetryBatchRecordsVertxKafkaTest.java +++ b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetryBatchRecordsVertxKafkaTest.java @@ -6,21 +6,16 @@ package io.opentelemetry.javaagent.instrumentation.vertx.kafka.v3_6; import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanKind; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; import static org.junit.jupiter.api.Assertions.assertTrue; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.sdk.trace.data.LinkData; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.data.StatusData; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import io.vertx.kafka.client.producer.KafkaProducerRecord; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import org.assertj.core.api.AbstractLongAssert; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; @@ -50,9 +45,11 @@ class NoReceiveTelemetryBatchRecordsVertxKafkaTest extends AbstractVertxKafkaTes void shouldCreateSpansForBatchReceiveAndProcess() throws InterruptedException { assertTrue(consumerReady.await(30, TimeUnit.SECONDS)); - sendBatchMessages( - KafkaProducerRecord.create("testBatchTopic", "10", "testSpan1"), - KafkaProducerRecord.create("testBatchTopic", "20", "testSpan2")); + KafkaProducerRecord record1 = + KafkaProducerRecord.create("testBatchTopic", "10", "testSpan1"); + KafkaProducerRecord record2 = + KafkaProducerRecord.create("testBatchTopic", "20", "testSpan2"); + sendBatchMessages(record1, record2); AtomicReference producer1 = new AtomicReference<>(); AtomicReference producer2 = new AtomicReference<>(); @@ -60,7 +57,7 @@ class NoReceiveTelemetryBatchRecordsVertxKafkaTest extends AbstractVertxKafkaTes testing.waitAndAssertSortedTraces( orderByRootSpanKind(SpanKind.INTERNAL, SpanKind.CONSUMER), trace -> { - trace.hasSpansSatisfyingExactly( + trace.hasSpansSatisfyingExactlyInAnyOrder( span -> span.hasName("producer"), // first record @@ -68,34 +65,12 @@ class NoReceiveTelemetryBatchRecordsVertxKafkaTest extends AbstractVertxKafkaTes span.hasName("testBatchTopic send") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + .hasAttributesSatisfyingExactly(sendAttributes(record1)), span -> span.hasName("testBatchTopic process") .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(1)) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), - satisfies( - SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, - AbstractLongAssert::isNotNegative), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + .hasAttributesSatisfyingExactly(processAttributes(record1)), span -> span.hasName("process testSpan1").hasParent(trace.getSpan(2)), // second record @@ -103,34 +78,12 @@ class NoReceiveTelemetryBatchRecordsVertxKafkaTest extends AbstractVertxKafkaTes span.hasName("testBatchTopic send") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + .hasAttributesSatisfyingExactly(sendAttributes(record2)), span -> span.hasName("testBatchTopic process") .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(4)) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), - satisfies( - SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, - AbstractLongAssert::isNotNegative), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + .hasAttributesSatisfyingExactly(processAttributes(record2)), span -> span.hasName("process testSpan2").hasParent(trace.getSpan(5))); producer1.set(trace.getSpan(1)); @@ -146,12 +99,7 @@ class NoReceiveTelemetryBatchRecordsVertxKafkaTest extends AbstractVertxKafkaTes .hasLinks( LinkData.create(producer1.get().getSpanContext()), LinkData.create(producer2.get().getSpanContext())) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process")), + .hasAttributesSatisfyingExactly(batchProcessAttributes("testBatchTopic")), span -> span.hasName("batch consumer").hasParent(trace.getSpan(0)))); } @@ -160,7 +108,9 @@ class NoReceiveTelemetryBatchRecordsVertxKafkaTest extends AbstractVertxKafkaTes void shouldHandleFailureInKafkaBatchListener() throws InterruptedException { assertTrue(consumerReady.await(30, TimeUnit.SECONDS)); - sendBatchMessages(KafkaProducerRecord.create("testBatchTopic", "10", "error")); + KafkaProducerRecord record = + KafkaProducerRecord.create("testBatchTopic", "10", "error"); + sendBatchMessages(record); // make sure that the consumer eats up any leftover records kafkaConsumer.resume(); @@ -176,34 +126,12 @@ class NoReceiveTelemetryBatchRecordsVertxKafkaTest extends AbstractVertxKafkaTes span.hasName("testBatchTopic send") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + .hasAttributesSatisfyingExactly(sendAttributes(record)), span -> span.hasName("testBatchTopic process") .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(1)) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), - satisfies( - SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, - AbstractLongAssert::isNotNegative), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + .hasAttributesSatisfyingExactly(processAttributes(record)), span -> span.hasName("process error").hasParent(trace.getSpan(2))); producer.set(trace.getSpan(1)); @@ -217,12 +145,7 @@ class NoReceiveTelemetryBatchRecordsVertxKafkaTest extends AbstractVertxKafkaTes .hasLinks(LinkData.create(producer.get().getSpanContext())) .hasStatus(StatusData.error()) .hasException(new IllegalArgumentException("boom")) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - SemanticAttributes.MESSAGING_DESTINATION_NAME, "testBatchTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process")), + .hasAttributesSatisfyingExactly(batchProcessAttributes("testBatchTopic")), span -> span.hasName("batch consumer").hasParent(trace.getSpan(0)))); } } diff --git a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetrySingleRecordVertxKafkaTest.java b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetrySingleRecordVertxKafkaTest.java index 6042ddbf87..e824b55476 100644 --- a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetrySingleRecordVertxKafkaTest.java +++ b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetrySingleRecordVertxKafkaTest.java @@ -5,18 +5,13 @@ package io.opentelemetry.javaagent.instrumentation.vertx.kafka.v3_6; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; import static org.junit.jupiter.api.Assertions.assertTrue; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.sdk.trace.data.StatusData; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import io.vertx.kafka.client.producer.KafkaProducerRecord; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import org.assertj.core.api.AbstractLongAssert; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -42,13 +37,10 @@ class NoReceiveTelemetrySingleRecordVertxKafkaTest extends AbstractVertxKafkaTes void shouldCreateSpansForSingleRecordProcess() throws InterruptedException { assertTrue(consumerReady.await(30, TimeUnit.SECONDS)); + KafkaProducerRecord record = + KafkaProducerRecord.create("testSingleTopic", "10", "testSpan"); CountDownLatch sent = new CountDownLatch(1); - testing.runWithSpan( - "producer", - () -> - sendRecord( - KafkaProducerRecord.create("testSingleTopic", "10", "testSpan"), - result -> sent.countDown())); + testing.runWithSpan("producer", () -> sendRecord(record, result -> sent.countDown())); assertTrue(sent.await(30, TimeUnit.SECONDS)); testing.waitAndAssertTraces( @@ -59,36 +51,12 @@ class NoReceiveTelemetrySingleRecordVertxKafkaTest extends AbstractVertxKafkaTes span.hasName("testSingleTopic send") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - SemanticAttributes.MESSAGING_DESTINATION_NAME, "testSingleTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + .hasAttributesSatisfyingExactly(sendAttributes(record)), span -> span.hasName("testSingleTopic process") .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(1)) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - SemanticAttributes.MESSAGING_DESTINATION_NAME, "testSingleTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), - satisfies( - SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, - AbstractLongAssert::isNotNegative), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + .hasAttributesSatisfyingExactly(processAttributes(record)), span -> span.hasName("consumer").hasParent(trace.getSpan(2)))); } @@ -96,13 +64,10 @@ class NoReceiveTelemetrySingleRecordVertxKafkaTest extends AbstractVertxKafkaTes void shouldHandleFailureInSingleRecordHandler() throws InterruptedException { assertTrue(consumerReady.await(30, TimeUnit.SECONDS)); + KafkaProducerRecord record = + KafkaProducerRecord.create("testSingleTopic", "10", "error"); CountDownLatch sent = new CountDownLatch(1); - testing.runWithSpan( - "producer", - () -> - sendRecord( - KafkaProducerRecord.create("testSingleTopic", "10", "error"), - result -> sent.countDown())); + testing.runWithSpan("producer", () -> sendRecord(record, result -> sent.countDown())); assertTrue(sent.await(30, TimeUnit.SECONDS)); testing.waitAndAssertTraces( @@ -113,38 +78,14 @@ class NoReceiveTelemetrySingleRecordVertxKafkaTest extends AbstractVertxKafkaTes span.hasName("testSingleTopic send") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - SemanticAttributes.MESSAGING_DESTINATION_NAME, "testSingleTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + .hasAttributesSatisfyingExactly(sendAttributes(record)), span -> span.hasName("testSingleTopic process") .hasKind(SpanKind.CONSUMER) .hasParent(trace.getSpan(1)) .hasStatus(StatusData.error()) .hasException(new IllegalArgumentException("boom")) - .hasAttributesSatisfyingExactly( - equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), - equalTo( - SemanticAttributes.MESSAGING_DESTINATION_NAME, "testSingleTopic"), - equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), - equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), - satisfies( - SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, - AbstractLongAssert::isNotNegative), - satisfies( - SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, - AbstractLongAssert::isNotNegative), - satisfies( - AttributeKey.longKey("messaging.kafka.message.offset"), - AbstractLongAssert::isNotNegative)), + .hasAttributesSatisfyingExactly(processAttributes(record)), span -> span.hasName("consumer").hasParent(trace.getSpan(2)))); } } diff --git a/instrumentation/vertx/vertx-kafka-client-3.6/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/AbstractVertxKafkaTest.java b/instrumentation/vertx/vertx-kafka-client-3.6/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/AbstractVertxKafkaTest.java index 978dc6d9eb..8c88d6142b 100644 --- a/instrumentation/vertx/vertx-kafka-client-3.6/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/AbstractVertxKafkaTest.java +++ b/instrumentation/vertx/vertx-kafka-client-3.6/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/AbstractVertxKafkaTest.java @@ -5,10 +5,15 @@ package io.opentelemetry.javaagent.instrumentation.vertx.kafka.v3_6; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; import static org.junit.jupiter.api.Assertions.assertTrue; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import io.vertx.core.AsyncResult; import io.vertx.core.Handler; import io.vertx.core.Vertx; @@ -19,12 +24,17 @@ import io.vertx.kafka.client.producer.RecordMetadata; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Properties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.kafka.common.serialization.StringDeserializer; import org.apache.kafka.common.serialization.StringSerializer; +import org.assertj.core.api.AbstractLongAssert; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; @@ -182,4 +192,87 @@ public abstract class AbstractVertxKafkaTest { throw new AssertionError("Failed producer send/write invocation", e); } } + + protected static List sendAttributes( + KafkaProducerRecord record) { + List assertions = + new ArrayList<>( + Arrays.asList( + equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), + equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, record.topic()), + equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), + satisfies( + SemanticAttributes.MESSAGING_KAFKA_DESTINATION_PARTITION, + AbstractLongAssert::isNotNegative), + satisfies( + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative))); + String messageKey = record.key(); + if (messageKey != null) { + assertions.add(equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, messageKey)); + } + return assertions; + } + + protected static List receiveAttributes(String topic) { + return batchConsumerAttributes(topic, "receive"); + } + + protected static List batchProcessAttributes(String topic) { + return batchConsumerAttributes(topic, "process"); + } + + private static List batchConsumerAttributes(String topic, String operation) { + List assertions = + new ArrayList<>( + Arrays.asList( + equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), + equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, topic), + equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), + equalTo(SemanticAttributes.MESSAGING_OPERATION, operation))); + // consumer group id is not available in version 0.11 + if (Boolean.getBoolean("testLatestDeps")) { + assertions.add(equalTo(SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, "test")); + } + return assertions; + } + + protected static List processAttributes( + KafkaProducerRecord record) { + List assertions = + new ArrayList<>( + Arrays.asList( + equalTo(SemanticAttributes.MESSAGING_SYSTEM, "kafka"), + equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, record.topic()), + equalTo(SemanticAttributes.MESSAGING_DESTINATION_KIND, "topic"), + equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"), + satisfies( + SemanticAttributes.MESSAGING_KAFKA_SOURCE_PARTITION, + AbstractLongAssert::isNotNegative), + satisfies( + SemanticAttributes.MESSAGING_KAFKA_MESSAGE_OFFSET, + AbstractLongAssert::isNotNegative))); + if (Boolean.getBoolean("otel.instrumentation.kafka.experimental-span-attributes")) { + assertions.add( + satisfies( + AttributeKey.longKey("kafka.record.queue_time_ms"), + AbstractLongAssert::isNotNegative)); + } + // consumer group id is not available in version 0.11 + if (Boolean.getBoolean("testLatestDeps")) { + assertions.add(equalTo(SemanticAttributes.MESSAGING_KAFKA_CONSUMER_GROUP, "test")); + } + String messageKey = record.key(); + if (messageKey != null) { + assertions.add(equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, messageKey)); + } + String messageValue = record.value(); + if (messageValue != null) { + assertions.add( + equalTo( + SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, + messageValue.getBytes(StandardCharsets.UTF_8).length)); + } + return assertions; + } }