From 77610942803bc3b2bdfbb2c3a66c971ac7a7af3c Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sun, 8 Mar 2020 22:41:58 -0700 Subject: [PATCH 1/2] Fix for Spring JMS listeners --- .../AdditionalLibraryIgnoresMatcher.java | 8 +- .../groovy/SpringListenerJMS2Test.groovy | 46 +++++++++++ .../groovy/listener/Config.groovy | 79 +++++++++++++++++++ .../groovy/listener/TestListener.groovy | 28 +++++++ .../test/groovy/SpringListenerJMS1Test.groovy | 50 ++++++++++++ .../src/test/groovy/listener/Config.groovy | 51 ++++++++++++ .../test/groovy/listener/TestListener.groovy | 28 +++++++ 7 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 dd-java-agent/instrumentation/jms/src/latestDepTest/groovy/SpringListenerJMS2Test.groovy create mode 100644 dd-java-agent/instrumentation/jms/src/latestDepTest/groovy/listener/Config.groovy create mode 100644 dd-java-agent/instrumentation/jms/src/latestDepTest/groovy/listener/TestListener.groovy create mode 100644 dd-java-agent/instrumentation/jms/src/test/groovy/SpringListenerJMS1Test.groovy create mode 100644 dd-java-agent/instrumentation/jms/src/test/groovy/listener/Config.groovy create mode 100644 dd-java-agent/instrumentation/jms/src/test/groovy/listener/TestListener.groovy diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/matcher/AdditionalLibraryIgnoresMatcher.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/matcher/AdditionalLibraryIgnoresMatcher.java index df441159b58..51d1f7deee6 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/matcher/AdditionalLibraryIgnoresMatcher.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/matcher/AdditionalLibraryIgnoresMatcher.java @@ -50,7 +50,6 @@ public boolean matches(final T target) { || name.startsWith("org.springframework.format.") || name.startsWith("org.springframework.jca.") || name.startsWith("org.springframework.jdbc.") - || name.startsWith("org.springframework.jms.") || name.startsWith("org.springframework.jmx.") || name.startsWith("org.springframework.jndi.") || name.startsWith("org.springframework.lang.") @@ -151,6 +150,13 @@ public boolean matches(final T target) { return true; } + if (name.startsWith("org.springframework.jms.")) { + if (name.startsWith("org.springframework.jms.listener.")) { + return false; + } + return true; + } + if (name.startsWith("org.springframework.util.")) { if (name.startsWith("org.springframework.util.concurrent.")) { return false; diff --git a/dd-java-agent/instrumentation/jms/src/latestDepTest/groovy/SpringListenerJMS2Test.groovy b/dd-java-agent/instrumentation/jms/src/latestDepTest/groovy/SpringListenerJMS2Test.groovy new file mode 100644 index 00000000000..91327eb4f7a --- /dev/null +++ b/dd-java-agent/instrumentation/jms/src/latestDepTest/groovy/SpringListenerJMS2Test.groovy @@ -0,0 +1,46 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import datadog.trace.agent.test.AgentTestRunner +import listener.Config +import org.hornetq.jms.client.HornetQMessageConsumer +import org.springframework.context.annotation.AnnotationConfigApplicationContext +import org.springframework.jms.core.JmsTemplate +import org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter + +import javax.jms.ConnectionFactory + +import static JMS2Test.consumerTrace +import static JMS2Test.producerTrace + +class SpringListenerJMS2Test extends AgentTestRunner { + + def "receiving message in spring listener generates spans"() { + setup: + def context = new AnnotationConfigApplicationContext(Config) + def factory = context.getBean(ConnectionFactory) + def template = new JmsTemplate(factory) + template.convertAndSend("someSpringQueue", "a message") + + expect: + assertTraces(3) { + producerTrace(it, 0, "Queue someSpringQueue") + consumerTrace(it, 1, "Queue someSpringQueue", false, HornetQMessageConsumer) + consumerTrace(it, 2, "Queue someSpringQueue", true, MessagingMessageListenerAdapter) + } + } +} diff --git a/dd-java-agent/instrumentation/jms/src/latestDepTest/groovy/listener/Config.groovy b/dd-java-agent/instrumentation/jms/src/latestDepTest/groovy/listener/Config.groovy new file mode 100644 index 00000000000..aaf0d393192 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/src/latestDepTest/groovy/listener/Config.groovy @@ -0,0 +1,79 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package listener + +import com.google.common.io.Files +import org.hornetq.api.core.TransportConfiguration +import org.hornetq.api.core.client.HornetQClient +import org.hornetq.api.jms.HornetQJMSClient +import org.hornetq.api.jms.JMSFactoryType +import org.hornetq.core.config.CoreQueueConfiguration +import org.hornetq.core.config.impl.ConfigurationImpl +import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory +import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory +import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory +import org.hornetq.core.server.HornetQServers +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration +import org.springframework.jms.annotation.EnableJms +import org.springframework.jms.config.DefaultJmsListenerContainerFactory +import org.springframework.jms.config.JmsListenerContainerFactory + +import javax.jms.ConnectionFactory + +@Configuration +@ComponentScan +@EnableJms +class Config { + + @Bean + ConnectionFactory connectionFactory() { + def tempDir = Files.createTempDir() + tempDir.deleteOnExit() + + org.hornetq.core.config.Configuration config = new ConfigurationImpl() + config.bindingsDirectory = tempDir.path + config.journalDirectory = tempDir.path + config.createBindingsDir = false + config.createJournalDir = false + config.securityEnabled = false + config.persistenceEnabled = false + config.setQueueConfigurations([new CoreQueueConfiguration("someQueue", "someQueue", null, true)]) + config.setAcceptorConfigurations([new TransportConfiguration(NettyAcceptorFactory.name), + new TransportConfiguration(InVMAcceptorFactory.name)].toSet()) + + HornetQServers.newHornetQServer(config).start() + + def serverLocator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(InVMConnectorFactory.name)) + def sf = serverLocator.createSessionFactory() + def clientSession = sf.createSession(false, false, false) + clientSession.createQueue("jms.queue.someSpringQueue", "jms.queue.someSpringQueue", true) + clientSession.close() + sf.close() + serverLocator.close() + + return HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, + new TransportConfiguration(InVMConnectorFactory.name)) + } + + @Bean + JmsListenerContainerFactory containerFactory(ConnectionFactory connectionFactory) { + DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory() + factory.setConnectionFactory(connectionFactory) + return factory + } +} diff --git a/dd-java-agent/instrumentation/jms/src/latestDepTest/groovy/listener/TestListener.groovy b/dd-java-agent/instrumentation/jms/src/latestDepTest/groovy/listener/TestListener.groovy new file mode 100644 index 00000000000..6745a463b4d --- /dev/null +++ b/dd-java-agent/instrumentation/jms/src/latestDepTest/groovy/listener/TestListener.groovy @@ -0,0 +1,28 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package listener + +import org.springframework.jms.annotation.JmsListener +import org.springframework.stereotype.Component + +@Component +class TestListener { + + @JmsListener(destination = "someSpringQueue", containerFactory = "containerFactory") + void receiveMessage(String message) { + println "received: " + message + } +} diff --git a/dd-java-agent/instrumentation/jms/src/test/groovy/SpringListenerJMS1Test.groovy b/dd-java-agent/instrumentation/jms/src/test/groovy/SpringListenerJMS1Test.groovy new file mode 100644 index 00000000000..1f739ea04fb --- /dev/null +++ b/dd-java-agent/instrumentation/jms/src/test/groovy/SpringListenerJMS1Test.groovy @@ -0,0 +1,50 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import datadog.trace.agent.test.AgentTestRunner +import listener.Config +import org.apache.activemq.ActiveMQMessageConsumer +import org.apache.activemq.junit.EmbeddedActiveMQBroker +import org.springframework.context.annotation.AnnotationConfigApplicationContext +import org.springframework.jms.core.JmsTemplate +import org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter + +import javax.jms.ConnectionFactory + +import static JMS1Test.consumerTrace +import static JMS1Test.producerTrace + +class SpringListenerJMS1Test extends AgentTestRunner { + + def "receiving message in spring listener generates spans"() { + setup: + def context = new AnnotationConfigApplicationContext(Config) + def factory = context.getBean(ConnectionFactory) + def template = new JmsTemplate(factory) + template.convertAndSend("someSpringQueue", "a message") + + expect: + assertTraces(3) { + producerTrace(it, 0, "Queue someSpringQueue") + consumerTrace(it, 1, "Queue someSpringQueue", false, ActiveMQMessageConsumer) + consumerTrace(it, 2, "Queue someSpringQueue", true, MessagingMessageListenerAdapter) + } + + cleanup: + context.getBean(EmbeddedActiveMQBroker).stop() + } +} diff --git a/dd-java-agent/instrumentation/jms/src/test/groovy/listener/Config.groovy b/dd-java-agent/instrumentation/jms/src/test/groovy/listener/Config.groovy new file mode 100644 index 00000000000..d4e40232b4d --- /dev/null +++ b/dd-java-agent/instrumentation/jms/src/test/groovy/listener/Config.groovy @@ -0,0 +1,51 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package listener + +import org.apache.activemq.junit.EmbeddedActiveMQBroker +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration +import org.springframework.jms.annotation.EnableJms +import org.springframework.jms.config.DefaultJmsListenerContainerFactory +import org.springframework.jms.config.JmsListenerContainerFactory + +import javax.jms.ConnectionFactory + +@Configuration +@ComponentScan +@EnableJms +class Config { + + @Bean + EmbeddedActiveMQBroker broker() { + def broker = new EmbeddedActiveMQBroker() + broker.start() + return broker + } + + @Bean + ConnectionFactory connectionFactory(EmbeddedActiveMQBroker broker) { + return broker.createConnectionFactory() + } + + @Bean + JmsListenerContainerFactory containerFactory(ConnectionFactory connectionFactory) { + DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory() + factory.setConnectionFactory(connectionFactory) + return factory + } +} diff --git a/dd-java-agent/instrumentation/jms/src/test/groovy/listener/TestListener.groovy b/dd-java-agent/instrumentation/jms/src/test/groovy/listener/TestListener.groovy new file mode 100644 index 00000000000..6745a463b4d --- /dev/null +++ b/dd-java-agent/instrumentation/jms/src/test/groovy/listener/TestListener.groovy @@ -0,0 +1,28 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package listener + +import org.springframework.jms.annotation.JmsListener +import org.springframework.stereotype.Component + +@Component +class TestListener { + + @JmsListener(destination = "someSpringQueue", containerFactory = "containerFactory") + void receiveMessage(String message) { + println "received: " + message + } +} From 279af696f4029b3163d66d34458b85dd04012e0d Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 9 Mar 2020 09:30:31 -0700 Subject: [PATCH 2/2] Improve test reliability --- .../latestDepTest/groovy/SpringListenerJMS2Test.groovy | 8 ++++++++ .../jms/src/test/groovy/SpringListenerJMS1Test.groovy | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/dd-java-agent/instrumentation/jms/src/latestDepTest/groovy/SpringListenerJMS2Test.groovy b/dd-java-agent/instrumentation/jms/src/latestDepTest/groovy/SpringListenerJMS2Test.groovy index 91327eb4f7a..7693f30fc17 100644 --- a/dd-java-agent/instrumentation/jms/src/latestDepTest/groovy/SpringListenerJMS2Test.groovy +++ b/dd-java-agent/instrumentation/jms/src/latestDepTest/groovy/SpringListenerJMS2Test.groovy @@ -36,6 +36,14 @@ class SpringListenerJMS2Test extends AgentTestRunner { def template = new JmsTemplate(factory) template.convertAndSend("someSpringQueue", "a message") + TEST_WRITER.waitForTraces(3) + // Manually reorder if reported in the wrong order. + if (TEST_WRITER[1][0].operationName == "jms.produce") { + def producerTrace = TEST_WRITER[1] + TEST_WRITER[1] = TEST_WRITER[0] + TEST_WRITER[0] = producerTrace + } + expect: assertTraces(3) { producerTrace(it, 0, "Queue someSpringQueue") diff --git a/dd-java-agent/instrumentation/jms/src/test/groovy/SpringListenerJMS1Test.groovy b/dd-java-agent/instrumentation/jms/src/test/groovy/SpringListenerJMS1Test.groovy index 1f739ea04fb..2b3cfb8bc17 100644 --- a/dd-java-agent/instrumentation/jms/src/test/groovy/SpringListenerJMS1Test.groovy +++ b/dd-java-agent/instrumentation/jms/src/test/groovy/SpringListenerJMS1Test.groovy @@ -37,6 +37,14 @@ class SpringListenerJMS1Test extends AgentTestRunner { def template = new JmsTemplate(factory) template.convertAndSend("someSpringQueue", "a message") + TEST_WRITER.waitForTraces(3) + // Manually reorder if reported in the wrong order. + if (TEST_WRITER[1][0].operationName == "jms.produce") { + def producerTrace = TEST_WRITER[1] + TEST_WRITER[1] = TEST_WRITER[0] + TEST_WRITER[0] = producerTrace + } + expect: assertTraces(3) { producerTrace(it, 0, "Queue someSpringQueue")