Extract a common JMS module before implementing Jakarta JMS instrumen… (#7377)

…tation

We need to instrument Jakarta JMS (3.0) before instrumenting Spring JMS
6.0

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
This commit is contained in:
Mateusz Rzeszutek 2022-12-12 19:22:00 +01:00 committed by GitHub
parent 59b7513cd0
commit d971e26501
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 489 additions and 261 deletions

View File

@ -1,92 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jms;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessageOperation;
import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesGetter;
import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingSpanNameExtractor;
import io.opentelemetry.instrumentation.api.internal.PropagatorBasedSpanLinksExtractor;
import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig;
public final class JmsSingletons {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jms-1.1";
private static final Instrumenter<MessageWithDestination, Void> PRODUCER_INSTRUMENTER =
buildProducerInstrumenter();
private static final Instrumenter<MessageWithDestination, Void> CONSUMER_INSTRUMENTER =
buildConsumerInstrumenter();
private static final Instrumenter<MessageWithDestination, Void> LISTENER_INSTRUMENTER =
buildListenerInstrumenter();
private static Instrumenter<MessageWithDestination, Void> buildProducerInstrumenter() {
JmsMessageAttributesGetter getter = JmsMessageAttributesGetter.INSTANCE;
MessageOperation operation = MessageOperation.SEND;
return Instrumenter.<MessageWithDestination, Void>builder(
GlobalOpenTelemetry.get(),
INSTRUMENTATION_NAME,
MessagingSpanNameExtractor.create(getter, operation))
.addAttributesExtractor(buildMessagingAttributesExtractor(getter, operation))
.buildProducerInstrumenter(MessagePropertySetter.INSTANCE);
}
private static Instrumenter<MessageWithDestination, Void> buildConsumerInstrumenter() {
JmsMessageAttributesGetter getter = JmsMessageAttributesGetter.INSTANCE;
MessageOperation operation = MessageOperation.RECEIVE;
// MessageConsumer does not do context propagation
return Instrumenter.<MessageWithDestination, Void>builder(
GlobalOpenTelemetry.get(),
INSTRUMENTATION_NAME,
MessagingSpanNameExtractor.create(getter, operation))
.addAttributesExtractor(buildMessagingAttributesExtractor(getter, operation))
.setEnabled(ExperimentalConfig.get().messagingReceiveInstrumentationEnabled())
.addSpanLinksExtractor(
new PropagatorBasedSpanLinksExtractor<>(
GlobalOpenTelemetry.getPropagators().getTextMapPropagator(),
MessagePropertyGetter.INSTANCE))
.buildInstrumenter(SpanKindExtractor.alwaysConsumer());
}
private static Instrumenter<MessageWithDestination, Void> buildListenerInstrumenter() {
JmsMessageAttributesGetter getter = JmsMessageAttributesGetter.INSTANCE;
MessageOperation operation = MessageOperation.PROCESS;
return Instrumenter.<MessageWithDestination, Void>builder(
GlobalOpenTelemetry.get(),
INSTRUMENTATION_NAME,
MessagingSpanNameExtractor.create(getter, operation))
.addAttributesExtractor(buildMessagingAttributesExtractor(getter, operation))
.buildConsumerInstrumenter(MessagePropertyGetter.INSTANCE);
}
private static MessagingAttributesExtractor<MessageWithDestination, Void>
buildMessagingAttributesExtractor(
MessagingAttributesGetter<MessageWithDestination, Void> getter,
MessageOperation operation) {
return MessagingAttributesExtractor.builder(getter, operation)
.setCapturedHeaders(ExperimentalConfig.get().getMessagingHeaders())
.build();
}
public static Instrumenter<MessageWithDestination, Void> producerInstrumenter() {
return PRODUCER_INSTRUMENTER;
}
public static Instrumenter<MessageWithDestination, Void> consumerInstrumenter() {
return CONSUMER_INSTRUMENTER;
}
public static Instrumenter<MessageWithDestination, Void> listenerInstrumenter() {
return LISTENER_INSTRUMENTER;
}
private JmsSingletons() {}
}

View File

@ -1,79 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jms;
import com.google.auto.value.AutoValue;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.TemporaryQueue;
import javax.jms.TemporaryTopic;
import javax.jms.Topic;
@AutoValue
public abstract class MessageWithDestination {
// visible for tests
static final String TIBCO_TMP_PREFIX = "$TMP$";
public abstract Message message();
public abstract String destinationName();
public abstract String destinationKind();
public abstract boolean isTemporaryDestination();
public static MessageWithDestination create(Message message, Destination fallbackDestination) {
Destination jmsDestination = null;
try {
jmsDestination = message.getJMSDestination();
} catch (Exception ignored) {
// Ignore
}
if (jmsDestination == null) {
jmsDestination = fallbackDestination;
}
if (jmsDestination instanceof Queue) {
return createMessageWithQueue(message, (Queue) jmsDestination);
}
if (jmsDestination instanceof Topic) {
return createMessageWithTopic(message, (Topic) jmsDestination);
}
return new AutoValue_MessageWithDestination(
message, "unknown", "unknown", /* isTemporaryDestination= */ false);
}
private static MessageWithDestination createMessageWithQueue(Message message, Queue destination) {
String queueName;
try {
queueName = destination.getQueueName();
} catch (JMSException e) {
queueName = "unknown";
}
boolean temporary =
destination instanceof TemporaryQueue || queueName.startsWith(TIBCO_TMP_PREFIX);
return new AutoValue_MessageWithDestination(message, queueName, "queue", temporary);
}
private static MessageWithDestination createMessageWithTopic(Message message, Topic destination) {
String topicName;
try {
topicName = destination.getTopicName();
} catch (JMSException e) {
topicName = "unknown";
}
boolean temporary =
destination instanceof TemporaryTopic || topicName.startsWith(TIBCO_TMP_PREFIX);
return new AutoValue_MessageWithDestination(message, topicName, "topic", temporary);
}
}

View File

@ -39,6 +39,8 @@ dependencies {
compileOnly("com.google.auto.value:auto-value-annotations")
annotationProcessor("com.google.auto.value:auto-value")
implementation(project(":instrumentation:jms:jms-common:javaagent"))
compileOnly("javax.jms:jms-api:1.1-rev-1")
testImplementation("org.apache.activemq:activemq-client:5.16.5")

View File

@ -0,0 +1,65 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jms.v1_1;
import io.opentelemetry.javaagent.instrumentation.jms.DestinationAdapter;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Queue;
import javax.jms.TemporaryQueue;
import javax.jms.TemporaryTopic;
import javax.jms.Topic;
public final class JavaxDestinationAdapter implements DestinationAdapter {
public static DestinationAdapter create(Destination destination) {
return new JavaxDestinationAdapter(destination);
}
private final Destination destination;
private JavaxDestinationAdapter(Destination destination) {
this.destination = destination;
}
@Override
public boolean isQueue() {
return destination instanceof Queue;
}
@Override
public boolean isTopic() {
return destination instanceof Topic;
}
@Override
public String getQueueName() throws JMSException {
if (!(destination instanceof Queue)) {
throw new IllegalStateException(
"This destination is not a Queue; make sure to call isQueue() before");
}
return ((Queue) destination).getQueueName();
}
@Override
public String getTopicName() throws JMSException {
if (!(destination instanceof Topic)) {
throw new IllegalStateException(
"This destination is not a Topic; make sure to call isTopic() before");
}
return ((Topic) destination).getTopicName();
}
@Override
public boolean isTemporaryQueue() {
return destination instanceof TemporaryQueue;
}
@Override
public boolean isTemporaryTopic() {
return destination instanceof TemporaryTopic;
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jms.v1_1;
import io.opentelemetry.javaagent.instrumentation.jms.DestinationAdapter;
import io.opentelemetry.javaagent.instrumentation.jms.MessageAdapter;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
public final class JavaxMessageAdapter implements MessageAdapter {
public static MessageAdapter create(Message message) {
return new JavaxMessageAdapter(message);
}
private final Message message;
private JavaxMessageAdapter(Message message) {
this.message = message;
}
@Nullable
@Override
public DestinationAdapter getJmsDestination() throws JMSException {
Destination destination = message.getJMSDestination();
if (destination == null) {
return null;
}
return JavaxDestinationAdapter.create(destination);
}
@Override
@SuppressWarnings("unchecked")
public List<String> getPropertyNames() throws JMSException {
return Collections.list(message.getPropertyNames());
}
@Nullable
@Override
public Object getObjectProperty(String key) throws JMSException {
return message.getObjectProperty(key);
}
@Nullable
@Override
public String getStringProperty(String key) throws JMSException {
return message.getStringProperty(key);
}
@Override
public void setStringProperty(String key, String value) throws JMSException {
message.setStringProperty(key, value);
}
@Nullable
@Override
public String getJmsCorrelationId() throws JMSException {
return message.getJMSCorrelationID();
}
@Nullable
@Override
public String getJmsMessageId() throws JMSException {
return message.getJMSMessageID();
}
}

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jms;
package io.opentelemetry.javaagent.instrumentation.jms.v1_1;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesBuilder;

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jms;
package io.opentelemetry.javaagent.instrumentation.jms.v1_1;
import static java.util.Arrays.asList;

View File

@ -3,11 +3,11 @@
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jms;
package io.opentelemetry.javaagent.instrumentation.jms.v1_1;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static io.opentelemetry.javaagent.instrumentation.jms.JmsSingletons.consumerInstrumenter;
import static io.opentelemetry.javaagent.instrumentation.jms.v1_1.JmsSingletons.consumerReceiveInstrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.returns;
@ -18,6 +18,8 @@ import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil;
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.jms.MessageWithDestination;
import io.opentelemetry.javaagent.instrumentation.jms.Timer;
import javax.jms.Message;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
@ -70,11 +72,12 @@ public class JmsMessageConsumerInstrumentation implements TypeInstrumentation {
}
Context parentContext = Java8BytecodeBridge.currentContext();
MessageWithDestination request = MessageWithDestination.create(message, null);
MessageWithDestination request =
MessageWithDestination.create(JavaxMessageAdapter.create(message), null);
if (consumerInstrumenter().shouldStart(parentContext, request)) {
if (consumerReceiveInstrumenter().shouldStart(parentContext, request)) {
InstrumenterUtil.startAndEnd(
consumerInstrumenter(),
consumerReceiveInstrumenter(),
parentContext,
request,
null,

View File

@ -3,11 +3,11 @@
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jms;
package io.opentelemetry.javaagent.instrumentation.jms.v1_1;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static io.opentelemetry.javaagent.instrumentation.jms.JmsSingletons.listenerInstrumenter;
import static io.opentelemetry.javaagent.instrumentation.jms.v1_1.JmsSingletons.consumerProcessInstrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
@ -17,6 +17,7 @@ import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.jms.MessageWithDestination;
import javax.jms.Message;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
@ -52,13 +53,13 @@ public class JmsMessageListenerInstrumentation implements TypeInstrumentation {
@Advice.Local("otelScope") Scope scope) {
Context parentContext = Java8BytecodeBridge.currentContext();
request = MessageWithDestination.create(message, null);
request = MessageWithDestination.create(JavaxMessageAdapter.create(message), null);
if (!listenerInstrumenter().shouldStart(parentContext, request)) {
if (!consumerProcessInstrumenter().shouldStart(parentContext, request)) {
return;
}
context = listenerInstrumenter().start(parentContext, request);
context = consumerProcessInstrumenter().start(parentContext, request);
scope = context.makeCurrent();
}
@ -72,7 +73,7 @@ public class JmsMessageListenerInstrumentation implements TypeInstrumentation {
return;
}
scope.close();
listenerInstrumenter().end(context, request, null, throwable);
consumerProcessInstrumenter().end(context, request, null, throwable);
}
}
}

View File

@ -3,11 +3,11 @@
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jms;
package io.opentelemetry.javaagent.instrumentation.jms.v1_1;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static io.opentelemetry.javaagent.instrumentation.jms.JmsSingletons.producerInstrumenter;
import static io.opentelemetry.javaagent.instrumentation.jms.v1_1.JmsSingletons.producerInstrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
@ -18,6 +18,7 @@ import io.opentelemetry.javaagent.bootstrap.CallDepth;
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.jms.MessageWithDestination;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
@ -75,7 +76,10 @@ public class JmsMessageProducerInstrumentation implements TypeInstrumentation {
}
Context parentContext = Java8BytecodeBridge.currentContext();
request = MessageWithDestination.create(message, defaultDestination);
request =
MessageWithDestination.create(
JavaxMessageAdapter.create(message),
JavaxDestinationAdapter.create(defaultDestination));
if (!producerInstrumenter().shouldStart(parentContext, request)) {
return;
}
@ -119,7 +123,9 @@ public class JmsMessageProducerInstrumentation implements TypeInstrumentation {
}
Context parentContext = Java8BytecodeBridge.currentContext();
request = MessageWithDestination.create(message, destination);
request =
MessageWithDestination.create(
JavaxMessageAdapter.create(message), JavaxDestinationAdapter.create(destination));
if (!producerInstrumenter().shouldStart(parentContext, request)) {
return;
}

View File

@ -0,0 +1,46 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jms.v1_1;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig;
import io.opentelemetry.javaagent.instrumentation.jms.JmsInstrumenterFactory;
import io.opentelemetry.javaagent.instrumentation.jms.MessageWithDestination;
public final class JmsSingletons {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jms-1.1";
private static final Instrumenter<MessageWithDestination, Void> PRODUCER_INSTRUMENTER;
private static final Instrumenter<MessageWithDestination, Void> CONSUMER_RECEIVE_INSTRUMENTER;
private static final Instrumenter<MessageWithDestination, Void> CONSUMER_PROCESS_INSTRUMENTER;
static {
JmsInstrumenterFactory factory =
new JmsInstrumenterFactory(GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME)
.setCapturedHeaders(ExperimentalConfig.get().getMessagingHeaders())
.setMessagingReceiveInstrumentationEnabled(
ExperimentalConfig.get().messagingReceiveInstrumentationEnabled());
PRODUCER_INSTRUMENTER = factory.createProducerInstrumenter();
CONSUMER_RECEIVE_INSTRUMENTER = factory.createConsumerReceiveInstrumenter();
CONSUMER_PROCESS_INSTRUMENTER = factory.createConsumerProcessInstrumenter();
}
public static Instrumenter<MessageWithDestination, Void> producerInstrumenter() {
return PRODUCER_INSTRUMENTER;
}
public static Instrumenter<MessageWithDestination, Void> consumerReceiveInstrumenter() {
return CONSUMER_RECEIVE_INSTRUMENTER;
}
public static Instrumenter<MessageWithDestination, Void> consumerProcessInstrumenter() {
return CONSUMER_PROCESS_INSTRUMENTER;
}
private JmsSingletons() {}
}

View File

@ -3,8 +3,7 @@ plugins {
}
dependencies {
testImplementation("javax.jms:jms-api:1.1-rev-1")
testImplementation(project(":instrumentation:jms-1.1:javaagent"))
testImplementation(project(":instrumentation:jms:jms-common:javaagent"))
testImplementation(project(":instrumentation-api"))
testImplementation(project(":instrumentation-api-semconv"))
}

View File

@ -11,13 +11,6 @@ import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.Mockito.when;
import java.util.stream.Stream;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.TemporaryQueue;
import javax.jms.TemporaryTopic;
import javax.jms.Topic;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
@ -29,17 +22,13 @@ import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class MessageWithDestinationTest {
@Mock Message message;
@Mock Topic topic;
@Mock TemporaryTopic temporaryTopic;
@Mock Queue queue;
@Mock TemporaryQueue temporaryQueue;
@Mock Destination destination;
@Mock MessageAdapter message;
@Mock DestinationAdapter destination;
@Test
void shouldCreateMessageWithUnknownDestination() throws JMSException {
void shouldCreateMessageWithUnknownDestination() throws Exception {
// given
when(message.getJMSDestination()).thenReturn(destination);
when(message.getJmsDestination()).thenReturn(destination);
// when
MessageWithDestination result = MessageWithDestination.create(message, null);
@ -49,9 +38,9 @@ class MessageWithDestinationTest {
}
@Test
void shouldUseFallbackDestinationToCreateMessage() throws JMSException {
void shouldUseFallbackDestinationToCreateMessage() throws Exception {
// given
when(message.getJMSDestination()).thenThrow(JMSException.class);
when(message.getJmsDestination()).thenThrow(RuntimeException.class);
// when
MessageWithDestination result = MessageWithDestination.create(message, destination);
@ -67,15 +56,17 @@ class MessageWithDestinationTest {
boolean useTemporaryDestination,
String expectedDestinationName,
boolean expectedTemporary)
throws JMSException {
// given
Queue queue = useTemporaryDestination ? this.temporaryQueue : this.queue;
throws Exception {
// given
when(message.getJmsDestination()).thenReturn(destination);
when(destination.isQueue()).thenReturn(true);
when(destination.isTemporaryQueue()).thenReturn(useTemporaryDestination);
when(message.getJMSDestination()).thenReturn(queue);
if (queueName == null) {
when(queue.getQueueName()).thenThrow(JMSException.class);
when(destination.getQueueName()).thenThrow(RuntimeException.class);
} else {
when(queue.getQueueName()).thenReturn(queueName);
when(destination.getQueueName()).thenReturn(queueName);
}
// when
@ -92,15 +83,17 @@ class MessageWithDestinationTest {
boolean useTemporaryDestination,
String expectedDestinationName,
boolean expectedTemporary)
throws JMSException {
// given
Topic topic = useTemporaryDestination ? this.temporaryTopic : this.topic;
throws Exception {
// given
when(message.getJmsDestination()).thenReturn(destination);
when(destination.isTopic()).thenReturn(true);
when(destination.isTemporaryTopic()).thenReturn(useTemporaryDestination);
when(message.getJMSDestination()).thenReturn(topic);
if (topicName == null) {
when(topic.getTopicName()).thenThrow(JMSException.class);
when(destination.getTopicName()).thenThrow(RuntimeException.class);
} else {
when(topic.getTopicName()).thenReturn(topicName);
when(destination.getTopicName()).thenReturn(topicName);
}
// when

View File

@ -0,0 +1,8 @@
plugins {
id("otel.javaagent-instrumentation")
}
dependencies {
compileOnly("com.google.auto.value:auto-value-annotations")
annotationProcessor("com.google.auto.value:auto-value")
}

View File

@ -0,0 +1,21 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jms;
public interface DestinationAdapter {
boolean isQueue();
boolean isTopic();
String getQueueName() throws Exception;
String getTopicName() throws Exception;
boolean isTemporaryQueue();
boolean isTemporaryTopic();
}

View File

@ -0,0 +1,93 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jms;
import static java.util.Collections.emptyList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessageOperation;
import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingSpanNameExtractor;
import io.opentelemetry.instrumentation.api.internal.PropagatorBasedSpanLinksExtractor;
import java.util.List;
public final class JmsInstrumenterFactory {
private final OpenTelemetry openTelemetry;
private final String instrumentationName;
private List<String> capturedHeaders = emptyList();
private boolean messagingReceiveInstrumentationEnabled = false;
public JmsInstrumenterFactory(OpenTelemetry openTelemetry, String instrumentationName) {
this.openTelemetry = openTelemetry;
this.instrumentationName = instrumentationName;
}
@CanIgnoreReturnValue
public JmsInstrumenterFactory setCapturedHeaders(List<String> capturedHeaders) {
this.capturedHeaders = capturedHeaders;
return this;
}
@CanIgnoreReturnValue
public JmsInstrumenterFactory setMessagingReceiveInstrumentationEnabled(
boolean messagingReceiveInstrumentationEnabled) {
this.messagingReceiveInstrumentationEnabled = messagingReceiveInstrumentationEnabled;
return this;
}
public Instrumenter<MessageWithDestination, Void> createProducerInstrumenter() {
JmsMessageAttributesGetter getter = JmsMessageAttributesGetter.INSTANCE;
MessageOperation operation = MessageOperation.SEND;
return Instrumenter.<MessageWithDestination, Void>builder(
openTelemetry,
instrumentationName,
MessagingSpanNameExtractor.create(getter, operation))
.addAttributesExtractor(createMessagingAttributesExtractor(operation))
.buildProducerInstrumenter(MessagePropertySetter.INSTANCE);
}
public Instrumenter<MessageWithDestination, Void> createConsumerReceiveInstrumenter() {
JmsMessageAttributesGetter getter = JmsMessageAttributesGetter.INSTANCE;
MessageOperation operation = MessageOperation.RECEIVE;
// MessageConsumer does not do context propagation
return Instrumenter.<MessageWithDestination, Void>builder(
openTelemetry,
instrumentationName,
MessagingSpanNameExtractor.create(getter, operation))
.addAttributesExtractor(createMessagingAttributesExtractor(operation))
.setEnabled(messagingReceiveInstrumentationEnabled)
.addSpanLinksExtractor(
new PropagatorBasedSpanLinksExtractor<>(
openTelemetry.getPropagators().getTextMapPropagator(),
MessagePropertyGetter.INSTANCE))
.buildInstrumenter(SpanKindExtractor.alwaysConsumer());
}
public Instrumenter<MessageWithDestination, Void> createConsumerProcessInstrumenter() {
JmsMessageAttributesGetter getter = JmsMessageAttributesGetter.INSTANCE;
MessageOperation operation = MessageOperation.PROCESS;
return Instrumenter.<MessageWithDestination, Void>builder(
openTelemetry,
instrumentationName,
MessagingSpanNameExtractor.create(getter, operation))
.addAttributesExtractor(createMessagingAttributesExtractor(operation))
.buildConsumerInstrumenter(MessagePropertyGetter.INSTANCE);
}
private MessagingAttributesExtractor<MessageWithDestination, Void>
createMessagingAttributesExtractor(MessageOperation operation) {
return MessagingAttributesExtractor.builder(JmsMessageAttributesGetter.INSTANCE, operation)
.setCapturedHeaders(capturedHeaders)
.build();
}
}

View File

@ -12,10 +12,8 @@ import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.jms.JMSException;
public enum JmsMessageAttributesGetter
implements MessagingAttributesGetter<MessageWithDestination, Void> {
enum JmsMessageAttributesGetter implements MessagingAttributesGetter<MessageWithDestination, Void> {
INSTANCE;
private static final Logger logger = Logger.getLogger(JmsMessageAttributesGetter.class.getName());
@ -64,8 +62,8 @@ public enum JmsMessageAttributesGetter
@Override
public String conversationId(MessageWithDestination messageWithDestination) {
try {
return messageWithDestination.message().getJMSCorrelationID();
} catch (JMSException e) {
return messageWithDestination.message().getJmsCorrelationId();
} catch (Exception e) {
logger.log(FINE, "Failure getting JMS correlation id", e);
return null;
}
@ -87,8 +85,8 @@ public enum JmsMessageAttributesGetter
@Override
public String messageId(MessageWithDestination messageWithDestination, Void unused) {
try {
return messageWithDestination.message().getJMSMessageID();
} catch (JMSException exception) {
return messageWithDestination.message().getJmsMessageId();
} catch (Exception exception) {
logger.log(FINE, "Failure getting JMS message id", exception);
return null;
}
@ -101,7 +99,7 @@ public enum JmsMessageAttributesGetter
if (value != null) {
return Collections.singletonList(value);
}
} catch (JMSException exception) {
} catch (Exception exception) {
logger.log(FINE, "Failure getting JMS message header", exception);
}
return Collections.emptyList();

View File

@ -0,0 +1,31 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jms;
import java.util.List;
import javax.annotation.Nullable;
public interface MessageAdapter {
@Nullable
DestinationAdapter getJmsDestination() throws Exception;
List<String> getPropertyNames() throws Exception;
@Nullable
Object getObjectProperty(String key) throws Exception;
@Nullable
String getStringProperty(String key) throws Exception;
void setStringProperty(String key, String value) throws Exception;
@Nullable
String getJmsCorrelationId() throws Exception;
@Nullable
String getJmsMessageId() throws Exception;
}

View File

@ -7,17 +7,15 @@ package io.opentelemetry.javaagent.instrumentation.jms;
import io.opentelemetry.context.propagation.TextMapGetter;
import java.util.Collections;
import javax.jms.JMSException;
public enum MessagePropertyGetter implements TextMapGetter<MessageWithDestination> {
enum MessagePropertyGetter implements TextMapGetter<MessageWithDestination> {
INSTANCE;
@Override
@SuppressWarnings("unchecked")
public Iterable<String> keys(MessageWithDestination message) {
try {
return Collections.list(message.message().getPropertyNames());
} catch (JMSException e) {
return message.message().getPropertyNames();
} catch (Exception e) {
return Collections.emptyList();
}
}
@ -28,7 +26,7 @@ public enum MessagePropertyGetter implements TextMapGetter<MessageWithDestinatio
Object value;
try {
value = carrier.message().getObjectProperty(propName);
} catch (JMSException e) {
} catch (Exception e) {
throw new IllegalStateException(e);
}
if (value instanceof String) {

View File

@ -9,7 +9,6 @@ import static java.util.logging.Level.FINE;
import io.opentelemetry.context.propagation.TextMapSetter;
import java.util.logging.Logger;
import javax.jms.JMSException;
enum MessagePropertySetter implements TextMapSetter<MessageWithDestination> {
INSTANCE;
@ -23,7 +22,7 @@ enum MessagePropertySetter implements TextMapSetter<MessageWithDestination> {
String propName = key.replace("-", DASH);
try {
carrier.message().setStringProperty(propName, value);
} catch (JMSException e) {
} catch (Exception e) {
if (logger.isLoggable(FINE)) {
logger.log(FINE, "Failure setting jms property: " + propName, e);
}

View File

@ -0,0 +1,77 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.jms;
import com.google.auto.value.AutoValue;
@AutoValue
public abstract class MessageWithDestination {
// visible for tests
static final String TIBCO_TMP_PREFIX = "$TMP$";
public abstract MessageAdapter message();
public abstract String destinationName();
public abstract String destinationKind();
public abstract boolean isTemporaryDestination();
public static MessageWithDestination create(
MessageAdapter message, DestinationAdapter fallbackDestination) {
DestinationAdapter jmsDestination = null;
try {
jmsDestination = message.getJmsDestination();
} catch (Exception ignored) {
// Ignore
}
if (jmsDestination == null) {
jmsDestination = fallbackDestination;
}
if (jmsDestination.isQueue()) {
return createMessageWithQueue(message, jmsDestination);
}
if (jmsDestination.isTopic()) {
return createMessageWithTopic(message, jmsDestination);
}
return new AutoValue_MessageWithDestination(
message, "unknown", "unknown", /* isTemporaryDestination= */ false);
}
private static MessageWithDestination createMessageWithQueue(
MessageAdapter message, DestinationAdapter queue) {
String queueName = getDestinationName(queue, DestinationAdapter::getQueueName);
boolean temporary = queue.isTemporaryQueue() || queueName.startsWith(TIBCO_TMP_PREFIX);
return new AutoValue_MessageWithDestination(message, queueName, "queue", temporary);
}
private static MessageWithDestination createMessageWithTopic(
MessageAdapter message, DestinationAdapter topic) {
String topicName = getDestinationName(topic, DestinationAdapter::getTopicName);
boolean temporary = topic.isTemporaryTopic() || topicName.startsWith(TIBCO_TMP_PREFIX);
return new AutoValue_MessageWithDestination(message, topicName, "topic", temporary);
}
private static String getDestinationName(DestinationAdapter destination, NameGetter nameGetter) {
try {
return nameGetter.getName(destination);
} catch (Exception e) {
return "unknown";
}
}
@FunctionalInterface
private interface NameGetter {
String getName(DestinationAdapter destination) throws Exception;
}
}

View File

@ -31,14 +31,15 @@ tasks {
}
dependencies {
implementation(project(":instrumentation:jms-1.1:javaagent"))
implementation(project(":instrumentation:jms:jms-common:javaagent"))
implementation(project(":instrumentation:jms:jms-1.1:javaagent"))
library("org.springframework:spring-jms:2.0")
compileOnly("javax.jms:jms-api:1.1-rev-1")
compileOnly("com.google.auto.value:auto-value-annotations")
annotationProcessor("com.google.auto.value:auto-value")
testInstrumentation(project(":instrumentation:jms-1.1:javaagent"))
testInstrumentation(project(":instrumentation:jms:jms-1.1:javaagent"))
testImplementation("org.springframework.boot:spring-boot-starter-activemq:2.5.3")
testImplementation("org.springframework.boot:spring-boot-starter-test:2.5.3") {

View File

@ -19,6 +19,7 @@ import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.jms.MessageWithDestination;
import io.opentelemetry.javaagent.instrumentation.jms.v1_1.JavaxMessageAdapter;
import javax.jms.Message;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
@ -58,7 +59,7 @@ public class SpringJmsMessageListenerInstrumentation implements TypeInstrumentat
@Advice.Local("otelScope") Scope scope) {
Context parentContext = Java8BytecodeBridge.currentContext();
request = MessageWithDestination.create(message, null);
request = MessageWithDestination.create(JavaxMessageAdapter.create(message), null);
if (!listenerInstrumenter().shouldStart(parentContext, request)) {
return;

View File

@ -7,34 +7,17 @@ package io.opentelemetry.javaagent.instrumentation.spring.jms;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessageOperation;
import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingSpanNameExtractor;
import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig;
import io.opentelemetry.javaagent.instrumentation.jms.JmsMessageAttributesGetter;
import io.opentelemetry.javaagent.instrumentation.jms.MessagePropertyGetter;
import io.opentelemetry.javaagent.instrumentation.jms.JmsInstrumenterFactory;
import io.opentelemetry.javaagent.instrumentation.jms.MessageWithDestination;
public final class SpringJmsSingletons {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.spring-jms-2.0";
private static final Instrumenter<MessageWithDestination, Void> LISTENER_INSTRUMENTER =
buildListenerInstrumenter();
private static Instrumenter<MessageWithDestination, Void> buildListenerInstrumenter() {
JmsMessageAttributesGetter getter = JmsMessageAttributesGetter.INSTANCE;
MessageOperation operation = MessageOperation.PROCESS;
return Instrumenter.<MessageWithDestination, Void>builder(
GlobalOpenTelemetry.get(),
INSTRUMENTATION_NAME,
MessagingSpanNameExtractor.create(getter, operation))
.addAttributesExtractor(
MessagingAttributesExtractor.builder(getter, operation)
.setCapturedHeaders(ExperimentalConfig.get().getMessagingHeaders())
.build())
.buildConsumerInstrumenter(MessagePropertyGetter.INSTANCE);
}
new JmsInstrumenterFactory(GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME)
.setCapturedHeaders(ExperimentalConfig.get().getMessagingHeaders())
.createConsumerProcessInstrumenter();
public static Instrumenter<MessageWithDestination, Void> listenerInstrumenter() {
return LISTENER_INSTRUMENTER;

View File

@ -290,8 +290,9 @@ hideFromDependabot(":instrumentation:jetty:jetty-common:javaagent")
hideFromDependabot(":instrumentation:jetty-httpclient:jetty-httpclient-9.2:javaagent")
hideFromDependabot(":instrumentation:jetty-httpclient:jetty-httpclient-9.2:library")
hideFromDependabot(":instrumentation:jetty-httpclient:jetty-httpclient-9.2:testing")
hideFromDependabot(":instrumentation:jms-1.1:javaagent")
hideFromDependabot(":instrumentation:jms-1.1:javaagent-unit-tests")
hideFromDependabot(":instrumentation:jms:jms-1.1:javaagent")
hideFromDependabot(":instrumentation:jms:jms-common:javaagent")
hideFromDependabot(":instrumentation:jms:jms-common:javaagent-unit-tests")
hideFromDependabot(":instrumentation:jmx-metrics:javaagent")
hideFromDependabot(":instrumentation:jmx-metrics:library")
hideFromDependabot(":instrumentation:jsf:jsf-common:javaagent")