This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-java.git
The following commit(s) were added to refs/heads/main by this push:
new 233effa70f Add Spring RabbitMQ plugin (#796)
233effa70f is described below
commit 233effa70f86f3d6d53dfc4b8a4aa4817143e58c
Author: liuhaolong10 <[email protected]>
AuthorDate: Fri Mar 13 10:37:13 2026 +0800
Add Spring RabbitMQ plugin (#796)
---
.github/workflows/plugins-jdk17-test.0.yaml | 1 +
CHANGES.md | 1 +
.../rabbitmq/RabbitMQConsumerInterceptor.java | 9 +
apm-sniffer/apm-sdk-plugin/spring-plugins/pom.xml | 3 +-
.../spring-plugins/spring-rabbitmq-plugin/pom.xml | 46 +++++
.../SpringRabbitMQConsumerInterceptor.java | 146 ++++++++++++++++
.../SpringRabbitMQConsumerInstrumentation.java | 69 ++++++++
.../src/main/resources/skywalking-plugin.def | 17 ++
.../RabbitMQSpringConsumerInterceptorTest.java | 192 +++++++++++++++++++++
.../setup/service-agent/java-agent/Plugin-list.md | 1 +
.../service-agent/java-agent/Supported-list.md | 1 +
.../spring-rabbitmq-scenario/bin/startup.sh | 20 +++
.../config/expectedData.yaml | 165 ++++++++++++++++++
.../spring-rabbitmq-scenario/configuration.yml | 33 ++++
.../scenarios/spring-rabbitmq-scenario/pom.xml | 117 +++++++++++++
.../src/main/assembly/assembly.xml | 41 +++++
.../apm/testcase/spring/rabbitmq/Application.java | 30 ++++
.../spring/rabbitmq/config/RabbitMqConfig.java | 54 ++++++
.../spring/rabbitmq/controller/CaseController.java | 85 +++++++++
.../src/main/resources/application.yml | 29 ++++
.../src/main/resources/log4j2.xml | 31 ++++
.../spring-rabbitmq-scenario/support-version.list | 18 ++
22 files changed, 1108 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/plugins-jdk17-test.0.yaml
b/.github/workflows/plugins-jdk17-test.0.yaml
index 092fa2daae..74ae79f0fe 100644
--- a/.github/workflows/plugins-jdk17-test.0.yaml
+++ b/.github/workflows/plugins-jdk17-test.0.yaml
@@ -80,6 +80,7 @@ jobs:
- jetty-11.x-scenario
- jetty-10.x-scenario
- spring-ai-1.x-scenario
+ - spring-rabbitmq-scenario
steps:
- uses: actions/checkout@v2
with:
diff --git a/CHANGES.md b/CHANGES.md
index 47b9ac0d94..e49c314aab 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -8,6 +8,7 @@ Release Notes.
* Added support for Lettuce reactive Redis commands.
* Add Spring AI 1.x plugin and GenAI layer.
* Fix httpclient-5.x plugin injecting sw8 propagation headers into ClickHouse
HTTP requests (port 8123), causing HTTP 400. Add `PROPAGATION_EXCLUDE_PORTS`
config to skip tracing (including header injection) for specified ports in the
classic client interceptor.
+* Add Spring RabbitMQ 2.x - 4.x plugin.
All issues and pull requests are
[here](https://github.com/apache/skywalking/milestone/249?closed=1)
diff --git
a/apm-sniffer/apm-sdk-plugin/rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/rabbitmq/RabbitMQConsumerInterceptor.java
b/apm-sniffer/apm-sdk-plugin/rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/rabbitmq/RabbitMQConsumerInterceptor.java
index 50240c9729..3ac0caf121 100644
---
a/apm-sniffer/apm-sdk-plugin/rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/rabbitmq/RabbitMQConsumerInterceptor.java
+++
b/apm-sniffer/apm-sdk-plugin/rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/rabbitmq/RabbitMQConsumerInterceptor.java
@@ -26,10 +26,19 @@ import
org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInt
public class RabbitMQConsumerInterceptor implements
InstanceMethodsAroundInterceptor {
+ public static final String SMLC_INTERNAL_CONSUMER =
"org.springframework.amqp.rabbit.listener.BlockingQueueConsumer$InternalConsumer";
+ public static final String DMLC_INTERNAL_CONSUMER =
"org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer$SimpleConsumer";
+
@Override
public void beforeMethod(EnhancedInstance objInst, Method method, Object[]
allArguments, Class<?>[] argumentsTypes,
MethodInterceptResult result) throws Throwable {
Consumer consumer = (Consumer) allArguments[6];
+ if (consumer != null) {
+ String className = consumer.getClass().getName();
+ if (SMLC_INTERNAL_CONSUMER.equals(className) ||
DMLC_INTERNAL_CONSUMER.equals(className)) {
+ return;
+ }
+ }
allArguments[6] = new TracerConsumer(consumer, (String)
objInst.getSkyWalkingDynamicField());
}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/pom.xml
b/apm-sniffer/apm-sdk-plugin/spring-plugins/pom.xml
index 5a307810b3..4326ec7354 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/pom.xml
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/pom.xml
@@ -46,10 +46,11 @@
<module>spring-webflux-6.x-webclient-plugin</module>
<module>resttemplate-commons</module>
<module>spring-ai-1.x-plugin</module>
+ <module>spring-rabbitmq-plugin</module>
</modules>
<packaging>pom</packaging>
- <name>apm-sdk-plugin</name>
+ <name>spring-plugins</name>
<url>http://maven.apache.org</url>
<properties>
diff --git
a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/pom.xml
b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/pom.xml
new file mode 100644
index 0000000000..cb0c687268
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You 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.
+ ~
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.skywalking</groupId>
+ <artifactId>spring-plugins</artifactId>
+ <version>9.7.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>apm-spring-rabbitmq-plugin</artifactId>
+ <name>apm-spring-rabbitmq-plugin</name>
+ <packaging>jar</packaging>
+
+ <properties>
+ <spring-rabbit.version>3.0.0</spring-rabbit.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.amqp</groupId>
+ <artifactId>spring-rabbit</artifactId>
+ <version>${spring-rabbit.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
diff --git
a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/rabbitmq/SpringRabbitMQConsumerInterceptor.java
b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/rabbitmq/SpringRabbitMQConsumerInterceptor.java
new file mode 100644
index 0000000000..29bf40599f
--- /dev/null
+++
b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/rabbitmq/SpringRabbitMQConsumerInterceptor.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.rabbitmq;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.skywalking.apm.agent.core.context.CarrierItem;
+import org.apache.skywalking.apm.agent.core.context.ContextCarrier;
+import org.apache.skywalking.apm.agent.core.context.ContextManager;
+import org.apache.skywalking.apm.agent.core.context.tag.Tags;
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
+import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.InstanceMethodsAroundInterceptorV2;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext;
+import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
+import org.springframework.amqp.core.Message;
+import org.springframework.amqp.core.MessageProperties;
+
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+
+public class SpringRabbitMQConsumerInterceptor implements
InstanceMethodsAroundInterceptorV2 {
+ public static final String OPERATE_NAME_PREFIX = "RabbitMQ/";
+ public static final String CONSUMER_OPERATE_NAME_SUFFIX = "/Consumer";
+
+ @Override
+ public void beforeMethod(EnhancedInstance objInst, Method method, Object[]
allArguments, Class<?>[] argumentsTypes,
+ MethodInvocationContext context) throws Throwable {
+ Channel channel = (Channel) allArguments[0];
+
+ if (allArguments[1] instanceof Message) {
+ // Single message consume
+ Message message = (Message) allArguments[1];
+ MessageProperties messageProperties =
message.getMessageProperties();
+ Map<String, Object> headers = messageProperties.getHeaders();
+
+ ContextCarrier contextCarrier = buildContextCarrier(headers);
+ String operationName = buildOperationName(messageProperties);
+ AbstractSpan activeSpan =
ContextManager.createEntrySpan(operationName, contextCarrier);
+
+ setSpanAttributes(activeSpan, channel, messageProperties);
+ } else if (allArguments[1] instanceof List) {
+ // Batch message consume
+ List<?> messages = (List<?>) allArguments[1];
+ if (messages.isEmpty()) {
+ return;
+ }
+
+ // Use the first message to create EntrySpan
+ Message firstMessage = (Message) messages.get(0);
+ MessageProperties firstMessageProperties =
firstMessage.getMessageProperties();
+ Map<String, Object> firstMessageHeaders =
firstMessageProperties.getHeaders();
+
+ ContextCarrier contextCarrier =
buildContextCarrier(firstMessageHeaders);
+ String operationName = buildOperationName(firstMessageProperties);
+ AbstractSpan activeSpan =
ContextManager.createEntrySpan(operationName, contextCarrier);
+
+ setSpanAttributes(activeSpan, channel, firstMessageProperties);
+
+ // Extract trace context from remaining messages (skip first,
already used for EntrySpan)
+ // to correlate all producer traces with this consumer span
+ for (int i = 1; i < messages.size(); i++) {
+ Object msg = messages.get(i);
+ if (msg instanceof Message) {
+ Message message = (Message) msg;
+ MessageProperties messageProperties =
message.getMessageProperties();
+ Map<String, Object> headers =
messageProperties.getHeaders();
+
+ ContextCarrier carrier = buildContextCarrier(headers);
+ if (carrier.isValid()) {
+ ContextManager.extract(carrier);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Build ContextCarrier from message headers
+ */
+ private ContextCarrier buildContextCarrier(Map<String, Object> headers) {
+ ContextCarrier contextCarrier = new ContextCarrier();
+ CarrierItem next = contextCarrier.items();
+ while (next.hasNext()) {
+ next = next.next();
+ Object value = headers.get(next.getHeadKey());
+ if (value != null) {
+ next.setHeadValue(value.toString());
+ }
+ }
+ return contextCarrier;
+ }
+
+ private String buildOperationName(MessageProperties messageProperties) {
+ return OPERATE_NAME_PREFIX + "Topic/" +
messageProperties.getReceivedExchange()
+ + "Queue/" + messageProperties.getReceivedRoutingKey()
+ + CONSUMER_OPERATE_NAME_SUFFIX;
+ }
+
+ private void setSpanAttributes(AbstractSpan span, Channel channel,
MessageProperties messageProperties) {
+ Connection connection = channel.getConnection();
+ String serverUrl = connection.getAddress().getHostAddress() + ":" +
connection.getPort();
+ Tags.MQ_BROKER.set(span, serverUrl);
+ Tags.MQ_TOPIC.set(span, messageProperties.getReceivedExchange());
+ Tags.MQ_QUEUE.set(span, messageProperties.getReceivedRoutingKey());
+ span.setComponent(ComponentsDefine.RABBITMQ_CONSUMER);
+ span.setPeer(serverUrl);
+ SpanLayer.asMQ(span);
+ }
+
+ @Override
+ public Object afterMethod(EnhancedInstance objInst, Method method,
Object[] allArguments, Class<?>[] argumentsTypes,
+ Object ret, MethodInvocationContext context) throws Throwable {
+ if (ContextManager.isActive()) {
+ ContextManager.stopSpan();
+ }
+ return ret;
+ }
+
+ @Override
+ public void handleMethodException(EnhancedInstance objInst, Method method,
Object[] allArguments,
+ Class<?>[] argumentsTypes, Throwable t, MethodInvocationContext
context) {
+ if (ContextManager.isActive()) {
+ ContextManager.activeSpan().log(t);
+ }
+ }
+}
diff --git
a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/rabbitmq/define/SpringRabbitMQConsumerInstrumentation.java
b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/rabbitmq/define/SpringRabbitMQConsumerInstrumentation.java
new file mode 100644
index 0000000000..b4a541adc8
--- /dev/null
+++
b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/rabbitmq/define/SpringRabbitMQConsumerInstrumentation.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.rabbitmq.define;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.ClassInstanceMethodsEnhancePluginDefineV2;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.v2.DeclaredInstanceMethodsInterceptV2Point;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.v2.InstanceMethodsInterceptV2Point;
+import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
+import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class SpringRabbitMQConsumerInstrumentation extends
ClassInstanceMethodsEnhancePluginDefineV2 {
+ public static final String ENHANCE_CLASS =
"org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer";
+ public static final String ENHANCE_METHOD = "executeListener";
+ public static final String INTERCEPTOR_CLASS =
"org.apache.skywalking.apm.plugin.spring.rabbitmq.SpringRabbitMQConsumerInterceptor";
+
+ @Override
+ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
+ return null;
+ }
+
+ @Override
+ public InstanceMethodsInterceptV2Point[]
getInstanceMethodsInterceptV2Points() {
+ return new InstanceMethodsInterceptV2Point[] {
+ new DeclaredInstanceMethodsInterceptV2Point() {
+ @Override
+ public ElementMatcher<MethodDescription> getMethodsMatcher() {
+ return named(ENHANCE_METHOD);
+ }
+
+ @Override
+ public String getMethodsInterceptorV2() {
+ return INTERCEPTOR_CLASS;
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return false;
+ }
+ }
+ };
+ }
+
+ @Override
+ protected ClassMatch enhanceClass() {
+ return NameMatch.byName(ENHANCE_CLASS);
+ }
+}
diff --git
a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/main/resources/skywalking-plugin.def
b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/main/resources/skywalking-plugin.def
new file mode 100644
index 0000000000..94d87de414
--- /dev/null
+++
b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/main/resources/skywalking-plugin.def
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+spring-rabbitmq=org.apache.skywalking.apm.plugin.spring.rabbitmq.define.SpringRabbitMQConsumerInstrumentation
\ No newline at end of file
diff --git
a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/rabbitmq/RabbitMQSpringConsumerInterceptorTest.java
b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/rabbitmq/RabbitMQSpringConsumerInterceptorTest.java
new file mode 100644
index 0000000000..e29864204e
--- /dev/null
+++
b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-rabbitmq-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/rabbitmq/RabbitMQSpringConsumerInterceptorTest.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.spring.rabbitmq;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.skywalking.apm.agent.core.context.SW8CarrierItem;
+import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment;
+import
org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule;
+import org.apache.skywalking.apm.agent.test.tools.SegmentStorage;
+import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint;
+import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.springframework.amqp.core.Message;
+import org.springframework.amqp.core.MessageProperties;
+
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+
+@RunWith(TracingSegmentRunner.class)
+public class RabbitMQSpringConsumerInterceptorTest {
+
+ @SegmentStoragePoint
+ private SegmentStorage segmentStorage;
+
+ private SpringRabbitMQConsumerInterceptor rabbitMQConsumerInterceptor;
+
+ @Mock
+ private EnhancedInstance enhancedInstance;
+
+ @Rule
+ public AgentServiceRule serviceRule = new AgentServiceRule();
+
+ @Before
+ public void setUp() throws Exception {
+ rabbitMQConsumerInterceptor = new SpringRabbitMQConsumerInterceptor();
+ }
+
+ @Test
+ public void testRabbitMQConsumerInterceptorWithNilHeaders() throws
Throwable {
+ Object[] args = prepareMockData(false);
+ rabbitMQConsumerInterceptor.beforeMethod(enhancedInstance, null, args,
new Class[0], null);
+ rabbitMQConsumerInterceptor.afterMethod(enhancedInstance, null, args,
new Class[0], null, null);
+ List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
+ Assert.assertThat(traceSegments.size(), is(1));
+ }
+
+ @Test
+ public void testRabbitMQConsumerInterceptor() throws Throwable {
+ Object[] args = prepareMockData(true);
+ rabbitMQConsumerInterceptor.beforeMethod(enhancedInstance, null, args,
new Class[0], null);
+ rabbitMQConsumerInterceptor.afterMethod(enhancedInstance, null, args,
new Class[0], null, null);
+ List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
+ Assert.assertThat(traceSegments.size(), is(1));
+ }
+
+ @Test
+ public void testBatchMessageConsumption() throws Throwable {
+ Object[] args = prepareMockBatchData(true);
+ rabbitMQConsumerInterceptor.beforeMethod(enhancedInstance, null, args,
new Class[0], null);
+ rabbitMQConsumerInterceptor.afterMethod(enhancedInstance, null, args,
new Class[0], null, null);
+ List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
+ Assert.assertThat(traceSegments.size(), is(1));
+ }
+
+ @Test
+ public void testEmptyBatchMessageConsumption() throws Throwable {
+ Channel channel = mock(Channel.class);
+ Connection connection = mock(Connection.class);
+ InetAddress address = mock(InetAddress.class);
+
+ when(channel.getConnection()).thenReturn(connection);
+ when(connection.getAddress()).thenReturn(address);
+ when(address.getHostAddress()).thenReturn("127.0.0.1");
+ when(connection.getPort()).thenReturn(5672);
+
+ Object[] args = new Object[] {channel, new
java.util.ArrayList<Message>()};
+ rabbitMQConsumerInterceptor.beforeMethod(enhancedInstance, null, args,
new Class[0], null);
+ rabbitMQConsumerInterceptor.afterMethod(enhancedInstance, null, args,
new Class[0], null, null);
+ List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
+ Assert.assertThat(traceSegments.size(), is(0));
+ }
+
+ private Object[] prepareMockData(boolean withHeaders) throws Exception {
+ Channel channel = mock(Channel.class);
+ Connection connection = mock(Connection.class);
+ InetAddress address = mock(InetAddress.class);
+ Message message = mock(Message.class);
+ MessageProperties messageProperties = mock(MessageProperties.class);
+
+ when(channel.getConnection()).thenReturn(connection);
+ when(connection.getAddress()).thenReturn(address);
+ when(address.getHostAddress()).thenReturn("127.0.0.1");
+ when(connection.getPort()).thenReturn(5672);
+ when(message.getMessageProperties()).thenReturn(messageProperties);
+
when(messageProperties.getReceivedExchange()).thenReturn("test-exchange");
+
when(messageProperties.getReceivedRoutingKey()).thenReturn("test-routing-key");
+
+ if (withHeaders) {
+ Map<String, Object> headers = new HashMap<>();
+ headers.put(SW8CarrierItem.HEADER_NAME,
+
"1-My40LjU=-MS4yLjM=-3-c2VydmljZQ==-aW5zdGFuY2U=-L2FwcA==-MTI3LjAuMC4xOjgwODA=");
+ when(messageProperties.getHeader(SW8CarrierItem.HEADER_NAME))
+ .thenReturn(headers.get(SW8CarrierItem.HEADER_NAME));
+ }
+
+ return new Object[] {channel, message};
+ }
+
+ private Object[] prepareMockBatchData(boolean withHeaders) throws
Exception {
+ Channel channel = mock(Channel.class);
+ Connection connection = mock(Connection.class);
+ InetAddress address = mock(InetAddress.class);
+
+ when(channel.getConnection()).thenReturn(connection);
+ when(connection.getAddress()).thenReturn(address);
+ when(address.getHostAddress()).thenReturn("127.0.0.1");
+ when(connection.getPort()).thenReturn(5672);
+
+ Message message1 = mock(Message.class);
+ Message message2 = mock(Message.class);
+ Message message3 = mock(Message.class);
+ MessageProperties props1 = mock(MessageProperties.class);
+ MessageProperties props2 = mock(MessageProperties.class);
+ MessageProperties props3 = mock(MessageProperties.class);
+
+ when(message1.getMessageProperties()).thenReturn(props1);
+ when(message2.getMessageProperties()).thenReturn(props2);
+ when(message3.getMessageProperties()).thenReturn(props3);
+
+ when(props1.getReceivedExchange()).thenReturn("test-exchange");
+ when(props1.getReceivedRoutingKey()).thenReturn("test-routing-key");
+ when(props2.getReceivedExchange()).thenReturn("test-exchange");
+ when(props2.getReceivedRoutingKey()).thenReturn("test-routing-key");
+ when(props3.getReceivedExchange()).thenReturn("test-exchange");
+ when(props3.getReceivedRoutingKey()).thenReturn("test-routing-key");
+
+ if (withHeaders) {
+ Map<String, Object> headers1 = new HashMap<>();
+ headers1.put(SW8CarrierItem.HEADER_NAME,
+
"1-My40LjU=-MS4yLjM=-3-c2VydmljZQ==-aW5zdGFuY2U=-L2FwcA==-MTI3LjAuMC4xOjgwODA=");
+ when(props1.getHeader(SW8CarrierItem.HEADER_NAME))
+ .thenReturn(headers1.get(SW8CarrierItem.HEADER_NAME));
+
+ Map<String, Object> headers2 = new HashMap<>();
+ headers2.put(SW8CarrierItem.HEADER_NAME,
+
"1-NTY3Ljg=-OS4xMC4xMQ==-12-ZXJ2aWNlMg==-aW5zdGFuY2UyLU9hcHA=-MTI3LjAuMC4yOjgwODA=");
+ when(props2.getHeader(SW8CarrierItem.HEADER_NAME))
+ .thenReturn(headers2.get(SW8CarrierItem.HEADER_NAME));
+
+ Map<String, Object> headers3 = new HashMap<>();
+ headers3.put(SW8CarrierItem.HEADER_NAME,
+
"1-MTExLjIyMi4zMzM=-NDQ0LjU1NS42NjY=-MzQ1-ZXJ2aWNlMw==-aW5zdGFuY2UzLU9hcHA=-MTI3LjAuMC4zOjgwODA=");
+ when(props3.getHeader(SW8CarrierItem.HEADER_NAME))
+ .thenReturn(headers3.get(SW8CarrierItem.HEADER_NAME));
+ }
+
+ List<Message> messages = new ArrayList<>(Arrays.asList(message1,
message2, message3));
+ return new Object[] {channel, messages};
+ }
+}
diff --git a/docs/en/setup/service-agent/java-agent/Plugin-list.md
b/docs/en/setup/service-agent/java-agent/Plugin-list.md
index 49d1cb9c8e..3764ae9716 100644
--- a/docs/en/setup/service-agent/java-agent/Plugin-list.md
+++ b/docs/en/setup/service-agent/java-agent/Plugin-list.md
@@ -114,6 +114,7 @@
- spring-core-patch
- spring-kafka-1.x
- spring-kafka-2.x
+- spring-rabbitmq
- spring-mvc-annotation
- spring-mvc-annotation-3.x
- spring-mvc-annotation-4.x
diff --git a/docs/en/setup/service-agent/java-agent/Supported-list.md
b/docs/en/setup/service-agent/java-agent/Supported-list.md
index cdadd27422..1645c43b4e 100644
--- a/docs/en/setup/service-agent/java-agent/Supported-list.md
+++ b/docs/en/setup/service-agent/java-agent/Supported-list.md
@@ -80,6 +80,7 @@ metrics based on the tracing data.
* [Spring-Kafka](https://github.com/spring-projects/spring-kafka) Spring
Kafka Consumer 1.3.x -> 2.3.x (2.0.x and 2.1.x not tested and not recommended
by [the official document](https://spring.io/projects/spring-kafka))
* [ActiveMQ](https://github.com/apache/activemq) 5.10.0 -> 5.15.4
* [RabbitMQ](https://www.rabbitmq.com/) 3.x-> 5.x
+ * [Spring-RabbitMQ](https://github.com/spring-projects/spring-amqp) 2.x ->
4.x
* [Pulsar](http://pulsar.apache.org) 2.2.x -> 2.9.x
* [NATS](https://github.com/nats-io/nats.java) 2.14.x -> 2.16.5
* [ActiveMQ-Artemis](https://github.com/apache/activemq) 2.30.0 -> 2.31.2
diff --git a/test/plugin/scenarios/spring-rabbitmq-scenario/bin/startup.sh
b/test/plugin/scenarios/spring-rabbitmq-scenario/bin/startup.sh
new file mode 100644
index 0000000000..f98cd90a75
--- /dev/null
+++ b/test/plugin/scenarios/spring-rabbitmq-scenario/bin/startup.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+home="$(cd "$(dirname $0)"; pwd)"
+
+java -Dspring.rabbitmq.host=${RABBITMQ_HOST:-rabbitmq-server} -jar
${agent_opts} ${home}/../libs/spring-rabbitmq-scenario.jar &
diff --git
a/test/plugin/scenarios/spring-rabbitmq-scenario/config/expectedData.yaml
b/test/plugin/scenarios/spring-rabbitmq-scenario/config/expectedData.yaml
new file mode 100644
index 0000000000..a9c4a431a2
--- /dev/null
+++ b/test/plugin/scenarios/spring-rabbitmq-scenario/config/expectedData.yaml
@@ -0,0 +1,165 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+segmentItems:
+- serviceName: spring-rabbitmq-scenario
+ segmentSize: ge 4
+ segments:
+ - segmentId: not null
+ spans:
+ - operationName: HEAD:/spring-rabbitmq-scenario/case/healthcheck
+ parentSpanId: -1
+ spanId: 0
+ spanLayer: Http
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 1
+ isError: false
+ spanType: Entry
+ peer: ''
+ tags:
+ - {key: url, value:
'http://localhost:8080/spring-rabbitmq-scenario/case/healthcheck'}
+ - {key: http.method, value: HEAD}
+ - {key: http.status_code, value: '200'}
+ skipAnalysis: 'false'
+ - segmentId: not null
+ spans:
+ - operationName: RabbitMQ/Topic/Queue/test/Producer
+ parentSpanId: 0
+ spanId: 1
+ spanLayer: MQ
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 52
+ isError: false
+ spanType: Exit
+ peer: not blank
+ tags:
+ - {key: mq.broker, value: not blank}
+ - {key: mq.queue, value: test}
+ - {key: mq.topic, value: ''}
+ skipAnalysis: 'false'
+ - operationName: RabbitMQ/Topic/Queue/test-batch/Producer
+ parentSpanId: 0
+ spanId: 2
+ spanLayer: MQ
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 52
+ isError: false
+ spanType: Exit
+ peer: not blank
+ tags:
+ - {key: mq.broker, value: not blank}
+ - {key: mq.queue, value: test-batch}
+ - {key: mq.topic, value: ''}
+ skipAnalysis: 'false'
+ - operationName: RabbitMQ/Topic/Queue/test-batch/Producer
+ parentSpanId: 0
+ spanId: 3
+ spanLayer: MQ
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 52
+ isError: false
+ spanType: Exit
+ peer: not blank
+ tags:
+ - {key: mq.broker, value: not blank}
+ - {key: mq.queue, value: test-batch}
+ - {key: mq.topic, value: ''}
+ skipAnalysis: 'false'
+ - operationName: RabbitMQ/Topic/Queue/test-batch/Producer
+ parentSpanId: 0
+ spanId: 4
+ spanLayer: MQ
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 52
+ isError: false
+ spanType: Exit
+ peer: not blank
+ tags:
+ - {key: mq.broker, value: not blank}
+ - {key: mq.queue, value: test-batch}
+ - {key: mq.topic, value: ''}
+ skipAnalysis: 'false'
+ - operationName: GET:/spring-rabbitmq-scenario/case/rabbitmq
+ parentSpanId: -1
+ spanId: 0
+ spanLayer: Http
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 1
+ isError: false
+ spanType: Entry
+ peer: ''
+ tags:
+ - {key: url, value:
'http://localhost:8080/spring-rabbitmq-scenario/case/rabbitmq'}
+ - {key: http.method, value: GET}
+ - {key: http.status_code, value: '200'}
+ skipAnalysis: 'false'
+ - segmentId: not null
+ spans:
+ - operationName: RabbitMQ/Topic/Queue/test-batch/Consumer
+ parentSpanId: -1
+ spanId: 0
+ spanLayer: MQ
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 53
+ isError: false
+ spanType: Entry
+ peer: not blank
+ tags:
+ - {key: transmission.latency, value: ge 0}
+ - {key: mq.broker, value: not blank}
+ - {key: mq.topic, value: ''}
+ - {key: mq.queue, value: test-batch}
+ - {key: transmission.latency, value: ge 0}
+ - {key: transmission.latency, value: ge 0}
+ refs:
+ - {parentEndpoint: GET:/spring-rabbitmq-scenario/case/rabbitmq,
networkAddress: not null,
+ refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not
null, parentServiceInstance: not
+ null, parentService: spring-rabbitmq-scenario, traceId: not null}
+ - {parentEndpoint: GET:/spring-rabbitmq-scenario/case/rabbitmq,
networkAddress: not null,
+ refType: CrossProcess, parentSpanId: 3, parentTraceSegmentId: not
null, parentServiceInstance: not
+ null, parentService: spring-rabbitmq-scenario, traceId: not null}
+ - {parentEndpoint: GET:/spring-rabbitmq-scenario/case/rabbitmq,
networkAddress: not null,
+ refType: CrossProcess, parentSpanId: 4, parentTraceSegmentId: not
null, parentServiceInstance: not
+ null, parentService: spring-rabbitmq-scenario, traceId: not null}
+ skipAnalysis: 'false'
+ - segmentId: not null
+ spans:
+ - operationName: RabbitMQ/Topic/Queue/test/Consumer
+ parentSpanId: -1
+ spanId: 0
+ spanLayer: MQ
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 53
+ isError: false
+ spanType: Entry
+ peer: not blank
+ tags:
+ - {key: transmission.latency, value: ge 0}
+ - {key: mq.broker, value: not blank}
+ - {key: mq.topic, value: ''}
+ - {key: mq.queue, value: test}
+ refs:
+ - {parentEndpoint: GET:/spring-rabbitmq-scenario/case/rabbitmq,
networkAddress: not null,
+ refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not
null, parentServiceInstance: not
+ null, parentService: spring-rabbitmq-scenario, traceId: not null}
+ skipAnalysis: 'false'
diff --git a/test/plugin/scenarios/spring-rabbitmq-scenario/configuration.yml
b/test/plugin/scenarios/spring-rabbitmq-scenario/configuration.yml
new file mode 100644
index 0000000000..31cb305bd1
--- /dev/null
+++ b/test/plugin/scenarios/spring-rabbitmq-scenario/configuration.yml
@@ -0,0 +1,33 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+type: jvm
+entryService: http://localhost:8080/spring-rabbitmq-scenario/case/rabbitmq
+healthCheck: http://localhost:8080/spring-rabbitmq-scenario/case/healthcheck
+startScript: ./bin/startup.sh
+environment:
+ - RABBITMQ_HOST=rabbitmq-server
+depends_on:
+ - rabbitmq-server
+dependencies:
+ rabbitmq-server:
+ image: rabbitmq:3.8.18
+ hostname: rabbitmq-server
+ expose:
+ - 5672
+ - 15672
+ environment:
+ - RABBITMQ_DEFAULT_PASS=admin
+ - RABBITMQ_DEFAULT_USER=admin
+ - RABBITMQ_DEFAULT_VHOST=/
diff --git a/test/plugin/scenarios/spring-rabbitmq-scenario/pom.xml
b/test/plugin/scenarios/spring-rabbitmq-scenario/pom.xml
new file mode 100644
index 0000000000..0bf98a4869
--- /dev/null
+++ b/test/plugin/scenarios/spring-rabbitmq-scenario/pom.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You 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.
+ ~
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.apache.skywalking</groupId>
+ <artifactId>spring-rabbitmq-scenario</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <compiler.version>17</compiler.version>
+ <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
+ <test.framework.version>3.0.0</test.framework.version>
+ <docker.image.version>${test.framework.version}</docker.image.version>
+ <log4j.version>2.19.0</log4j.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-amqp</artifactId>
+ <version>${test.framework.version}</version>
+ </dependency>
+ <!-- Spring Boot-->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter</artifactId>
+ <version>${test.framework.version}</version>
+ <exclusions>
+ <exclusion>
+ <artifactId>spring-boot-starter-logging</artifactId>
+ <groupId>org.springframework.boot</groupId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ <version>${test.framework.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ <version>${log4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <version>${log4j.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <finalName>spring-rabbitmq-scenario</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <version>${test.framework.version}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>repackage</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>${maven-compiler-plugin.version}</version>
+ <configuration>
+ <source>${compiler.version}</source>
+ <target>${compiler.version}</target>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>assemble</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptors>
+
<descriptor>src/main/assembly/assembly.xml</descriptor>
+ </descriptors>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
\ No newline at end of file
diff --git
a/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/assembly/assembly.xml
b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/assembly/assembly.xml
new file mode 100644
index 0000000000..0e51e5c531
--- /dev/null
+++
b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/assembly/assembly.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You 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.
+ ~
+ -->
+<assembly
+
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2
http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+ <formats>
+ <format>zip</format>
+ </formats>
+
+ <fileSets>
+ <fileSet>
+ <directory>./bin</directory>
+ <fileMode>0775</fileMode>
+ </fileSet>
+ </fileSets>
+
+ <files>
+ <file>
+
<source>${project.build.directory}/spring-rabbitmq-scenario.jar</source>
+ <outputDirectory>./libs</outputDirectory>
+ <fileMode>0775</fileMode>
+ </file>
+ </files>
+</assembly>
diff --git
a/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/rabbitmq/Application.java
b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/rabbitmq/Application.java
new file mode 100644
index 0000000000..9001ed22a9
--- /dev/null
+++
b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/rabbitmq/Application.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.spring.rabbitmq;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
diff --git
a/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/rabbitmq/config/RabbitMqConfig.java
b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/rabbitmq/config/RabbitMqConfig.java
new file mode 100644
index 0000000000..a0de5ee428
--- /dev/null
+++
b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/rabbitmq/config/RabbitMqConfig.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.spring.rabbitmq.config;
+
+import org.springframework.amqp.core.Queue;
+import
org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
+import org.springframework.amqp.rabbit.connection.ConnectionFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class RabbitMqConfig {
+
+ @Bean
+ public Queue testQueue() {
+ return new Queue("test", false);
+ }
+
+ @Bean
+ public Queue testBatchQueue() {
+ return new Queue("test-batch", false);
+ }
+
+ @Bean
+ public SimpleRabbitListenerContainerFactory
batchRabbitListenerContainerFactory(
+ ConnectionFactory connectionFactory) {
+ SimpleRabbitListenerContainerFactory factory = new
SimpleRabbitListenerContainerFactory();
+ factory.setConnectionFactory(connectionFactory);
+ // Enable batch listening mode
+ factory.setBatchListener(true);
+ factory.setConsumerBatchEnabled(true);
+ // Set batch size
+ factory.setBatchSize(3);
+ factory.setPrefetchCount(3);
+ factory.setReceiveTimeout(3000L);
+ return factory;
+ }
+}
diff --git
a/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/rabbitmq/controller/CaseController.java
b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/rabbitmq/controller/CaseController.java
new file mode 100644
index 0000000000..e143c51bf9
--- /dev/null
+++
b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/java/org/apache/skywalking/apm/testcase/spring/rabbitmq/controller/CaseController.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.spring.rabbitmq.controller;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.amqp.core.Message;
+import org.springframework.amqp.core.MessageBuilder;
+import org.springframework.amqp.rabbit.annotation.RabbitListener;
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/case")
+public class CaseController {
+
+ private static final Logger LOGGER =
LogManager.getLogger(CaseController.class);
+
+ private static final String QUEUE_NAME = "test";
+ private static final String BATCH_QUEUE_NAME = "test-batch";
+
+ private static final String MESSAGE = "rabbitmq-testcase";
+
+ @Autowired
+ private RabbitTemplate rabbitTemplate;
+
+ @RequestMapping("/rabbitmq")
+ @ResponseBody
+ public String send() {
+ LOGGER.info("Message being published -------------->" + MESSAGE);
+ rabbitTemplate.convertAndSend(QUEUE_NAME, MESSAGE);
+ LOGGER.info("Message has been published-------------->" + MESSAGE);
+
+ // Also send batch messages to test batch consumption
+ LOGGER.info("Sending batch messages --------------");
+ List<Message> messages = new ArrayList<>();
+ for (int i = 0; i < 3; i++) {
+ Message message = MessageBuilder.withBody(("batch-message-" +
i).getBytes()).build();
+ messages.add(message);
+ }
+ for (Message message : messages) {
+ rabbitTemplate.send(BATCH_QUEUE_NAME, message);
+ }
+ LOGGER.info("Batch messages have been published--------------");
+
+ return "Success";
+ }
+
+ @RabbitListener(queues = QUEUE_NAME)
+ public void consumer(String message) {
+ LOGGER.info("Message Consumer received-------------->" + message);
+ }
+
+ @RabbitListener(queues = BATCH_QUEUE_NAME, containerFactory =
"batchRabbitListenerContainerFactory")
+ public void batchConsumer(List<String> messages) {
+ LOGGER.info("Batch Consumer received " + messages.size() + " messages:
" + messages);
+ }
+
+ @RequestMapping("/healthcheck")
+ public String healthCheck() {
+ return "Success";
+ }
+}
diff --git
a/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/resources/application.yml
b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/resources/application.yml
new file mode 100644
index 0000000000..5b93e6c91a
--- /dev/null
+++
b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/resources/application.yml
@@ -0,0 +1,29 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+#
+server:
+ port: 8080
+ servlet:
+ context-path: /spring-rabbitmq-scenario
+
+spring:
+ rabbitmq:
+ host: ${RABBITMQ_HOST:127.0.0.1}
+ port: 5672
+ username: admin
+ password: admin
+ virtual-host: /
diff --git
a/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/resources/log4j2.xml
b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/resources/log4j2.xml
new file mode 100644
index 0000000000..565fda00e0
--- /dev/null
+++
b/test/plugin/scenarios/spring-rabbitmq-scenario/src/main/resources/log4j2.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You 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.
+ ~
+ -->
+
+<Configuration status="WARN">
+ <Appenders>
+ <Console name="Console" target="SYSTEM_ERR">
+ <PatternLayout charset="UTF-8" pattern="[%d{yyyy-MM-dd
HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
+ </Console>
+ </Appenders>
+ <Loggers>
+ <Root level="info">
+ <AppenderRef ref="Console"/>
+ </Root>
+ </Loggers>
+</Configuration>
diff --git
a/test/plugin/scenarios/spring-rabbitmq-scenario/support-version.list
b/test/plugin/scenarios/spring-rabbitmq-scenario/support-version.list
new file mode 100644
index 0000000000..8c67d6f4a8
--- /dev/null
+++ b/test/plugin/scenarios/spring-rabbitmq-scenario/support-version.list
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+3.0.0
+4.0.0
\ No newline at end of file