From 2a012ae7fcdd8050129549e3096d92149b0d5acc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 00:56:05 +0000 Subject: [PATCH 01/21] Initial plan From 34d68ec0259effa3ec80dbd1f8351fca87ffc444 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 01:05:56 +0000 Subject: [PATCH 02/21] Initial plan for refactoring kafka interceptors Co-authored-by: trask <218610+trask@users.noreply.github.com> --- .../.kotlin/sessions/kotlin-compiler-16318826606158418015.salive | 0 .../.kotlin/sessions/kotlin-compiler-16068091306083868015.salive | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 conventions/.kotlin/sessions/kotlin-compiler-16318826606158418015.salive create mode 100644 gradle-plugins/.kotlin/sessions/kotlin-compiler-16068091306083868015.salive diff --git a/conventions/.kotlin/sessions/kotlin-compiler-16318826606158418015.salive b/conventions/.kotlin/sessions/kotlin-compiler-16318826606158418015.salive new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/gradle-plugins/.kotlin/sessions/kotlin-compiler-16068091306083868015.salive b/gradle-plugins/.kotlin/sessions/kotlin-compiler-16068091306083868015.salive new file mode 100644 index 000000000000..e69de29bb2d1 From 3b68915fba6d4a6f37e4795315be137571f2ccd0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 01:26:31 +0000 Subject: [PATCH 03/21] Refactor kafka interceptors to be configurable Co-authored-by: trask <218610+trask@users.noreply.github.com> --- ...otlin-compiler-16318826606158418015.salive | 0 ...otlin-compiler-16068091306083868015.salive | 0 .../kafkaclients/v2_6/KafkaTelemetry.java | 56 +++++++++++++++++++ .../v2_6/TracingConsumerInterceptor.java | 37 ++++++++---- .../v2_6/TracingProducerInterceptor.java | 35 ++++++++---- .../v2_6/AbstractInterceptorsTest.java | 17 ++++++ .../InterceptorsSuppressReceiveSpansTest.java | 6 ++ .../internal/KafkaTelemetrySupplier.java | 37 ++++++++++++ 8 files changed, 166 insertions(+), 22 deletions(-) delete mode 100644 conventions/.kotlin/sessions/kotlin-compiler-16318826606158418015.salive delete mode 100644 gradle-plugins/.kotlin/sessions/kotlin-compiler-16068091306083868015.salive create mode 100644 instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaTelemetrySupplier.java diff --git a/conventions/.kotlin/sessions/kotlin-compiler-16318826606158418015.salive b/conventions/.kotlin/sessions/kotlin-compiler-16318826606158418015.salive deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/gradle-plugins/.kotlin/sessions/kotlin-compiler-16068091306083868015.salive b/gradle-plugins/.kotlin/sessions/kotlin-compiler-16068091306083868015.salive deleted file mode 100644 index e69de29bb2d1..000000000000 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 d66003f6607f..3e285da18f2d 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 @@ -21,6 +21,7 @@ import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaProcessRequest; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaProducerRequest; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaReceiveRequest; +import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaTelemetrySupplier; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaUtil; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.MetricsReporterList; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.OpenTelemetryMetricsReporter; @@ -207,6 +208,61 @@ ConsumerRecords addTracing( return Collections.unmodifiableMap(config); } + /** + * Returns a config property key that can be used to pass this {@link KafkaTelemetry} instance to + * interceptors. + * + *

This is an internal config key used by the library instrumentation interceptors. + */ + static final String CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER = + "opentelemetry.experimental.kafka-telemetry.supplier"; + + /** + * Produces a set of kafka producer config properties to configure tracing in {@code + * TracingProducerInterceptor}. Add these resulting properties to the configuration map used to + * initialize a {@link KafkaProducer}. + * + *

Example usage: + * + *

{@code
+   * //    Map config = new HashMap<>();
+   * //    config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, ...);
+   * //    config.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName());
+   * //    config.putAll(kafkaTelemetry.producerInterceptorConfigProperties());
+   * //    try (KafkaProducer producer = new KafkaProducer<>(config)) { ... }
+   * }
+ * + * @return the kafka producer interceptor config properties + */ + public Map producerInterceptorConfigProperties() { + Map config = new HashMap<>(); + config.put(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, new KafkaTelemetrySupplier(this)); + return Collections.unmodifiableMap(config); + } + + /** + * Produces a set of kafka consumer config properties to configure tracing in {@code + * TracingConsumerInterceptor}. Add these resulting properties to the configuration map used to + * initialize a {@link KafkaConsumer}. + * + *

Example usage: + * + *

{@code
+   * //    Map config = new HashMap<>();
+   * //    config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, ...);
+   * //    config.put(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName());
+   * //    config.putAll(kafkaTelemetry.consumerInterceptorConfigProperties());
+   * //    try (KafkaConsumer consumer = new KafkaConsumer<>(config)) { ... }
+   * }
+ * + * @return the kafka consumer interceptor config properties + */ + public Map consumerInterceptorConfigProperties() { + Map config = new HashMap<>(); + config.put(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, new KafkaTelemetrySupplier(this)); + return Collections.unmodifiableMap(config); + } + /** * Build and inject span into record. * 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 8f60b07e6a88..48744d8a5ce7 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 @@ -16,6 +16,8 @@ import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaConsumerContextUtil; import java.util.Map; import java.util.Objects; +import java.util.function.Supplier; +import javax.annotation.Nullable; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerInterceptor; import org.apache.kafka.clients.consumer.ConsumerRecords; @@ -29,22 +31,16 @@ */ public class TracingConsumerInterceptor implements ConsumerInterceptor { - private static final KafkaTelemetry telemetry = - KafkaTelemetry.builder(GlobalOpenTelemetry.get()) - .setMessagingReceiveInstrumentationEnabled( - ConfigPropertiesUtil.getBoolean( - "otel.instrumentation.messaging.experimental.receive-telemetry.enabled", false)) - .setCapturedHeaders( - ConfigPropertiesUtil.getList( - "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) - .build(); - + @Nullable private KafkaTelemetry telemetry; private String consumerGroup; private String clientId; @Override @CanIgnoreReturnValue public ConsumerRecords onConsume(ConsumerRecords records) { + if (telemetry == null) { + return records; + } // timer should be started before fetching ConsumerRecords, but there is no callback for that Timer timer = Timer.start(); Context receiveContext = telemetry.buildAndFinishSpan(records, consumerGroup, clientId, timer); @@ -67,6 +63,25 @@ public void configure(Map configs) { consumerGroup = Objects.toString(configs.get(ConsumerConfig.GROUP_ID_CONFIG), null); clientId = Objects.toString(configs.get(ConsumerConfig.CLIENT_ID_CONFIG), null); - // TODO: support experimental attributes config + // Try to get KafkaTelemetry from config + Object telemetrySupplier = configs.get(KafkaTelemetry.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); + if (telemetrySupplier instanceof Supplier) { + Object kafkaTelemetry = ((Supplier) telemetrySupplier).get(); + if (kafkaTelemetry instanceof KafkaTelemetry) { + this.telemetry = (KafkaTelemetry) kafkaTelemetry; + return; + } + } + + // Fallback to GlobalOpenTelemetry if not configured + this.telemetry = + KafkaTelemetry.builder(GlobalOpenTelemetry.get()) + .setMessagingReceiveInstrumentationEnabled( + ConfigPropertiesUtil.getBoolean( + "otel.instrumentation.messaging.experimental.receive-telemetry.enabled", false)) + .setCapturedHeaders( + ConfigPropertiesUtil.getList( + "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) + .build(); } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java index 833b0785d181..921e605ce687 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java @@ -12,6 +12,7 @@ import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil; import java.util.Map; import java.util.Objects; +import java.util.function.Supplier; import javax.annotation.Nullable; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerInterceptor; @@ -25,19 +26,15 @@ */ public class TracingProducerInterceptor implements ProducerInterceptor { - private static final KafkaTelemetry telemetry = - KafkaTelemetry.builder(GlobalOpenTelemetry.get()) - .setCapturedHeaders( - ConfigPropertiesUtil.getList( - "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) - .build(); - + @Nullable private KafkaTelemetry telemetry; @Nullable private String clientId; @Override @CanIgnoreReturnValue public ProducerRecord onSend(ProducerRecord producerRecord) { - telemetry.buildAndInjectSpan(producerRecord, clientId); + if (telemetry != null) { + telemetry.buildAndInjectSpan(producerRecord, clientId); + } return producerRecord; } @@ -48,9 +45,25 @@ public void onAcknowledgement(RecordMetadata recordMetadata, Exception e) {} public void close() {} @Override - public void configure(Map map) { - clientId = Objects.toString(map.get(ProducerConfig.CLIENT_ID_CONFIG), null); + public void configure(Map configs) { + clientId = Objects.toString(configs.get(ProducerConfig.CLIENT_ID_CONFIG), null); + + // Try to get KafkaTelemetry from config + Object telemetrySupplier = configs.get(KafkaTelemetry.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); + if (telemetrySupplier instanceof Supplier) { + Object kafkaTelemetry = ((Supplier) telemetrySupplier).get(); + if (kafkaTelemetry instanceof KafkaTelemetry) { + this.telemetry = (KafkaTelemetry) kafkaTelemetry; + return; + } + } - // TODO: support experimental attributes config + // Fallback to GlobalOpenTelemetry if not configured + this.telemetry = + KafkaTelemetry.builder(GlobalOpenTelemetry.get()) + .setCapturedHeaders( + ConfigPropertiesUtil.getList( + "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) + .build(); } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java index 108b80635425..ec966499d63b 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java @@ -28,19 +28,36 @@ abstract class AbstractInterceptorsTest extends KafkaClientBaseTest { static final String greeting = "Hello Kafka!"; + private static KafkaTelemetry kafkaTelemetry; + + protected static KafkaTelemetry createKafkaTelemetry() { + return KafkaTelemetry.builder(testing.getOpenTelemetry()) + .setCapturedHeaders(java.util.Collections.singletonList("Test-Message-Header")) + .setMessagingReceiveInstrumentationEnabled(true) + .build(); + } + @Override public Map producerProps() { + if (kafkaTelemetry == null) { + kafkaTelemetry = createKafkaTelemetry(); + } Map props = super.producerProps(); props.put( ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); + props.putAll(kafkaTelemetry.producerInterceptorConfigProperties()); return props; } @Override public Map consumerProps() { + if (kafkaTelemetry == null) { + kafkaTelemetry = createKafkaTelemetry(); + } Map props = super.consumerProps(); props.put( ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); + props.putAll(kafkaTelemetry.consumerInterceptorConfigProperties()); return props; } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsSuppressReceiveSpansTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsSuppressReceiveSpansTest.java index 049020a2d074..33c87d4456df 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsSuppressReceiveSpansTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsSuppressReceiveSpansTest.java @@ -23,6 +23,12 @@ class InterceptorsSuppressReceiveSpansTest extends AbstractInterceptorsTest { + protected static KafkaTelemetry createKafkaTelemetry() { + return KafkaTelemetry.builder(testing.getOpenTelemetry()) + .setMessagingReceiveInstrumentationEnabled(false) + .build(); + } + @SuppressWarnings("deprecation") // using deprecated semconv @Override void assertTraces() { diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaTelemetrySupplier.java b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaTelemetrySupplier.java new file mode 100644 index 000000000000..38bf8bb592b7 --- /dev/null +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaTelemetrySupplier.java @@ -0,0 +1,37 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal; + +import java.io.Serializable; +import java.util.Objects; +import java.util.function.Supplier; + +/** + * Wrapper for KafkaTelemetry that can be injected into kafka configuration without breaking + * serialization. Used by kafka interceptors to get a configured KafkaTelemetry instance. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class KafkaTelemetrySupplier implements Supplier, Serializable { + private static final long serialVersionUID = 1L; + private final transient Object kafkaTelemetry; + + public KafkaTelemetrySupplier(Object kafkaTelemetry) { + Objects.requireNonNull(kafkaTelemetry); + this.kafkaTelemetry = kafkaTelemetry; + } + + @Override + public Object get() { + return kafkaTelemetry; + } + + private Object writeReplace() { + // serialize this object to null + return null; + } +} From ce0eafc8694385efae8874e617075aedbc649279 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 01:29:31 +0000 Subject: [PATCH 04/21] Update README with interceptor configuration documentation Co-authored-by: trask <218610+trask@users.noreply.github.com> --- .../kafka-clients-2.6/library/README.md | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md index 2d4019de6fd1..33592ea49323 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md @@ -35,6 +35,49 @@ The Kafka clients API provides a way to "intercept" messages before they are sen The OpenTelemetry instrumented Kafka library provides two interceptors to be configured to add tracing information automatically. The interceptor class has to be set in the properties bag used to create the Kafka client. +##### Recommended approach: Configuring interceptors with KafkaTelemetry + +The recommended way to use interceptors is to configure them with a `KafkaTelemetry` instance. +This gives you full control over the configuration, including which `OpenTelemetry` instance to use, +whether to enable receive telemetry, and which headers to capture. + +For the producer, configure the interceptor with the KafkaTelemetry instance: + +```java +KafkaTelemetry telemetry = KafkaTelemetry.builder(openTelemetry) + .setCapturedHeaders(Arrays.asList("custom-header")) + .build(); + +Map props = new HashMap<>(); +props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); +props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); +props.putAll(telemetry.producerInterceptorConfigProperties()); + +Producer producer = new KafkaProducer<>(props); +``` + +For the consumer, configure the interceptor with the KafkaTelemetry instance: + +```java +KafkaTelemetry telemetry = KafkaTelemetry.builder(openTelemetry) + .setMessagingReceiveInstrumentationEnabled(true) + .setCapturedHeaders(Arrays.asList("custom-header")) + .build(); + +Map props = new HashMap<>(); +props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); +props.put(ConsumerConfig.GROUP_ID_CONFIG, "my-group"); +props.put(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); +props.putAll(telemetry.consumerInterceptorConfigProperties()); + +Consumer consumer = new KafkaConsumer<>(props); +``` + +##### Alternative: Using interceptors with global OpenTelemetry + +If you don't explicitly configure the interceptors with a `KafkaTelemetry` instance, they will fall back to using +`GlobalOpenTelemetry.get()` and system properties for configuration. + Use the `TracingProducerInterceptor` for the producer in order to create a "send" span automatically, each time a message is sent. ```java @@ -47,6 +90,10 @@ Use the `TracingConsumerInterceptor` for the consumer in order to create a "rece props.setProperty(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); ``` +The interceptors will use the following system properties for configuration: +- `otel.instrumentation.messaging.experimental.receive-telemetry.enabled` - Enable receive telemetry (default: false) +- `otel.instrumentation.messaging.experimental.capture-headers` - List of headers to capture as span attributes + #### Wrapping clients The other way is by wrapping the Kafka client with a tracing enabled Kafka client. From b1877912a7c1caffd172909ab7bff9ce3135d0f4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 02:50:34 +0000 Subject: [PATCH 05/21] Follow OpenTelemetryMetricsReporter pattern for property validation Co-authored-by: trask <218610+trask@users.noreply.github.com> --- .../v2_6/TracingConsumerInterceptor.java | 46 ++++++++++++------- .../v2_6/TracingProducerInterceptor.java | 39 ++++++++++------ 2 files changed, 54 insertions(+), 31 deletions(-) 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 48744d8a5ce7..e4424f5fbfdc 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 @@ -63,25 +63,37 @@ public void configure(Map configs) { consumerGroup = Objects.toString(configs.get(ConsumerConfig.GROUP_ID_CONFIG), null); clientId = Objects.toString(configs.get(ConsumerConfig.CLIENT_ID_CONFIG), null); - // Try to get KafkaTelemetry from config Object telemetrySupplier = configs.get(KafkaTelemetry.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); - if (telemetrySupplier instanceof Supplier) { - Object kafkaTelemetry = ((Supplier) telemetrySupplier).get(); - if (kafkaTelemetry instanceof KafkaTelemetry) { - this.telemetry = (KafkaTelemetry) kafkaTelemetry; - return; - } + if (telemetrySupplier == null) { + // Fallback to GlobalOpenTelemetry if not configured + this.telemetry = + KafkaTelemetry.builder(GlobalOpenTelemetry.get()) + .setMessagingReceiveInstrumentationEnabled( + ConfigPropertiesUtil.getBoolean( + "otel.instrumentation.messaging.experimental.receive-telemetry.enabled", + false)) + .setCapturedHeaders( + ConfigPropertiesUtil.getList( + "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) + .build(); + return; } - // Fallback to GlobalOpenTelemetry if not configured - this.telemetry = - KafkaTelemetry.builder(GlobalOpenTelemetry.get()) - .setMessagingReceiveInstrumentationEnabled( - ConfigPropertiesUtil.getBoolean( - "otel.instrumentation.messaging.experimental.receive-telemetry.enabled", false)) - .setCapturedHeaders( - ConfigPropertiesUtil.getList( - "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) - .build(); + if (!(telemetrySupplier instanceof Supplier)) { + throw new IllegalStateException( + "Configuration property " + + KafkaTelemetry.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + + " is not instance of Supplier"); + } + + Object kafkaTelemetry = ((Supplier) telemetrySupplier).get(); + if (!(kafkaTelemetry instanceof KafkaTelemetry)) { + throw new IllegalStateException( + "Configuration property " + + KafkaTelemetry.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + + " supplier does not return KafkaTelemetry instance"); + } + + this.telemetry = (KafkaTelemetry) kafkaTelemetry; } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java index 921e605ce687..7f2938456c1b 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java @@ -48,22 +48,33 @@ public void close() {} public void configure(Map configs) { clientId = Objects.toString(configs.get(ProducerConfig.CLIENT_ID_CONFIG), null); - // Try to get KafkaTelemetry from config Object telemetrySupplier = configs.get(KafkaTelemetry.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); - if (telemetrySupplier instanceof Supplier) { - Object kafkaTelemetry = ((Supplier) telemetrySupplier).get(); - if (kafkaTelemetry instanceof KafkaTelemetry) { - this.telemetry = (KafkaTelemetry) kafkaTelemetry; - return; - } + if (telemetrySupplier == null) { + // Fallback to GlobalOpenTelemetry if not configured + this.telemetry = + KafkaTelemetry.builder(GlobalOpenTelemetry.get()) + .setCapturedHeaders( + ConfigPropertiesUtil.getList( + "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) + .build(); + return; } - // Fallback to GlobalOpenTelemetry if not configured - this.telemetry = - KafkaTelemetry.builder(GlobalOpenTelemetry.get()) - .setCapturedHeaders( - ConfigPropertiesUtil.getList( - "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) - .build(); + if (!(telemetrySupplier instanceof Supplier)) { + throw new IllegalStateException( + "Configuration property " + + KafkaTelemetry.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + + " is not instance of Supplier"); + } + + Object kafkaTelemetry = ((Supplier) telemetrySupplier).get(); + if (!(kafkaTelemetry instanceof KafkaTelemetry)) { + throw new IllegalStateException( + "Configuration property " + + KafkaTelemetry.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + + " supplier does not return KafkaTelemetry instance"); + } + + this.telemetry = (KafkaTelemetry) kafkaTelemetry; } } From 85ec08993c8753241ca75e1bdcead6353fbb2234 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 03:23:37 +0000 Subject: [PATCH 06/21] Avoid changing KafkaTelemetry - move config keys to interceptors Co-authored-by: trask <218610+trask@users.noreply.github.com> --- .../kafka-clients-2.6/library/README.md | 6 +- .../kafkaclients/v2_6/KafkaTelemetry.java | 56 ------------------- .../v2_6/TracingConsumerInterceptor.java | 9 ++- .../v2_6/TracingProducerInterceptor.java | 9 ++- .../v2_6/AbstractInterceptorsTest.java | 12 +++- 5 files changed, 26 insertions(+), 66 deletions(-) diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md index 33592ea49323..db0872280c72 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md @@ -51,7 +51,8 @@ KafkaTelemetry telemetry = KafkaTelemetry.builder(openTelemetry) Map props = new HashMap<>(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); -props.putAll(telemetry.producerInterceptorConfigProperties()); +props.put(TracingProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, + new io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaTelemetrySupplier(telemetry)); Producer producer = new KafkaProducer<>(props); ``` @@ -68,7 +69,8 @@ Map props = new HashMap<>(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); props.put(ConsumerConfig.GROUP_ID_CONFIG, "my-group"); props.put(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); -props.putAll(telemetry.consumerInterceptorConfigProperties()); +props.put(TracingConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, + new io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaTelemetrySupplier(telemetry)); Consumer consumer = new KafkaConsumer<>(props); ``` 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 3e285da18f2d..d66003f6607f 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 @@ -21,7 +21,6 @@ import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaProcessRequest; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaProducerRequest; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaReceiveRequest; -import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaTelemetrySupplier; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaUtil; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.MetricsReporterList; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.OpenTelemetryMetricsReporter; @@ -208,61 +207,6 @@ ConsumerRecords addTracing( return Collections.unmodifiableMap(config); } - /** - * Returns a config property key that can be used to pass this {@link KafkaTelemetry} instance to - * interceptors. - * - *

This is an internal config key used by the library instrumentation interceptors. - */ - static final String CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER = - "opentelemetry.experimental.kafka-telemetry.supplier"; - - /** - * Produces a set of kafka producer config properties to configure tracing in {@code - * TracingProducerInterceptor}. Add these resulting properties to the configuration map used to - * initialize a {@link KafkaProducer}. - * - *

Example usage: - * - *

{@code
-   * //    Map config = new HashMap<>();
-   * //    config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, ...);
-   * //    config.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName());
-   * //    config.putAll(kafkaTelemetry.producerInterceptorConfigProperties());
-   * //    try (KafkaProducer producer = new KafkaProducer<>(config)) { ... }
-   * }
- * - * @return the kafka producer interceptor config properties - */ - public Map producerInterceptorConfigProperties() { - Map config = new HashMap<>(); - config.put(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, new KafkaTelemetrySupplier(this)); - return Collections.unmodifiableMap(config); - } - - /** - * Produces a set of kafka consumer config properties to configure tracing in {@code - * TracingConsumerInterceptor}. Add these resulting properties to the configuration map used to - * initialize a {@link KafkaConsumer}. - * - *

Example usage: - * - *

{@code
-   * //    Map config = new HashMap<>();
-   * //    config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, ...);
-   * //    config.put(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName());
-   * //    config.putAll(kafkaTelemetry.consumerInterceptorConfigProperties());
-   * //    try (KafkaConsumer consumer = new KafkaConsumer<>(config)) { ... }
-   * }
- * - * @return the kafka consumer interceptor config properties - */ - public Map consumerInterceptorConfigProperties() { - Map config = new HashMap<>(); - config.put(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, new KafkaTelemetrySupplier(this)); - return Collections.unmodifiableMap(config); - } - /** * Build and inject span into record. * 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 e4424f5fbfdc..aa306b00f9f1 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 @@ -31,6 +31,9 @@ */ public class TracingConsumerInterceptor implements ConsumerInterceptor { + public static final String CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER = + "opentelemetry.experimental.kafka-telemetry.supplier"; + @Nullable private KafkaTelemetry telemetry; private String consumerGroup; private String clientId; @@ -63,7 +66,7 @@ public void configure(Map configs) { consumerGroup = Objects.toString(configs.get(ConsumerConfig.GROUP_ID_CONFIG), null); clientId = Objects.toString(configs.get(ConsumerConfig.CLIENT_ID_CONFIG), null); - Object telemetrySupplier = configs.get(KafkaTelemetry.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); + Object telemetrySupplier = configs.get(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); if (telemetrySupplier == null) { // Fallback to GlobalOpenTelemetry if not configured this.telemetry = @@ -82,7 +85,7 @@ public void configure(Map configs) { if (!(telemetrySupplier instanceof Supplier)) { throw new IllegalStateException( "Configuration property " - + KafkaTelemetry.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + " is not instance of Supplier"); } @@ -90,7 +93,7 @@ public void configure(Map configs) { if (!(kafkaTelemetry instanceof KafkaTelemetry)) { throw new IllegalStateException( "Configuration property " - + KafkaTelemetry.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + " supplier does not return KafkaTelemetry instance"); } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java index 7f2938456c1b..10acc76db6e2 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java @@ -26,6 +26,9 @@ */ public class TracingProducerInterceptor implements ProducerInterceptor { + public static final String CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER = + "opentelemetry.experimental.kafka-telemetry.supplier"; + @Nullable private KafkaTelemetry telemetry; @Nullable private String clientId; @@ -48,7 +51,7 @@ public void close() {} public void configure(Map configs) { clientId = Objects.toString(configs.get(ProducerConfig.CLIENT_ID_CONFIG), null); - Object telemetrySupplier = configs.get(KafkaTelemetry.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); + Object telemetrySupplier = configs.get(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); if (telemetrySupplier == null) { // Fallback to GlobalOpenTelemetry if not configured this.telemetry = @@ -63,7 +66,7 @@ public void configure(Map configs) { if (!(telemetrySupplier instanceof Supplier)) { throw new IllegalStateException( "Configuration property " - + KafkaTelemetry.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + " is not instance of Supplier"); } @@ -71,7 +74,7 @@ public void configure(Map configs) { if (!(kafkaTelemetry instanceof KafkaTelemetry)) { throw new IllegalStateException( "Configuration property " - + KafkaTelemetry.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + " supplier does not return KafkaTelemetry instance"); } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java index ec966499d63b..da11f91bbc88 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java @@ -45,7 +45,11 @@ public Map producerProps() { Map props = super.producerProps(); props.put( ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); - props.putAll(kafkaTelemetry.producerInterceptorConfigProperties()); + props.put( + TracingProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, + new io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal + .KafkaTelemetrySupplier( + kafkaTelemetry)); return props; } @@ -57,7 +61,11 @@ public Map consumerProps() { Map props = super.consumerProps(); props.put( ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); - props.putAll(kafkaTelemetry.consumerInterceptorConfigProperties()); + props.put( + TracingConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, + new io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal + .KafkaTelemetrySupplier( + kafkaTelemetry)); return props; } From 5f6294bfcbc3b154005ae77a3500ed6925d7d60d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 03:50:43 +0000 Subject: [PATCH 07/21] Use OpenTelemetrySupplier instead of KafkaTelemetrySupplier Co-authored-by: trask <218610+trask@users.noreply.github.com> --- .../kafka-clients-2.6/library/README.md | 28 +++------- .../v2_6/TracingConsumerInterceptor.java | 56 ++++++++----------- .../v2_6/TracingProducerInterceptor.java | 49 +++++++--------- .../v2_6/AbstractInterceptorsTest.java | 12 ++-- .../internal/KafkaTelemetrySupplier.java | 37 ------------ 5 files changed, 60 insertions(+), 122 deletions(-) delete mode 100644 instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaTelemetrySupplier.java diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md index db0872280c72..c539e6c73523 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md @@ -35,42 +35,32 @@ The Kafka clients API provides a way to "intercept" messages before they are sen The OpenTelemetry instrumented Kafka library provides two interceptors to be configured to add tracing information automatically. The interceptor class has to be set in the properties bag used to create the Kafka client. -##### Recommended approach: Configuring interceptors with KafkaTelemetry +##### Recommended approach: Configuring interceptors with OpenTelemetry -The recommended way to use interceptors is to configure them with a `KafkaTelemetry` instance. -This gives you full control over the configuration, including which `OpenTelemetry` instance to use, -whether to enable receive telemetry, and which headers to capture. +The recommended way to use interceptors is to configure them with an `OpenTelemetry` instance. +Interceptors will use system properties for additional configuration like captured headers and receive telemetry settings. -For the producer, configure the interceptor with the KafkaTelemetry instance: +For the producer: ```java -KafkaTelemetry telemetry = KafkaTelemetry.builder(openTelemetry) - .setCapturedHeaders(Arrays.asList("custom-header")) - .build(); - Map props = new HashMap<>(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); -props.put(TracingProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, - new io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaTelemetrySupplier(telemetry)); +props.put(TracingProducerInterceptor.CONFIG_KEY_OPENTELEMETRY_SUPPLIER, + new io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.OpenTelemetrySupplier(openTelemetry)); Producer producer = new KafkaProducer<>(props); ``` -For the consumer, configure the interceptor with the KafkaTelemetry instance: +For the consumer: ```java -KafkaTelemetry telemetry = KafkaTelemetry.builder(openTelemetry) - .setMessagingReceiveInstrumentationEnabled(true) - .setCapturedHeaders(Arrays.asList("custom-header")) - .build(); - Map props = new HashMap<>(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); props.put(ConsumerConfig.GROUP_ID_CONFIG, "my-group"); props.put(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); -props.put(TracingConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, - new io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaTelemetrySupplier(telemetry)); +props.put(TracingConsumerInterceptor.CONFIG_KEY_OPENTELEMETRY_SUPPLIER, + new io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.OpenTelemetrySupplier(openTelemetry)); Consumer consumer = new KafkaConsumer<>(props); ``` 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 aa306b00f9f1..ba6c8b860f68 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 @@ -9,14 +9,15 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil; import io.opentelemetry.instrumentation.api.internal.Timer; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaConsumerContext; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaConsumerContextUtil; +import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.OpenTelemetrySupplier; import java.util.Map; import java.util.Objects; -import java.util.function.Supplier; import javax.annotation.Nullable; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerInterceptor; @@ -31,8 +32,7 @@ */ public class TracingConsumerInterceptor implements ConsumerInterceptor { - public static final String CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER = - "opentelemetry.experimental.kafka-telemetry.supplier"; + public static final String CONFIG_KEY_OPENTELEMETRY_SUPPLIER = "opentelemetry.supplier"; @Nullable private KafkaTelemetry telemetry; private String consumerGroup; @@ -66,37 +66,29 @@ public void configure(Map configs) { consumerGroup = Objects.toString(configs.get(ConsumerConfig.GROUP_ID_CONFIG), null); clientId = Objects.toString(configs.get(ConsumerConfig.CLIENT_ID_CONFIG), null); - Object telemetrySupplier = configs.get(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); - if (telemetrySupplier == null) { + OpenTelemetry openTelemetry; + Object openTelemetrySupplier = configs.get(CONFIG_KEY_OPENTELEMETRY_SUPPLIER); + if (openTelemetrySupplier == null) { // Fallback to GlobalOpenTelemetry if not configured - this.telemetry = - KafkaTelemetry.builder(GlobalOpenTelemetry.get()) - .setMessagingReceiveInstrumentationEnabled( - ConfigPropertiesUtil.getBoolean( - "otel.instrumentation.messaging.experimental.receive-telemetry.enabled", - false)) - .setCapturedHeaders( - ConfigPropertiesUtil.getList( - "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) - .build(); - return; + openTelemetry = GlobalOpenTelemetry.get(); + } else { + if (!(openTelemetrySupplier instanceof OpenTelemetrySupplier)) { + throw new IllegalStateException( + "Configuration property " + + CONFIG_KEY_OPENTELEMETRY_SUPPLIER + + " is not instance of OpenTelemetrySupplier"); + } + openTelemetry = ((OpenTelemetrySupplier) openTelemetrySupplier).get(); } - if (!(telemetrySupplier instanceof Supplier)) { - throw new IllegalStateException( - "Configuration property " - + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER - + " is not instance of Supplier"); - } - - Object kafkaTelemetry = ((Supplier) telemetrySupplier).get(); - if (!(kafkaTelemetry instanceof KafkaTelemetry)) { - throw new IllegalStateException( - "Configuration property " - + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER - + " supplier does not return KafkaTelemetry instance"); - } - - this.telemetry = (KafkaTelemetry) kafkaTelemetry; + this.telemetry = + KafkaTelemetry.builder(openTelemetry) + .setMessagingReceiveInstrumentationEnabled( + ConfigPropertiesUtil.getBoolean( + "otel.instrumentation.messaging.experimental.receive-telemetry.enabled", false)) + .setCapturedHeaders( + ConfigPropertiesUtil.getList( + "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) + .build(); } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java index 10acc76db6e2..477c884db111 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java @@ -9,10 +9,11 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil; +import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.OpenTelemetrySupplier; import java.util.Map; import java.util.Objects; -import java.util.function.Supplier; import javax.annotation.Nullable; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerInterceptor; @@ -26,8 +27,7 @@ */ public class TracingProducerInterceptor implements ProducerInterceptor { - public static final String CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER = - "opentelemetry.experimental.kafka-telemetry.supplier"; + public static final String CONFIG_KEY_OPENTELEMETRY_SUPPLIER = "opentelemetry.supplier"; @Nullable private KafkaTelemetry telemetry; @Nullable private String clientId; @@ -51,33 +51,26 @@ public void close() {} public void configure(Map configs) { clientId = Objects.toString(configs.get(ProducerConfig.CLIENT_ID_CONFIG), null); - Object telemetrySupplier = configs.get(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); - if (telemetrySupplier == null) { + OpenTelemetry openTelemetry; + Object openTelemetrySupplier = configs.get(CONFIG_KEY_OPENTELEMETRY_SUPPLIER); + if (openTelemetrySupplier == null) { // Fallback to GlobalOpenTelemetry if not configured - this.telemetry = - KafkaTelemetry.builder(GlobalOpenTelemetry.get()) - .setCapturedHeaders( - ConfigPropertiesUtil.getList( - "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) - .build(); - return; + openTelemetry = GlobalOpenTelemetry.get(); + } else { + if (!(openTelemetrySupplier instanceof OpenTelemetrySupplier)) { + throw new IllegalStateException( + "Configuration property " + + CONFIG_KEY_OPENTELEMETRY_SUPPLIER + + " is not instance of OpenTelemetrySupplier"); + } + openTelemetry = ((OpenTelemetrySupplier) openTelemetrySupplier).get(); } - if (!(telemetrySupplier instanceof Supplier)) { - throw new IllegalStateException( - "Configuration property " - + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER - + " is not instance of Supplier"); - } - - Object kafkaTelemetry = ((Supplier) telemetrySupplier).get(); - if (!(kafkaTelemetry instanceof KafkaTelemetry)) { - throw new IllegalStateException( - "Configuration property " - + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER - + " supplier does not return KafkaTelemetry instance"); - } - - this.telemetry = (KafkaTelemetry) kafkaTelemetry; + this.telemetry = + KafkaTelemetry.builder(openTelemetry) + .setCapturedHeaders( + ConfigPropertiesUtil.getList( + "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) + .build(); } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java index da11f91bbc88..eb5532305fc0 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java @@ -46,10 +46,10 @@ public Map producerProps() { props.put( ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); props.put( - TracingProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, + TracingProducerInterceptor.CONFIG_KEY_OPENTELEMETRY_SUPPLIER, new io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal - .KafkaTelemetrySupplier( - kafkaTelemetry)); + .OpenTelemetrySupplier( + testing.getOpenTelemetry())); return props; } @@ -62,10 +62,10 @@ public Map consumerProps() { props.put( ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); props.put( - TracingConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, + TracingConsumerInterceptor.CONFIG_KEY_OPENTELEMETRY_SUPPLIER, new io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal - .KafkaTelemetrySupplier( - kafkaTelemetry)); + .OpenTelemetrySupplier( + testing.getOpenTelemetry())); return props; } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaTelemetrySupplier.java b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaTelemetrySupplier.java deleted file mode 100644 index 38bf8bb592b7..000000000000 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaTelemetrySupplier.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal; - -import java.io.Serializable; -import java.util.Objects; -import java.util.function.Supplier; - -/** - * Wrapper for KafkaTelemetry that can be injected into kafka configuration without breaking - * serialization. Used by kafka interceptors to get a configured KafkaTelemetry instance. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -public final class KafkaTelemetrySupplier implements Supplier, Serializable { - private static final long serialVersionUID = 1L; - private final transient Object kafkaTelemetry; - - public KafkaTelemetrySupplier(Object kafkaTelemetry) { - Objects.requireNonNull(kafkaTelemetry); - this.kafkaTelemetry = kafkaTelemetry; - } - - @Override - public Object get() { - return kafkaTelemetry; - } - - private Object writeReplace() { - // serialize this object to null - return null; - } -} From e5ca107b2f119e5077f4e20ae3c30368cc66f194 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 04:16:12 +0000 Subject: [PATCH 08/21] Clean up unused code and use imports instead of FQCN Co-authored-by: trask <218610+trask@users.noreply.github.com> --- .../v2_6/AbstractInterceptorsTest.java | 24 +++---------------- .../InterceptorsSuppressReceiveSpansTest.java | 6 ----- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java index eb5532305fc0..295c618567fb 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java @@ -8,6 +8,7 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaClientBaseTest; +import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.OpenTelemetrySupplier; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import java.nio.charset.StandardCharsets; @@ -28,44 +29,25 @@ abstract class AbstractInterceptorsTest extends KafkaClientBaseTest { static final String greeting = "Hello Kafka!"; - private static KafkaTelemetry kafkaTelemetry; - - protected static KafkaTelemetry createKafkaTelemetry() { - return KafkaTelemetry.builder(testing.getOpenTelemetry()) - .setCapturedHeaders(java.util.Collections.singletonList("Test-Message-Header")) - .setMessagingReceiveInstrumentationEnabled(true) - .build(); - } - @Override public Map producerProps() { - if (kafkaTelemetry == null) { - kafkaTelemetry = createKafkaTelemetry(); - } Map props = super.producerProps(); props.put( ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); props.put( TracingProducerInterceptor.CONFIG_KEY_OPENTELEMETRY_SUPPLIER, - new io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal - .OpenTelemetrySupplier( - testing.getOpenTelemetry())); + new OpenTelemetrySupplier(testing.getOpenTelemetry())); return props; } @Override public Map consumerProps() { - if (kafkaTelemetry == null) { - kafkaTelemetry = createKafkaTelemetry(); - } Map props = super.consumerProps(); props.put( ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); props.put( TracingConsumerInterceptor.CONFIG_KEY_OPENTELEMETRY_SUPPLIER, - new io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal - .OpenTelemetrySupplier( - testing.getOpenTelemetry())); + new OpenTelemetrySupplier(testing.getOpenTelemetry())); return props; } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsSuppressReceiveSpansTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsSuppressReceiveSpansTest.java index 33c87d4456df..049020a2d074 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsSuppressReceiveSpansTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsSuppressReceiveSpansTest.java @@ -23,12 +23,6 @@ class InterceptorsSuppressReceiveSpansTest extends AbstractInterceptorsTest { - protected static KafkaTelemetry createKafkaTelemetry() { - return KafkaTelemetry.builder(testing.getOpenTelemetry()) - .setMessagingReceiveInstrumentationEnabled(false) - .build(); - } - @SuppressWarnings("deprecation") // using deprecated semconv @Override void assertTraces() { From e791fc8955084b308569c6b9f08b1d3d6002155c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 15:46:35 +0000 Subject: [PATCH 09/21] Add helper methods to KafkaTelemetry for interceptor config Co-authored-by: trask <218610+trask@users.noreply.github.com> --- .../kafka-clients-2.6/library/README.md | 14 ++--- .../kafkaclients/v2_6/KafkaTelemetry.java | 52 +++++++++++++++++++ .../v2_6/AbstractInterceptorsTest.java | 12 ++--- 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md index c539e6c73523..fa0dcf12aa5f 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md @@ -35,19 +35,20 @@ The Kafka clients API provides a way to "intercept" messages before they are sen The OpenTelemetry instrumented Kafka library provides two interceptors to be configured to add tracing information automatically. The interceptor class has to be set in the properties bag used to create the Kafka client. -##### Recommended approach: Configuring interceptors with OpenTelemetry +##### Recommended approach: Configuring interceptors with KafkaTelemetry -The recommended way to use interceptors is to configure them with an `OpenTelemetry` instance. +The recommended way to use interceptors is to configure them with a `KafkaTelemetry` instance. Interceptors will use system properties for additional configuration like captured headers and receive telemetry settings. For the producer: ```java +KafkaTelemetry telemetry = KafkaTelemetry.create(openTelemetry); + Map props = new HashMap<>(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); -props.put(TracingProducerInterceptor.CONFIG_KEY_OPENTELEMETRY_SUPPLIER, - new io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.OpenTelemetrySupplier(openTelemetry)); +props.putAll(telemetry.producerInterceptorConfigProperties()); Producer producer = new KafkaProducer<>(props); ``` @@ -55,12 +56,13 @@ Producer producer = new KafkaProducer<>(props); For the consumer: ```java +KafkaTelemetry telemetry = KafkaTelemetry.create(openTelemetry); + Map props = new HashMap<>(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); props.put(ConsumerConfig.GROUP_ID_CONFIG, "my-group"); props.put(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); -props.put(TracingConsumerInterceptor.CONFIG_KEY_OPENTELEMETRY_SUPPLIER, - new io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.OpenTelemetrySupplier(openTelemetry)); +props.putAll(telemetry.consumerInterceptorConfigProperties()); Consumer consumer = new KafkaConsumer<>(props); ``` 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 d66003f6607f..eafde11316b3 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 @@ -207,6 +207,58 @@ ConsumerRecords addTracing( return Collections.unmodifiableMap(config); } + /** + * Returns configuration properties that can be used to enable tracing via {@code + * TracingProducerInterceptor}. Add these resulting properties to the configuration map used to + * initialize a {@link org.apache.kafka.clients.producer.KafkaProducer}. + * + *

Example usage: + * + *

{@code
+   * //    KafkaTelemetry telemetry = KafkaTelemetry.create(openTelemetry);
+   * //    Map config = new HashMap<>();
+   * //    config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, ...);
+   * //    config.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName());
+   * //    config.putAll(telemetry.producerInterceptorConfigProperties());
+   * //    try (KafkaProducer producer = new KafkaProducer<>(config)) { ... }
+   * }
+ * + * @return the kafka producer interceptor config properties + */ + public Map producerInterceptorConfigProperties() { + Map config = new HashMap<>(); + config.put( + TracingProducerInterceptor.CONFIG_KEY_OPENTELEMETRY_SUPPLIER, + new OpenTelemetrySupplier(openTelemetry)); + return Collections.unmodifiableMap(config); + } + + /** + * Returns configuration properties that can be used to enable tracing via {@code + * TracingConsumerInterceptor}. Add these resulting properties to the configuration map used to + * initialize a {@link org.apache.kafka.clients.consumer.KafkaConsumer}. + * + *

Example usage: + * + *

{@code
+   * //    KafkaTelemetry telemetry = KafkaTelemetry.create(openTelemetry);
+   * //    Map config = new HashMap<>();
+   * //    config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, ...);
+   * //    config.put(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName());
+   * //    config.putAll(telemetry.consumerInterceptorConfigProperties());
+   * //    try (KafkaConsumer consumer = new KafkaConsumer<>(config)) { ... }
+   * }
+ * + * @return the kafka consumer interceptor config properties + */ + public Map consumerInterceptorConfigProperties() { + Map config = new HashMap<>(); + config.put( + TracingConsumerInterceptor.CONFIG_KEY_OPENTELEMETRY_SUPPLIER, + new OpenTelemetrySupplier(openTelemetry)); + return Collections.unmodifiableMap(config); + } + /** * Build and inject span into record. * diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java index 295c618567fb..9fb0cfc75bcd 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java @@ -8,7 +8,6 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaClientBaseTest; -import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.OpenTelemetrySupplier; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import java.nio.charset.StandardCharsets; @@ -29,14 +28,15 @@ abstract class AbstractInterceptorsTest extends KafkaClientBaseTest { static final String greeting = "Hello Kafka!"; + private static final KafkaTelemetry kafkaTelemetry = + KafkaTelemetry.create(testing.getOpenTelemetry()); + @Override public Map producerProps() { Map props = super.producerProps(); props.put( ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); - props.put( - TracingProducerInterceptor.CONFIG_KEY_OPENTELEMETRY_SUPPLIER, - new OpenTelemetrySupplier(testing.getOpenTelemetry())); + props.putAll(kafkaTelemetry.producerInterceptorConfigProperties()); return props; } @@ -45,9 +45,7 @@ public Map consumerProps() { Map props = super.consumerProps(); props.put( ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); - props.put( - TracingConsumerInterceptor.CONFIG_KEY_OPENTELEMETRY_SUPPLIER, - new OpenTelemetrySupplier(testing.getOpenTelemetry())); + props.putAll(kafkaTelemetry.consumerInterceptorConfigProperties()); return props; } From 97cbf9da22accca10b3b8e10a0ff7dbb6e05228f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:02:54 +0000 Subject: [PATCH 10/21] Include INTERCEPTOR_CLASSES_CONFIG in helper methods Co-authored-by: trask <218610+trask@users.noreply.github.com> --- .../kafka-clients/kafka-clients-2.6/library/README.md | 2 -- .../kafkaclients/v2_6/KafkaTelemetry.java | 10 ++++++++-- .../kafkaclients/v2_6/AbstractInterceptorsTest.java | 4 ---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md index fa0dcf12aa5f..dcca20a5bbf5 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md @@ -47,7 +47,6 @@ KafkaTelemetry telemetry = KafkaTelemetry.create(openTelemetry); Map props = new HashMap<>(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); -props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); props.putAll(telemetry.producerInterceptorConfigProperties()); Producer producer = new KafkaProducer<>(props); @@ -61,7 +60,6 @@ KafkaTelemetry telemetry = KafkaTelemetry.create(openTelemetry); Map props = new HashMap<>(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); props.put(ConsumerConfig.GROUP_ID_CONFIG, "my-group"); -props.put(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); props.putAll(telemetry.consumerInterceptorConfigProperties()); Consumer consumer = new KafkaConsumer<>(props); 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 eafde11316b3..f8d50a15cb6c 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 @@ -38,12 +38,14 @@ import java.util.logging.Logger; import org.apache.kafka.clients.CommonClientConfigs; import org.apache.kafka.clients.consumer.Consumer; +import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; import org.apache.kafka.clients.producer.Callback; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; +import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.RecordMetadata; import org.apache.kafka.common.TopicPartition; @@ -218,7 +220,6 @@ ConsumerRecords addTracing( * // KafkaTelemetry telemetry = KafkaTelemetry.create(openTelemetry); * // Map config = new HashMap<>(); * // config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, ...); - * // config.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); * // config.putAll(telemetry.producerInterceptorConfigProperties()); * // try (KafkaProducer producer = new KafkaProducer<>(config)) { ... } * } @@ -227,6 +228,9 @@ ConsumerRecords addTracing( */ public Map producerInterceptorConfigProperties() { Map config = new HashMap<>(); + config.put( + ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, + TracingProducerInterceptor.class.getName()); config.put( TracingProducerInterceptor.CONFIG_KEY_OPENTELEMETRY_SUPPLIER, new OpenTelemetrySupplier(openTelemetry)); @@ -244,7 +248,6 @@ ConsumerRecords addTracing( * // KafkaTelemetry telemetry = KafkaTelemetry.create(openTelemetry); * // Map config = new HashMap<>(); * // config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, ...); - * // config.put(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); * // config.putAll(telemetry.consumerInterceptorConfigProperties()); * // try (KafkaConsumer consumer = new KafkaConsumer<>(config)) { ... } * } @@ -253,6 +256,9 @@ ConsumerRecords addTracing( */ public Map consumerInterceptorConfigProperties() { Map config = new HashMap<>(); + config.put( + ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, + TracingConsumerInterceptor.class.getName()); config.put( TracingConsumerInterceptor.CONFIG_KEY_OPENTELEMETRY_SUPPLIER, new OpenTelemetrySupplier(openTelemetry)); diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java index 9fb0cfc75bcd..efdadd951fa9 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java @@ -34,8 +34,6 @@ abstract class AbstractInterceptorsTest extends KafkaClientBaseTest { @Override public Map producerProps() { Map props = super.producerProps(); - props.put( - ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); props.putAll(kafkaTelemetry.producerInterceptorConfigProperties()); return props; } @@ -43,8 +41,6 @@ public Map producerProps() { @Override public Map consumerProps() { Map props = super.consumerProps(); - props.put( - ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); props.putAll(kafkaTelemetry.consumerInterceptorConfigProperties()); return props; } From 4f3d30df5c6a903e6b897c587194b85aca564d53 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:20:43 +0000 Subject: [PATCH 11/21] Use KafkaTelemetrySupplier to pass exact KafkaTelemetry instance Co-authored-by: trask <218610+trask@users.noreply.github.com> --- .../kafkaclients/v2_6/KafkaTelemetry.java | 9 +-- .../v2_6/TracingConsumerInterceptor.java | 57 +++++++++++-------- .../v2_6/TracingProducerInterceptor.java | 50 +++++++++------- .../internal/KafkaTelemetrySupplier.java | 37 ++++++++++++ 4 files changed, 104 insertions(+), 49 deletions(-) create mode 100644 instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaTelemetrySupplier.java 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 f8d50a15cb6c..172ffdc6380d 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 @@ -21,6 +21,7 @@ import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaProcessRequest; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaProducerRequest; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaReceiveRequest; +import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaTelemetrySupplier; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaUtil; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.MetricsReporterList; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.OpenTelemetryMetricsReporter; @@ -232,8 +233,8 @@ ConsumerRecords addTracing( ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); config.put( - TracingProducerInterceptor.CONFIG_KEY_OPENTELEMETRY_SUPPLIER, - new OpenTelemetrySupplier(openTelemetry)); + TracingProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, + new KafkaTelemetrySupplier(this)); return Collections.unmodifiableMap(config); } @@ -260,8 +261,8 @@ ConsumerRecords addTracing( ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); config.put( - TracingConsumerInterceptor.CONFIG_KEY_OPENTELEMETRY_SUPPLIER, - new OpenTelemetrySupplier(openTelemetry)); + TracingConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, + new KafkaTelemetrySupplier(this)); return Collections.unmodifiableMap(config); } 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 ba6c8b860f68..2775e304335e 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 @@ -9,15 +9,15 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil; import io.opentelemetry.instrumentation.api.internal.Timer; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaConsumerContext; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaConsumerContextUtil; -import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.OpenTelemetrySupplier; +import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaTelemetrySupplier; import java.util.Map; import java.util.Objects; +import java.util.function.Supplier; import javax.annotation.Nullable; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerInterceptor; @@ -32,7 +32,8 @@ */ public class TracingConsumerInterceptor implements ConsumerInterceptor { - public static final String CONFIG_KEY_OPENTELEMETRY_SUPPLIER = "opentelemetry.supplier"; + public static final String CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER = + "opentelemetry.experimental.kafka-telemetry.supplier"; @Nullable private KafkaTelemetry telemetry; private String consumerGroup; @@ -66,29 +67,37 @@ public void configure(Map configs) { consumerGroup = Objects.toString(configs.get(ConsumerConfig.GROUP_ID_CONFIG), null); clientId = Objects.toString(configs.get(ConsumerConfig.CLIENT_ID_CONFIG), null); - OpenTelemetry openTelemetry; - Object openTelemetrySupplier = configs.get(CONFIG_KEY_OPENTELEMETRY_SUPPLIER); - if (openTelemetrySupplier == null) { + Object telemetrySupplier = configs.get(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); + if (telemetrySupplier == null) { // Fallback to GlobalOpenTelemetry if not configured - openTelemetry = GlobalOpenTelemetry.get(); - } else { - if (!(openTelemetrySupplier instanceof OpenTelemetrySupplier)) { - throw new IllegalStateException( - "Configuration property " - + CONFIG_KEY_OPENTELEMETRY_SUPPLIER - + " is not instance of OpenTelemetrySupplier"); - } - openTelemetry = ((OpenTelemetrySupplier) openTelemetrySupplier).get(); + this.telemetry = + KafkaTelemetry.builder(GlobalOpenTelemetry.get()) + .setMessagingReceiveInstrumentationEnabled( + ConfigPropertiesUtil.getBoolean( + "otel.instrumentation.messaging.experimental.receive-telemetry.enabled", + false)) + .setCapturedHeaders( + ConfigPropertiesUtil.getList( + "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) + .build(); + return; } - this.telemetry = - KafkaTelemetry.builder(openTelemetry) - .setMessagingReceiveInstrumentationEnabled( - ConfigPropertiesUtil.getBoolean( - "otel.instrumentation.messaging.experimental.receive-telemetry.enabled", false)) - .setCapturedHeaders( - ConfigPropertiesUtil.getList( - "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) - .build(); + if (!(telemetrySupplier instanceof Supplier)) { + throw new IllegalStateException( + "Configuration property " + + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + + " is not instance of Supplier"); + } + + Object kafkaTelemetry = ((Supplier) telemetrySupplier).get(); + if (!(kafkaTelemetry instanceof KafkaTelemetry)) { + throw new IllegalStateException( + "Configuration property " + + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + + " supplier does not return KafkaTelemetry instance"); + } + + this.telemetry = (KafkaTelemetry) kafkaTelemetry; } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java index 477c884db111..96e07494788d 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java @@ -9,11 +9,11 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil; -import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.OpenTelemetrySupplier; +import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaTelemetrySupplier; import java.util.Map; import java.util.Objects; +import java.util.function.Supplier; import javax.annotation.Nullable; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerInterceptor; @@ -27,7 +27,8 @@ */ public class TracingProducerInterceptor implements ProducerInterceptor { - public static final String CONFIG_KEY_OPENTELEMETRY_SUPPLIER = "opentelemetry.supplier"; + public static final String CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER = + "opentelemetry.experimental.kafka-telemetry.supplier"; @Nullable private KafkaTelemetry telemetry; @Nullable private String clientId; @@ -51,26 +52,33 @@ public void close() {} public void configure(Map configs) { clientId = Objects.toString(configs.get(ProducerConfig.CLIENT_ID_CONFIG), null); - OpenTelemetry openTelemetry; - Object openTelemetrySupplier = configs.get(CONFIG_KEY_OPENTELEMETRY_SUPPLIER); - if (openTelemetrySupplier == null) { + Object telemetrySupplier = configs.get(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); + if (telemetrySupplier == null) { // Fallback to GlobalOpenTelemetry if not configured - openTelemetry = GlobalOpenTelemetry.get(); - } else { - if (!(openTelemetrySupplier instanceof OpenTelemetrySupplier)) { - throw new IllegalStateException( - "Configuration property " - + CONFIG_KEY_OPENTELEMETRY_SUPPLIER - + " is not instance of OpenTelemetrySupplier"); - } - openTelemetry = ((OpenTelemetrySupplier) openTelemetrySupplier).get(); + this.telemetry = + KafkaTelemetry.builder(GlobalOpenTelemetry.get()) + .setCapturedHeaders( + ConfigPropertiesUtil.getList( + "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) + .build(); + return; } - this.telemetry = - KafkaTelemetry.builder(openTelemetry) - .setCapturedHeaders( - ConfigPropertiesUtil.getList( - "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) - .build(); + if (!(telemetrySupplier instanceof Supplier)) { + throw new IllegalStateException( + "Configuration property " + + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + + " is not instance of Supplier"); + } + + Object kafkaTelemetry = ((Supplier) telemetrySupplier).get(); + if (!(kafkaTelemetry instanceof KafkaTelemetry)) { + throw new IllegalStateException( + "Configuration property " + + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + + " supplier does not return KafkaTelemetry instance"); + } + + this.telemetry = (KafkaTelemetry) kafkaTelemetry; } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaTelemetrySupplier.java b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaTelemetrySupplier.java new file mode 100644 index 000000000000..28ba44cae8ae --- /dev/null +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaTelemetrySupplier.java @@ -0,0 +1,37 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal; + +import java.io.Serializable; +import java.util.Objects; +import java.util.function.Supplier; + +/** + * Wrapper for KafkaTelemetry that can be injected into kafka configuration without breaking + * serialization. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class KafkaTelemetrySupplier implements Supplier, Serializable { + private static final long serialVersionUID = 1L; + private final transient Object kafkaTelemetry; + + public KafkaTelemetrySupplier(Object kafkaTelemetry) { + Objects.requireNonNull(kafkaTelemetry); + this.kafkaTelemetry = kafkaTelemetry; + } + + @Override + public Object get() { + return kafkaTelemetry; + } + + private Object writeReplace() { + // serialize this object to null + return null; + } +} From a31210c170db5441c05aa8f37b378bf800f28c64 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:39:08 +0000 Subject: [PATCH 12/21] Remove experimental from config key, simplify fallback, add test Co-authored-by: trask <218610+trask@users.noreply.github.com> --- ...kotlin-compiler-3745061290931361383.salive | 0 ...kotlin-compiler-7174543796092179295.salive | 0 .../v2_6/TracingConsumerInterceptor.java | 16 +- .../v2_6/TracingProducerInterceptor.java | 12 +- .../v2_6/KafkaTelemetryInterceptorTest.java | 172 ++++++++++++++++++ 5 files changed, 176 insertions(+), 24 deletions(-) create mode 100644 conventions/.kotlin/sessions/kotlin-compiler-3745061290931361383.salive create mode 100644 gradle-plugins/.kotlin/sessions/kotlin-compiler-7174543796092179295.salive create mode 100644 instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryInterceptorTest.java diff --git a/conventions/.kotlin/sessions/kotlin-compiler-3745061290931361383.salive b/conventions/.kotlin/sessions/kotlin-compiler-3745061290931361383.salive new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/gradle-plugins/.kotlin/sessions/kotlin-compiler-7174543796092179295.salive b/gradle-plugins/.kotlin/sessions/kotlin-compiler-7174543796092179295.salive new file mode 100644 index 000000000000..e69de29bb2d1 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 2775e304335e..640e01285f66 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 @@ -5,12 +5,9 @@ package io.opentelemetry.instrumentation.kafkaclients.v2_6; -import static java.util.Collections.emptyList; - import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil; import io.opentelemetry.instrumentation.api.internal.Timer; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaConsumerContext; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaConsumerContextUtil; @@ -33,7 +30,7 @@ public class TracingConsumerInterceptor implements ConsumerInterceptor { public static final String CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER = - "opentelemetry.experimental.kafka-telemetry.supplier"; + "opentelemetry.kafka-telemetry.supplier"; @Nullable private KafkaTelemetry telemetry; private String consumerGroup; @@ -70,16 +67,7 @@ public void configure(Map configs) { Object telemetrySupplier = configs.get(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); if (telemetrySupplier == null) { // Fallback to GlobalOpenTelemetry if not configured - this.telemetry = - KafkaTelemetry.builder(GlobalOpenTelemetry.get()) - .setMessagingReceiveInstrumentationEnabled( - ConfigPropertiesUtil.getBoolean( - "otel.instrumentation.messaging.experimental.receive-telemetry.enabled", - false)) - .setCapturedHeaders( - ConfigPropertiesUtil.getList( - "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) - .build(); + this.telemetry = KafkaTelemetry.create(GlobalOpenTelemetry.get()); return; } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java index 96e07494788d..1a3a00070a48 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java @@ -5,11 +5,8 @@ package io.opentelemetry.instrumentation.kafkaclients.v2_6; -import static java.util.Collections.emptyList; - import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaTelemetrySupplier; import java.util.Map; import java.util.Objects; @@ -28,7 +25,7 @@ public class TracingProducerInterceptor implements ProducerInterceptor { public static final String CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER = - "opentelemetry.experimental.kafka-telemetry.supplier"; + "opentelemetry.kafka-telemetry.supplier"; @Nullable private KafkaTelemetry telemetry; @Nullable private String clientId; @@ -55,12 +52,7 @@ public void configure(Map configs) { Object telemetrySupplier = configs.get(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); if (telemetrySupplier == null) { // Fallback to GlobalOpenTelemetry if not configured - this.telemetry = - KafkaTelemetry.builder(GlobalOpenTelemetry.get()) - .setCapturedHeaders( - ConfigPropertiesUtil.getList( - "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) - .build(); + this.telemetry = KafkaTelemetry.create(GlobalOpenTelemetry.get()); return; } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryInterceptorTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryInterceptorTest.java new file mode 100644 index 000000000000..8395e022b34e --- /dev/null +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryInterceptorTest.java @@ -0,0 +1,172 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.kafkaclients.v2_6; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaTelemetrySupplier; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.util.HashMap; +import java.util.Map; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.common.serialization.StringSerializer; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class KafkaTelemetryInterceptorTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + private static Map producerConfig() { + Map config = new HashMap<>(); + config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); + config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); + config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); + config.putAll( + KafkaTelemetry.create(testing.getOpenTelemetry()).producerInterceptorConfigProperties()); + return config; + } + + private static Map consumerConfig() { + Map config = new HashMap<>(); + config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); + config.put(ConsumerConfig.GROUP_ID_CONFIG, "test"); + config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); + config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); + config.putAll( + KafkaTelemetry.create(testing.getOpenTelemetry()).consumerInterceptorConfigProperties()); + return config; + } + + @Test + void badProducerConfig() { + Assumptions.assumeFalse(Boolean.getBoolean("testLatestDeps")); + + // Bad config - wrong type for supplier + assertThatThrownBy( + () -> { + Map producerConfig = producerConfig(); + producerConfig.put( + TracingProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, "foo"); + new KafkaProducer<>(producerConfig).close(); + }) + .hasRootCauseInstanceOf(IllegalStateException.class) + .hasRootCauseMessage( + "Configuration property opentelemetry.kafka-telemetry.supplier is not instance of Supplier"); + + // Bad config - supplier returns wrong type + assertThatThrownBy( + () -> { + Map producerConfig = producerConfig(); + producerConfig.put( + TracingProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, + (java.util.function.Supplier) () -> "not a KafkaTelemetry"); + new KafkaProducer<>(producerConfig).close(); + }) + .hasRootCauseInstanceOf(IllegalStateException.class) + .hasRootCauseMessage( + "Configuration property opentelemetry.kafka-telemetry.supplier supplier does not return KafkaTelemetry instance"); + } + + @Test + void badConsumerConfig() { + Assumptions.assumeFalse(Boolean.getBoolean("testLatestDeps")); + + // Bad config - wrong type for supplier + assertThatThrownBy( + () -> { + Map consumerConfig = consumerConfig(); + consumerConfig.put( + TracingConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, "foo"); + new KafkaConsumer<>(consumerConfig).close(); + }) + .hasRootCauseInstanceOf(IllegalStateException.class) + .hasRootCauseMessage( + "Configuration property opentelemetry.kafka-telemetry.supplier is not instance of Supplier"); + + // Bad config - supplier returns wrong type + assertThatThrownBy( + () -> { + Map consumerConfig = consumerConfig(); + consumerConfig.put( + TracingConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, + (java.util.function.Supplier) () -> "not a KafkaTelemetry"); + new KafkaConsumer<>(consumerConfig).close(); + }) + .hasRootCauseInstanceOf(IllegalStateException.class) + .hasRootCauseMessage( + "Configuration property opentelemetry.kafka-telemetry.supplier supplier does not return KafkaTelemetry instance"); + } + + @Test + void serializableConfig() throws IOException, ClassNotFoundException { + testSerialize(producerConfig()); + testSerialize(consumerConfig()); + } + + @SuppressWarnings("unchecked") + private static void testSerialize(Map map) + throws IOException, ClassNotFoundException { + // Check that producer config has the supplier + Object producerSupplier = map.get(TracingProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); + Object consumerSupplier = map.get(TracingConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); + + KafkaTelemetrySupplier supplier = null; + if (producerSupplier instanceof KafkaTelemetrySupplier) { + supplier = (KafkaTelemetrySupplier) producerSupplier; + } else if (consumerSupplier instanceof KafkaTelemetrySupplier) { + supplier = (KafkaTelemetrySupplier) consumerSupplier; + } + + assertThat(supplier).isNotNull(); + assertThat(supplier.get()).isNotNull(); + + ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); + try (ObjectOutputStream outputStream = new ObjectOutputStream(byteOutputStream)) { + outputStream.writeObject(map); + } + + class CustomObjectInputStream extends ObjectInputStream { + CustomObjectInputStream(InputStream inputStream) throws IOException { + super(inputStream); + } + + @Override + protected Class resolveClass(ObjectStreamClass desc) + throws IOException, ClassNotFoundException { + if (desc.getName().startsWith("io.opentelemetry.")) { + throw new IllegalStateException( + "Serial form contains opentelemetry class " + desc.getName()); + } + return super.resolveClass(desc); + } + } + + try (ObjectInputStream inputStream = + new CustomObjectInputStream(new ByteArrayInputStream(byteOutputStream.toByteArray()))) { + Map result = (Map) inputStream.readObject(); + assertThat(result.get(TracingProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER)) + .isNull(); + assertThat(result.get(TracingConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER)) + .isNull(); + } + } +} From 84e69ad0543634152748265bb93990142b7d2ac6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:40:45 +0000 Subject: [PATCH 13/21] Refactor kafka-clients interceptors to be configurable Co-authored-by: trask <218610+trask@users.noreply.github.com> --- .../.kotlin/sessions/kotlin-compiler-3745061290931361383.salive | 0 .../.kotlin/sessions/kotlin-compiler-7174543796092179295.salive | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 conventions/.kotlin/sessions/kotlin-compiler-3745061290931361383.salive delete mode 100644 gradle-plugins/.kotlin/sessions/kotlin-compiler-7174543796092179295.salive diff --git a/conventions/.kotlin/sessions/kotlin-compiler-3745061290931361383.salive b/conventions/.kotlin/sessions/kotlin-compiler-3745061290931361383.salive deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/gradle-plugins/.kotlin/sessions/kotlin-compiler-7174543796092179295.salive b/gradle-plugins/.kotlin/sessions/kotlin-compiler-7174543796092179295.salive deleted file mode 100644 index e69de29bb2d1..000000000000 From 5419f0360dd794bfe7f286eaa449ce42ef6e985b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 17:18:46 +0000 Subject: [PATCH 14/21] Restore system property checks in fallback with TODO comment Co-authored-by: trask <218610+trask@users.noreply.github.com> --- .../v2_6/TracingConsumerInterceptor.java | 15 ++++++++++++++- .../v2_6/TracingProducerInterceptor.java | 11 ++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) 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 640e01285f66..b97ea86beb6a 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 @@ -5,9 +5,12 @@ package io.opentelemetry.instrumentation.kafkaclients.v2_6; +import static java.util.Collections.emptyList; + import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil; import io.opentelemetry.instrumentation.api.internal.Timer; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaConsumerContext; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaConsumerContextUtil; @@ -67,7 +70,17 @@ public void configure(Map configs) { Object telemetrySupplier = configs.get(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); if (telemetrySupplier == null) { // Fallback to GlobalOpenTelemetry if not configured - this.telemetry = KafkaTelemetry.create(GlobalOpenTelemetry.get()); + // TODO: remove these config properties checks once declarative configuration is available + this.telemetry = + KafkaTelemetry.builder(GlobalOpenTelemetry.get()) + .setMessagingReceiveInstrumentationEnabled( + ConfigPropertiesUtil.getBoolean( + "otel.instrumentation.messaging.experimental.receive-telemetry.enabled", + false)) + .setCapturedHeaders( + ConfigPropertiesUtil.getList( + "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) + .build(); return; } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java index 1a3a00070a48..4edee1ba6d86 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java @@ -5,8 +5,11 @@ package io.opentelemetry.instrumentation.kafkaclients.v2_6; +import static java.util.Collections.emptyList; + import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaTelemetrySupplier; import java.util.Map; import java.util.Objects; @@ -52,7 +55,13 @@ public void configure(Map configs) { Object telemetrySupplier = configs.get(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); if (telemetrySupplier == null) { // Fallback to GlobalOpenTelemetry if not configured - this.telemetry = KafkaTelemetry.create(GlobalOpenTelemetry.get()); + // TODO: remove these config properties checks once declarative configuration is available + this.telemetry = + KafkaTelemetry.builder(GlobalOpenTelemetry.get()) + .setCapturedHeaders( + ConfigPropertiesUtil.getList( + "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) + .build(); return; } From f4e568afb261e8576d66bd58edb9b405392b7712 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 17:23:46 +0000 Subject: [PATCH 15/21] Use import instead of FQCN for Supplier in test Co-authored-by: trask <218610+trask@users.noreply.github.com> --- .../kafkaclients/v2_6/KafkaTelemetryInterceptorTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryInterceptorTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryInterceptorTest.java index 8395e022b34e..21b92d72827e 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryInterceptorTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryInterceptorTest.java @@ -20,6 +20,7 @@ import java.io.ObjectStreamClass; import java.util.HashMap; import java.util.Map; +import java.util.function.Supplier; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.KafkaConsumer; import org.apache.kafka.clients.producer.KafkaProducer; @@ -78,7 +79,7 @@ void badProducerConfig() { Map producerConfig = producerConfig(); producerConfig.put( TracingProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, - (java.util.function.Supplier) () -> "not a KafkaTelemetry"); + (Supplier) () -> "not a KafkaTelemetry"); new KafkaProducer<>(producerConfig).close(); }) .hasRootCauseInstanceOf(IllegalStateException.class) @@ -108,7 +109,7 @@ void badConsumerConfig() { Map consumerConfig = consumerConfig(); consumerConfig.put( TracingConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, - (java.util.function.Supplier) () -> "not a KafkaTelemetry"); + (Supplier) () -> "not a KafkaTelemetry"); new KafkaConsumer<>(consumerConfig).close(); }) .hasRootCauseInstanceOf(IllegalStateException.class) From 82248c4c5c8a1c9a71924c99ab92b0442966e76e Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 3 Oct 2025 10:42:43 -0700 Subject: [PATCH 16/21] Apply suggestions from code review --- .../kafkaclients/v2_6/TracingConsumerInterceptor.java | 2 +- .../kafkaclients/v2_6/TracingProducerInterceptor.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 b97ea86beb6a..807d543f7fb0 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 @@ -70,9 +70,9 @@ public void configure(Map configs) { Object telemetrySupplier = configs.get(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); if (telemetrySupplier == null) { // Fallback to GlobalOpenTelemetry if not configured - // TODO: remove these config properties checks once declarative configuration is available this.telemetry = KafkaTelemetry.builder(GlobalOpenTelemetry.get()) + // TODO: remove now that programmatic configuration is available .setMessagingReceiveInstrumentationEnabled( ConfigPropertiesUtil.getBoolean( "otel.instrumentation.messaging.experimental.receive-telemetry.enabled", diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java index 4edee1ba6d86..c32dbef0ebcf 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java @@ -55,9 +55,9 @@ public void configure(Map configs) { Object telemetrySupplier = configs.get(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); if (telemetrySupplier == null) { // Fallback to GlobalOpenTelemetry if not configured - // TODO: remove these config properties checks once declarative configuration is available this.telemetry = KafkaTelemetry.builder(GlobalOpenTelemetry.get()) + // TODO: remove now that programmatic configuration is available .setCapturedHeaders( ConfigPropertiesUtil.getList( "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) From f1e7ddeaf36d529ac5ddeae4e5fcec56ef1fc707 Mon Sep 17 00:00:00 2001 From: otelbot <197425009+otelbot@users.noreply.github.com> Date: Fri, 3 Oct 2025 17:52:14 +0000 Subject: [PATCH 17/21] ./gradlew spotlessApply --- .../kafkaclients/v2_6/KafkaTelemetry.java | 6 ++---- .../v2_6/TracingConsumerInterceptor.java | 1 - .../v2_6/TracingProducerInterceptor.java | 1 - .../kafkaclients/v2_6/AbstractInterceptorsTest.java | 2 -- .../v2_6/KafkaTelemetryInterceptorTest.java | 12 +++++++----- 5 files changed, 9 insertions(+), 13 deletions(-) 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 172ffdc6380d..a58e5a42ffd5 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 @@ -230,8 +230,7 @@ ConsumerRecords addTracing( public Map producerInterceptorConfigProperties() { Map config = new HashMap<>(); config.put( - ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, - TracingProducerInterceptor.class.getName()); + ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); config.put( TracingProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, new KafkaTelemetrySupplier(this)); @@ -258,8 +257,7 @@ ConsumerRecords addTracing( public Map consumerInterceptorConfigProperties() { Map config = new HashMap<>(); config.put( - ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, - TracingConsumerInterceptor.class.getName()); + ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); config.put( TracingConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, new KafkaTelemetrySupplier(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 807d543f7fb0..124a2467c744 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 @@ -14,7 +14,6 @@ import io.opentelemetry.instrumentation.api.internal.Timer; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaConsumerContext; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaConsumerContextUtil; -import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaTelemetrySupplier; import java.util.Map; import java.util.Objects; import java.util.function.Supplier; diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java index c32dbef0ebcf..006943ed5306 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java @@ -10,7 +10,6 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil; -import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaTelemetrySupplier; import java.util.Map; import java.util.Objects; import java.util.function.Supplier; diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java index efdadd951fa9..2c3f87b2acd1 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/AbstractInterceptorsTest.java @@ -13,10 +13,8 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Map; -import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; -import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerRecord; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryInterceptorTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryInterceptorTest.java index 21b92d72827e..d870a9e133ea 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryInterceptorTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryInterceptorTest.java @@ -127,19 +127,21 @@ void serializableConfig() throws IOException, ClassNotFoundException { private static void testSerialize(Map map) throws IOException, ClassNotFoundException { // Check that producer config has the supplier - Object producerSupplier = map.get(TracingProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); - Object consumerSupplier = map.get(TracingConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); - + Object producerSupplier = + map.get(TracingProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); + Object consumerSupplier = + map.get(TracingConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); + KafkaTelemetrySupplier supplier = null; if (producerSupplier instanceof KafkaTelemetrySupplier) { supplier = (KafkaTelemetrySupplier) producerSupplier; } else if (consumerSupplier instanceof KafkaTelemetrySupplier) { supplier = (KafkaTelemetrySupplier) consumerSupplier; } - + assertThat(supplier).isNotNull(); assertThat(supplier.get()).isNotNull(); - + ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); try (ObjectOutputStream outputStream = new ObjectOutputStream(byteOutputStream)) { outputStream.writeObject(map); From 7703b1d2704109b94e0b12afcabcffe473f1377a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Oct 2025 03:50:27 +0000 Subject: [PATCH 18/21] Create new OpenTelemetry interceptors, keep old Tracing ones for backwards compatibility, move KafkaTelemetrySupplier Co-authored-by: trask <218610+trask@users.noreply.github.com> --- .../kafka-clients-2.6/library/README.md | 10 +- .../kafkaclients/v2_6/KafkaTelemetry.java | 23 ++--- .../v2_6}/KafkaTelemetrySupplier.java | 6 +- .../OpenTelemetryConsumerInterceptor.java | 92 +++++++++++++++++++ .../OpenTelemetryProducerInterceptor.java | 77 ++++++++++++++++ .../v2_6/TracingConsumerInterceptor.java | 51 ++-------- .../v2_6/TracingProducerInterceptor.java | 48 ++-------- .../v2_6/KafkaTelemetryInterceptorTest.java | 18 ++-- 8 files changed, 219 insertions(+), 106 deletions(-) rename instrumentation/kafka/kafka-clients/{kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal => kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6}/KafkaTelemetrySupplier.java (77%) create mode 100644 instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/OpenTelemetryConsumerInterceptor.java create mode 100644 instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/OpenTelemetryProducerInterceptor.java diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md index dcca20a5bbf5..22480c034082 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md @@ -70,18 +70,20 @@ Consumer consumer = new KafkaConsumer<>(props); If you don't explicitly configure the interceptors with a `KafkaTelemetry` instance, they will fall back to using `GlobalOpenTelemetry.get()` and system properties for configuration. -Use the `TracingProducerInterceptor` for the producer in order to create a "send" span automatically, each time a message is sent. +Use the `OpenTelemetryProducerInterceptor` for the producer in order to create a "send" span automatically, each time a message is sent. ```java -props.setProperty(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); +props.setProperty(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, OpenTelemetryProducerInterceptor.class.getName()); ``` -Use the `TracingConsumerInterceptor` for the consumer in order to create a "receive" span automatically, each time a message is received. +Use the `OpenTelemetryConsumerInterceptor` for the consumer in order to create a "receive" span automatically, each time a message is received. ```java -props.setProperty(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); +props.setProperty(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, OpenTelemetryConsumerInterceptor.class.getName()); ``` +Note: The `TracingProducerInterceptor` and `TracingConsumerInterceptor` classes are still available for backwards compatibility, but new code should use the `OpenTelemetry*` variants. + The interceptors will use the following system properties for configuration: - `otel.instrumentation.messaging.experimental.receive-telemetry.enabled` - Enable receive telemetry (default: false) - `otel.instrumentation.messaging.experimental.capture-headers` - List of headers to capture as span attributes 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 a58e5a42ffd5..77f91e110afa 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 @@ -21,7 +21,6 @@ import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaProcessRequest; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaProducerRequest; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaReceiveRequest; -import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaTelemetrySupplier; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaUtil; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.MetricsReporterList; import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.OpenTelemetryMetricsReporter; @@ -211,9 +210,9 @@ ConsumerRecords addTracing( } /** - * Returns configuration properties that can be used to enable tracing via {@code - * TracingProducerInterceptor}. Add these resulting properties to the configuration map used to - * initialize a {@link org.apache.kafka.clients.producer.KafkaProducer}. + * Returns configuration properties that can be used to enable OpenTelemetry instrumentation via + * {@code OpenTelemetryProducerInterceptor}. Add these resulting properties to the configuration + * map used to initialize a {@link org.apache.kafka.clients.producer.KafkaProducer}. * *

Example usage: * @@ -230,17 +229,18 @@ ConsumerRecords addTracing( public Map producerInterceptorConfigProperties() { Map config = new HashMap<>(); config.put( - ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingProducerInterceptor.class.getName()); + ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, + OpenTelemetryProducerInterceptor.class.getName()); config.put( - TracingProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, + OpenTelemetryProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, new KafkaTelemetrySupplier(this)); return Collections.unmodifiableMap(config); } /** - * Returns configuration properties that can be used to enable tracing via {@code - * TracingConsumerInterceptor}. Add these resulting properties to the configuration map used to - * initialize a {@link org.apache.kafka.clients.consumer.KafkaConsumer}. + * Returns configuration properties that can be used to enable OpenTelemetry instrumentation via + * {@code OpenTelemetryConsumerInterceptor}. Add these resulting properties to the configuration + * map used to initialize a {@link org.apache.kafka.clients.consumer.KafkaConsumer}. * *

Example usage: * @@ -257,9 +257,10 @@ ConsumerRecords addTracing( public Map consumerInterceptorConfigProperties() { Map config = new HashMap<>(); config.put( - ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, TracingConsumerInterceptor.class.getName()); + ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, + OpenTelemetryConsumerInterceptor.class.getName()); config.put( - TracingConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, + OpenTelemetryConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, new KafkaTelemetrySupplier(this)); return Collections.unmodifiableMap(config); } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaTelemetrySupplier.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetrySupplier.java similarity index 77% rename from instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaTelemetrySupplier.java rename to instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetrySupplier.java index 28ba44cae8ae..9d87a3b1af21 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common-0.11/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/common/v0_11/internal/KafkaTelemetrySupplier.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetrySupplier.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal; +package io.opentelemetry.instrumentation.kafkaclients.v2_6; import java.io.Serializable; import java.util.Objects; @@ -16,11 +16,11 @@ *

This class is internal and is hence not for public use. Its APIs are unstable and can change * at any time. */ -public final class KafkaTelemetrySupplier implements Supplier, Serializable { +final class KafkaTelemetrySupplier implements Supplier, Serializable { private static final long serialVersionUID = 1L; private final transient Object kafkaTelemetry; - public KafkaTelemetrySupplier(Object kafkaTelemetry) { + KafkaTelemetrySupplier(Object kafkaTelemetry) { Objects.requireNonNull(kafkaTelemetry); this.kafkaTelemetry = kafkaTelemetry; } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/OpenTelemetryConsumerInterceptor.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/OpenTelemetryConsumerInterceptor.java new file mode 100644 index 000000000000..5a98f18f71d7 --- /dev/null +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/OpenTelemetryConsumerInterceptor.java @@ -0,0 +1,92 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.kafkaclients.v2_6; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.internal.Timer; +import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaConsumerContext; +import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaConsumerContextUtil; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; +import javax.annotation.Nullable; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.consumer.ConsumerInterceptor; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.consumer.OffsetAndMetadata; +import org.apache.kafka.common.TopicPartition; + +/** + * A ConsumerInterceptor that adds OpenTelemetry instrumentation. Add this interceptor's class name + * or class via ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG property to your Consumer's properties to + * get it instantiated and used. See more details on ConsumerInterceptor usage in its Javadoc. + * + *

To configure the interceptor, use {@link KafkaTelemetry#consumerInterceptorConfigProperties} + * to obtain the configuration properties and add them to your consumer configuration. + * + * @see KafkaTelemetry#consumerInterceptorConfigProperties() + */ +public class OpenTelemetryConsumerInterceptor implements ConsumerInterceptor { + + public static final String CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER = + "opentelemetry.kafka-telemetry.supplier"; + + @Nullable private KafkaTelemetry telemetry; + private String consumerGroup; + private String clientId; + + @Override + @CanIgnoreReturnValue + public ConsumerRecords onConsume(ConsumerRecords records) { + if (telemetry == null) { + return records; + } + // timer should be started before fetching ConsumerRecords, but there is no callback for that + Timer timer = Timer.start(); + Context receiveContext = telemetry.buildAndFinishSpan(records, consumerGroup, clientId, timer); + if (receiveContext == null) { + receiveContext = Context.current(); + } + KafkaConsumerContext consumerContext = + KafkaConsumerContextUtil.create(receiveContext, consumerGroup, clientId); + return telemetry.addTracing(records, consumerContext); + } + + @Override + public void onCommit(Map offsets) {} + + @Override + public void close() {} + + @Override + public void configure(Map configs) { + consumerGroup = Objects.toString(configs.get(ConsumerConfig.GROUP_ID_CONFIG), null); + clientId = Objects.toString(configs.get(ConsumerConfig.CLIENT_ID_CONFIG), null); + + Object telemetrySupplier = configs.get(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); + if (telemetrySupplier == null) { + return; + } + + if (!(telemetrySupplier instanceof Supplier)) { + throw new IllegalStateException( + "Configuration property " + + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + + " is not instance of Supplier"); + } + + Object kafkaTelemetry = ((Supplier) telemetrySupplier).get(); + if (!(kafkaTelemetry instanceof KafkaTelemetry)) { + throw new IllegalStateException( + "Configuration property " + + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + + " supplier does not return KafkaTelemetry instance"); + } + + this.telemetry = (KafkaTelemetry) kafkaTelemetry; + } +} diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/OpenTelemetryProducerInterceptor.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/OpenTelemetryProducerInterceptor.java new file mode 100644 index 000000000000..242a10eb5693 --- /dev/null +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/OpenTelemetryProducerInterceptor.java @@ -0,0 +1,77 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.kafkaclients.v2_6; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; +import javax.annotation.Nullable; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.clients.producer.ProducerInterceptor; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.clients.producer.RecordMetadata; + +/** + * A ProducerInterceptor that adds OpenTelemetry instrumentation. Add this interceptor's class name + * or class via ProducerConfig.INTERCEPTOR_CLASSES_CONFIG property to your Producer's properties to + * get it instantiated and used. See more details on ProducerInterceptor usage in its Javadoc. + * + *

To configure the interceptor, use {@link KafkaTelemetry#producerInterceptorConfigProperties} + * to obtain the configuration properties and add them to your producer configuration. + * + * @see KafkaTelemetry#producerInterceptorConfigProperties() + */ +public class OpenTelemetryProducerInterceptor implements ProducerInterceptor { + + public static final String CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER = + "opentelemetry.kafka-telemetry.supplier"; + + @Nullable private KafkaTelemetry telemetry; + @Nullable private String clientId; + + @Override + @CanIgnoreReturnValue + public ProducerRecord onSend(ProducerRecord producerRecord) { + if (telemetry != null) { + telemetry.buildAndInjectSpan(producerRecord, clientId); + } + return producerRecord; + } + + @Override + public void onAcknowledgement(RecordMetadata recordMetadata, Exception e) {} + + @Override + public void close() {} + + @Override + public void configure(Map configs) { + clientId = Objects.toString(configs.get(ProducerConfig.CLIENT_ID_CONFIG), null); + + Object telemetrySupplier = configs.get(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); + if (telemetrySupplier == null) { + return; + } + + if (!(telemetrySupplier instanceof Supplier)) { + throw new IllegalStateException( + "Configuration property " + + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + + " is not instance of Supplier"); + } + + Object kafkaTelemetry = ((Supplier) telemetrySupplier).get(); + if (!(kafkaTelemetry instanceof KafkaTelemetry)) { + throw new IllegalStateException( + "Configuration property " + + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER + + " supplier does not return KafkaTelemetry instance"); + } + + this.telemetry = (KafkaTelemetry) kafkaTelemetry; + } +} 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 124a2467c744..8f60b07e6a88 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 @@ -16,8 +16,6 @@ import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaConsumerContextUtil; import java.util.Map; import java.util.Objects; -import java.util.function.Supplier; -import javax.annotation.Nullable; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerInterceptor; import org.apache.kafka.clients.consumer.ConsumerRecords; @@ -31,19 +29,22 @@ */ public class TracingConsumerInterceptor implements ConsumerInterceptor { - public static final String CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER = - "opentelemetry.kafka-telemetry.supplier"; + private static final KafkaTelemetry telemetry = + KafkaTelemetry.builder(GlobalOpenTelemetry.get()) + .setMessagingReceiveInstrumentationEnabled( + ConfigPropertiesUtil.getBoolean( + "otel.instrumentation.messaging.experimental.receive-telemetry.enabled", false)) + .setCapturedHeaders( + ConfigPropertiesUtil.getList( + "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) + .build(); - @Nullable private KafkaTelemetry telemetry; private String consumerGroup; private String clientId; @Override @CanIgnoreReturnValue public ConsumerRecords onConsume(ConsumerRecords records) { - if (telemetry == null) { - return records; - } // timer should be started before fetching ConsumerRecords, but there is no callback for that Timer timer = Timer.start(); Context receiveContext = telemetry.buildAndFinishSpan(records, consumerGroup, clientId, timer); @@ -66,38 +67,6 @@ public void configure(Map configs) { consumerGroup = Objects.toString(configs.get(ConsumerConfig.GROUP_ID_CONFIG), null); clientId = Objects.toString(configs.get(ConsumerConfig.CLIENT_ID_CONFIG), null); - Object telemetrySupplier = configs.get(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); - if (telemetrySupplier == null) { - // Fallback to GlobalOpenTelemetry if not configured - this.telemetry = - KafkaTelemetry.builder(GlobalOpenTelemetry.get()) - // TODO: remove now that programmatic configuration is available - .setMessagingReceiveInstrumentationEnabled( - ConfigPropertiesUtil.getBoolean( - "otel.instrumentation.messaging.experimental.receive-telemetry.enabled", - false)) - .setCapturedHeaders( - ConfigPropertiesUtil.getList( - "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) - .build(); - return; - } - - if (!(telemetrySupplier instanceof Supplier)) { - throw new IllegalStateException( - "Configuration property " - + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER - + " is not instance of Supplier"); - } - - Object kafkaTelemetry = ((Supplier) telemetrySupplier).get(); - if (!(kafkaTelemetry instanceof KafkaTelemetry)) { - throw new IllegalStateException( - "Configuration property " - + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER - + " supplier does not return KafkaTelemetry instance"); - } - - this.telemetry = (KafkaTelemetry) kafkaTelemetry; + // TODO: support experimental attributes config } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java index 006943ed5306..833b0785d181 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java @@ -12,7 +12,6 @@ import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil; import java.util.Map; import java.util.Objects; -import java.util.function.Supplier; import javax.annotation.Nullable; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerInterceptor; @@ -26,18 +25,19 @@ */ public class TracingProducerInterceptor implements ProducerInterceptor { - public static final String CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER = - "opentelemetry.kafka-telemetry.supplier"; + private static final KafkaTelemetry telemetry = + KafkaTelemetry.builder(GlobalOpenTelemetry.get()) + .setCapturedHeaders( + ConfigPropertiesUtil.getList( + "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) + .build(); - @Nullable private KafkaTelemetry telemetry; @Nullable private String clientId; @Override @CanIgnoreReturnValue public ProducerRecord onSend(ProducerRecord producerRecord) { - if (telemetry != null) { - telemetry.buildAndInjectSpan(producerRecord, clientId); - } + telemetry.buildAndInjectSpan(producerRecord, clientId); return producerRecord; } @@ -48,37 +48,9 @@ public void onAcknowledgement(RecordMetadata recordMetadata, Exception e) {} public void close() {} @Override - public void configure(Map configs) { - clientId = Objects.toString(configs.get(ProducerConfig.CLIENT_ID_CONFIG), null); + public void configure(Map map) { + clientId = Objects.toString(map.get(ProducerConfig.CLIENT_ID_CONFIG), null); - Object telemetrySupplier = configs.get(CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); - if (telemetrySupplier == null) { - // Fallback to GlobalOpenTelemetry if not configured - this.telemetry = - KafkaTelemetry.builder(GlobalOpenTelemetry.get()) - // TODO: remove now that programmatic configuration is available - .setCapturedHeaders( - ConfigPropertiesUtil.getList( - "otel.instrumentation.messaging.experimental.capture-headers", emptyList())) - .build(); - return; - } - - if (!(telemetrySupplier instanceof Supplier)) { - throw new IllegalStateException( - "Configuration property " - + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER - + " is not instance of Supplier"); - } - - Object kafkaTelemetry = ((Supplier) telemetrySupplier).get(); - if (!(kafkaTelemetry instanceof KafkaTelemetry)) { - throw new IllegalStateException( - "Configuration property " - + CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER - + " supplier does not return KafkaTelemetry instance"); - } - - this.telemetry = (KafkaTelemetry) kafkaTelemetry; + // TODO: support experimental attributes config } } diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryInterceptorTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryInterceptorTest.java index d870a9e133ea..82c4292bf70d 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryInterceptorTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/KafkaTelemetryInterceptorTest.java @@ -8,7 +8,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import io.opentelemetry.instrumentation.kafkaclients.common.v0_11.internal.KafkaTelemetrySupplier; +import io.opentelemetry.instrumentation.kafkaclients.v2_6.internal.KafkaTelemetrySupplier; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import java.io.ByteArrayInputStream; @@ -66,7 +66,7 @@ void badProducerConfig() { () -> { Map producerConfig = producerConfig(); producerConfig.put( - TracingProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, "foo"); + OpenTelemetryProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, "foo"); new KafkaProducer<>(producerConfig).close(); }) .hasRootCauseInstanceOf(IllegalStateException.class) @@ -78,7 +78,7 @@ void badProducerConfig() { () -> { Map producerConfig = producerConfig(); producerConfig.put( - TracingProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, + OpenTelemetryProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, (Supplier) () -> "not a KafkaTelemetry"); new KafkaProducer<>(producerConfig).close(); }) @@ -96,7 +96,7 @@ void badConsumerConfig() { () -> { Map consumerConfig = consumerConfig(); consumerConfig.put( - TracingConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, "foo"); + OpenTelemetryConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, "foo"); new KafkaConsumer<>(consumerConfig).close(); }) .hasRootCauseInstanceOf(IllegalStateException.class) @@ -108,7 +108,7 @@ void badConsumerConfig() { () -> { Map consumerConfig = consumerConfig(); consumerConfig.put( - TracingConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, + OpenTelemetryConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER, (Supplier) () -> "not a KafkaTelemetry"); new KafkaConsumer<>(consumerConfig).close(); }) @@ -128,9 +128,9 @@ private static void testSerialize(Map map) throws IOException, ClassNotFoundException { // Check that producer config has the supplier Object producerSupplier = - map.get(TracingProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); + map.get(OpenTelemetryProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); Object consumerSupplier = - map.get(TracingConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); + map.get(OpenTelemetryConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER); KafkaTelemetrySupplier supplier = null; if (producerSupplier instanceof KafkaTelemetrySupplier) { @@ -166,9 +166,9 @@ protected Class resolveClass(ObjectStreamClass desc) try (ObjectInputStream inputStream = new CustomObjectInputStream(new ByteArrayInputStream(byteOutputStream.toByteArray()))) { Map result = (Map) inputStream.readObject(); - assertThat(result.get(TracingProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER)) + assertThat(result.get(OpenTelemetryProducerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER)) .isNull(); - assertThat(result.get(TracingConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER)) + assertThat(result.get(OpenTelemetryConsumerInterceptor.CONFIG_KEY_KAFKA_TELEMETRY_SUPPLIER)) .isNull(); } } From 95d1723d1ca5d824adf6c1af0a1a178e7a552077 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Oct 2025 14:33:42 +0000 Subject: [PATCH 19/21] Mark TracingConsumerInterceptor and TracingProducerInterceptor as deprecated Co-authored-by: trask <218610+trask@users.noreply.github.com> --- .../kafkaclients/v2_6/TracingConsumerInterceptor.java | 5 +++++ .../kafkaclients/v2_6/TracingProducerInterceptor.java | 5 +++++ 2 files changed, 10 insertions(+) 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 8f60b07e6a88..82595c090eba 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 @@ -26,7 +26,12 @@ * A ConsumerInterceptor that adds tracing capability. Add this interceptor's class name or class * via ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG property to your Consumer's properties to get it * instantiated and used. See more details on ConsumerInterceptor usage in its Javadoc. + * + * @deprecated Use {@link OpenTelemetryConsumerInterceptor} instead. This class uses {@link + * GlobalOpenTelemetry} and is not configurable. The new class allows you to pass a configured + * {@link KafkaTelemetry} instance. */ +@Deprecated public class TracingConsumerInterceptor implements ConsumerInterceptor { private static final KafkaTelemetry telemetry = diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java index 833b0785d181..f0cbe4e8b5a1 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java @@ -22,7 +22,12 @@ * A ProducerInterceptor that adds tracing capability. Add this interceptor's class name or class * via ProducerConfig.INTERCEPTOR_CLASSES_CONFIG property to your Producer's properties to get it * instantiated and used. See more details on ProducerInterceptor usage in its Javadoc. + * + * @deprecated Use {@link OpenTelemetryProducerInterceptor} instead. This class uses {@link + * GlobalOpenTelemetry} and is not configurable. The new class allows you to pass a configured + * {@link KafkaTelemetry} instance. */ +@Deprecated public class TracingProducerInterceptor implements ProducerInterceptor { private static final KafkaTelemetry telemetry = From 3fdd463f965cf74a5c928698dc0c933469d7000e Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 8 Oct 2025 16:34:47 -0700 Subject: [PATCH 20/21] Update instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingConsumerInterceptor.java --- .../kafkaclients/v2_6/TracingConsumerInterceptor.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 82595c090eba..8f9d060ff968 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 @@ -27,9 +27,7 @@ * via ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG property to your Consumer's properties to get it * instantiated and used. See more details on ConsumerInterceptor usage in its Javadoc. * - * @deprecated Use {@link OpenTelemetryConsumerInterceptor} instead. This class uses {@link - * GlobalOpenTelemetry} and is not configurable. The new class allows you to pass a configured - * {@link KafkaTelemetry} instance. + * @deprecated Use {@link OpenTelemetryConsumerInterceptor} instead. */ @Deprecated public class TracingConsumerInterceptor implements ConsumerInterceptor { From f72ae47dc36e90880ded048a128746f576eac5ab Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 8 Oct 2025 16:35:07 -0700 Subject: [PATCH 21/21] Update instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java --- .../kafkaclients/v2_6/TracingProducerInterceptor.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java index f0cbe4e8b5a1..ea631f78054d 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/main/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/TracingProducerInterceptor.java @@ -23,9 +23,7 @@ * via ProducerConfig.INTERCEPTOR_CLASSES_CONFIG property to your Producer's properties to get it * instantiated and used. See more details on ProducerInterceptor usage in its Javadoc. * - * @deprecated Use {@link OpenTelemetryProducerInterceptor} instead. This class uses {@link - * GlobalOpenTelemetry} and is not configurable. The new class allows you to pass a configured - * {@link KafkaTelemetry} instance. + * @deprecated Use {@link OpenTelemetryProducerInterceptor} instead. */ @Deprecated public class TracingProducerInterceptor implements ProducerInterceptor {