You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by cl...@apache.org on 2022/03/03 16:09:02 UTC

[activemq-artemis] branch main updated: ARTEMIS-3686: Adding example showing how to do telemetry support

This is an automated email from the ASF dual-hosted git repository.

clebertsuconic pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/activemq-artemis.git


The following commit(s) were added to refs/heads/main by this push:
     new 7bee0d8  ARTEMIS-3686: Adding example showing how to do telemetry support
7bee0d8 is described below

commit 7bee0d8923ae2bd2ed6bafd51e870ee27208cdfa
Author: nbrendah <br...@gmail.com>
AuthorDate: Wed Jan 5 17:52:38 2022 +0300

    ARTEMIS-3686: Adding example showing how to do telemetry support
    
    This is adding an example intercepting and sending messages using opentelemetry to either Jaeger, zipkin or OTLP exporter
---
 .../apache/activemq/artemis/api/core/Message.java  |   6 +
 .../activemq/artemis/api/core/RefCountMessage.java |  18 ++
 .../core/message/impl/MessageInternalImpl.java     |   9 +
 .../core/protocol/openwire/OpenwireMessage.java    |  10 +
 .../server/impl/ScheduledDeliveryHandlerTest.java  |  10 +
 examples/features/standard/opentracing/pom.xml     | 201 +++++++++++++++++++++
 examples/features/standard/opentracing/readme.md   |  68 +++++++
 .../artemis/jms/example/OpenTracingPlugin.java     | 113 ++++++++++++
 .../jms/example/OpenTracingPluginExample.java      | 121 +++++++++++++
 .../src/main/resources/activemq/server0/broker.xml | 199 ++++++++++++++++++++
 .../src/main/resources/tracing.properties          |  37 ++++
 .../artemis/jms/example/OpenTracingPluginTest.java | 134 ++++++++++++++
 examples/features/standard/pom.xml                 |   2 +
 pom.xml                                            |  21 +++
 .../tests/integration/client/AcknowledgeTest.java  |  10 +
 15 files changed, 959 insertions(+)

diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/Message.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/Message.java
index 88f303f..8975159 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/Message.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/Message.java
@@ -801,4 +801,10 @@ public interface Message {
    default String getStringBody() {
       return null;
    }
+
+   /** Used for user context data. Useful on interceptors. */
+   Object getUserContext(Object key);
+
+   /** Used for user context data. Useful on interceptors. */
+   void setUserContext(Object key, Object value);
 }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/RefCountMessage.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/RefCountMessage.java
index 35d591c..0665a78 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/RefCountMessage.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/RefCountMessage.java
@@ -17,6 +17,7 @@
 
 package org.apache.activemq.artemis.api.core;
 
+import java.util.HashMap;
 import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
 
 // import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet; -- #ifdef DEBUG
@@ -27,6 +28,8 @@ public class RefCountMessage {
    private static final AtomicIntegerFieldUpdater<RefCountMessage> REF_COUNT_UPDATER = AtomicIntegerFieldUpdater.newUpdater(RefCountMessage.class, "refCount");
    private static final AtomicIntegerFieldUpdater<RefCountMessage> REF_USAGE_UPDATER = AtomicIntegerFieldUpdater.newUpdater(RefCountMessage.class, "usageCount");
 
+   private volatile HashMap userContext;
+
    private volatile int durableRefCount = 0;
 
    private volatile int refCount = 0;
@@ -170,4 +173,19 @@ public class RefCountMessage {
       return count;
    }
 
+   public Object getUserContext(Object key) {
+      if (userContext == null) {
+         return null;
+      } else {
+         return userContext.get(key);
+      }
+   }
+
+   public void setUserContext(Object key, Object value) {
+      if (userContext == null) {
+         userContext = new HashMap();
+      }
+      userContext.put(key, value);
+   }
+
 }
diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/MessageInternalImpl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/MessageInternalImpl.java
index f7495a9..0f990f0 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/MessageInternalImpl.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/message/impl/MessageInternalImpl.java
@@ -723,4 +723,13 @@ public class MessageInternalImpl implements MessageInternal {
       message.setOwner(object);
    }
 
+   @Override
+   public Object getUserContext(Object key) {
+      return message.getUserContext(key);
+   }
+
+   @Override
+   public void setUserContext(Object key, Object value) {
+      message.setUserContext(key, value);
+   }
 }
diff --git a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenwireMessage.java b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenwireMessage.java
index 96efff8..47eb4e5 100644
--- a/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenwireMessage.java
+++ b/artemis-protocols/artemis-openwire-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/openwire/OpenwireMessage.java
@@ -516,4 +516,14 @@ public class OpenwireMessage implements Message {
    public int usageDown() {
       return 0;
    }
+
+   @Override
+   public Object getUserContext(Object key) {
+      return null;
+   }
+
+   @Override
+   public void setUserContext(Object key, Object value) {
+
+   }
 }
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/ScheduledDeliveryHandlerTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/ScheduledDeliveryHandlerTest.java
index 6532749..9204f49 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/ScheduledDeliveryHandlerTest.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/ScheduledDeliveryHandlerTest.java
@@ -297,6 +297,16 @@ public class ScheduledDeliveryHandlerTest extends Assert {
    class FakeMessage implements Message {
 
       @Override
+      public Object getUserContext(Object key) {
+         return null;
+      }
+
+      @Override
+      public void setUserContext(Object key, Object value) {
+
+      }
+
+      @Override
       public String getProtocolName() {
          // should normally not be visible in GUI
          return getClass().getName();
diff --git a/examples/features/standard/opentracing/pom.xml b/examples/features/standard/opentracing/pom.xml
new file mode 100644
index 0000000..68d519a
--- /dev/null
+++ b/examples/features/standard/opentracing/pom.xml
@@ -0,0 +1,201 @@
+<?xml version='1.0'?>
+<!--
+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/maven-v4_0_0.xsd">
+   <modelVersion>4.0.0</modelVersion>
+
+   <parent>
+      <groupId>org.apache.activemq.examples.broker</groupId>
+      <artifactId>jms-examples</artifactId>
+      <version>2.21.0-SNAPSHOT</version>
+   </parent>
+
+   <artifactId>opentracing</artifactId>
+   <packaging>jar</packaging>
+   <name>ActiveMQ Artemis Broker Plugin Example</name>
+
+   <properties>
+      <activemq.basedir>${project.basedir}/../../../..</activemq.basedir>
+   </properties>
+
+   <dependencies>
+      <dependency>
+         <groupId>org.apache.activemq</groupId>
+         <artifactId>artemis-jms-client-all</artifactId>
+         <version>${project.version}</version>
+      </dependency>
+      <dependency>
+         <groupId>org.apache.activemq</groupId>
+         <artifactId>artemis-server</artifactId>
+         <version>${project.version}</version>
+      </dependency>
+      <dependency>
+         <groupId>org.apache.activemq</groupId>
+         <artifactId>artemis-amqp-protocol</artifactId>
+         <version>${project.version}</version>
+      </dependency>
+      <dependency>
+         <groupId>org.apache.qpid</groupId>
+         <artifactId>qpid-jms-client</artifactId>
+      </dependency>
+      <dependency>
+         <groupId>io.opentelemetry</groupId>
+         <artifactId>opentelemetry-api</artifactId>
+         <version>1.10.0</version>
+      </dependency>
+      <dependency>
+         <groupId>io.opentelemetry</groupId>
+         <artifactId>opentelemetry-sdk</artifactId>
+      </dependency>
+      <dependency>
+         <groupId>io.opentelemetry</groupId>
+         <artifactId>opentelemetry-semconv</artifactId>
+      </dependency>
+      <dependency>
+         <groupId>io.opentelemetry</groupId>
+         <artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId>
+      </dependency>
+      <dependency>
+         <groupId>io.opentelemetry</groupId>
+         <artifactId>opentelemetry-exporter-logging</artifactId>
+      </dependency>
+      <dependency>
+         <groupId>io.opentelemetry</groupId>
+         <artifactId>opentelemetry-exporter-otlp</artifactId>
+      </dependency>
+      <dependency>
+         <groupId>io.opentelemetry</groupId>
+         <artifactId>opentelemetry-exporter-jaeger</artifactId>
+      </dependency>
+      <dependency>
+         <groupId>io.opentelemetry</groupId>
+         <artifactId>opentelemetry-exporter-zipkin</artifactId>
+      </dependency>
+      <dependency>
+         <groupId>io.opentelemetry</groupId>
+         <artifactId>opentelemetry-sdk-testing</artifactId>
+      </dependency>
+      <dependency>
+         <groupId>junit</groupId>
+         <artifactId>junit</artifactId>
+         <scope>test</scope>
+      </dependency>
+      <!-- https://mvnrepository.com/artifact/org.assertj/assertj-core -->
+      <dependency>
+         <groupId>org.assertj</groupId>
+         <artifactId>assertj-core</artifactId>
+         <version>3.22.0</version>
+         <scope>test</scope>
+      </dependency>
+      <dependency>
+         <groupId>org.mockito</groupId>
+         <artifactId>mockito-core</artifactId>
+         <scope>test</scope>
+      </dependency>
+   </dependencies>
+
+   <build>
+      <plugins>
+         <plugin>
+            <groupId>org.apache.activemq</groupId>
+            <artifactId>artemis-maven-plugin</artifactId>
+            <executions>
+               <execution>
+                  <id>create</id>
+                  <phase>verify</phase>
+                  <configuration>
+                     <!-- The broker plugin will install this library on the server's classpath -->
+                     <libList>
+                        <arg>org.apache.activemq.examples.broker:opentracing:${project.version}</arg>
+                     </libList>
+                     <libListWithDeps>
+                        <arg>io.opentelemetry:opentelemetry-api:${opentelemetry.version}</arg>
+                        <arg>io.opentelemetry:opentelemetry-sdk:${opentelemetry.version}</arg>
+                        <arg>io.opentelemetry:opentelemetry-semconv:${opentelemetry-alpha.version}</arg>
+                        <arg>io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:${opentelemetry-alpha.version}
+                        </arg>
+                        <arg>io.opentelemetry:opentelemetry-exporter-logging:${opentelemetry.version}</arg>
+                        <arg>io.opentelemetry:opentelemetry-exporter-otlp:${opentelemetry.version}</arg>
+                        <arg>io.opentelemetry:opentelemetry-exporter-jaeger:${opentelemetry.version}</arg>
+                        <arg>io.opentelemetry:opentelemetry-exporter-zipkin:${opentelemetry.version}</arg>
+                        <arg>io.opentelemetry:opentelemetry-sdk:${opentelemetry.version}</arg>
+                        <arg>io.opentelemetry:opentelemetry-sdk-testing:${opentelemetry.version}</arg>
+                        <arg>junit:junit:${junit.version}</arg>
+                        <arg>org.assertj:assertj-core:3.22.0</arg>
+                        <arg>org.mockito:mockito-core:${mockito.version}</arg>
+                     </libListWithDeps>
+                     <ignore>${noServer}</ignore>
+                  </configuration>
+                  <goals>
+                     <goal>create</goal>
+                  </goals>
+               </execution>
+               <execution>
+                  <id>start</id>
+                  <goals>
+                     <goal>cli</goal>
+                  </goals>
+                  <configuration>
+                     <spawn>true</spawn>
+                     <ignore>${noServer}</ignore>
+                     <testURI>tcp://localhost:61616</testURI>
+                     <args>
+                        <param>run</param>
+                     </args>
+                  </configuration>
+               </execution>
+               <execution>
+                  <id>runClient</id>
+                  <goals>
+                     <goal>runClient</goal>
+                  </goals>
+                  <configuration>
+                     <clientClass>org.apache.activemq.artemis.jms.example.OpenTracingPluginExample</clientClass>
+                  </configuration>
+               </execution>
+               <execution>
+                  <id>stop</id>
+                  <goals>
+                     <goal>cli</goal>
+                  </goals>
+                  <configuration>
+                     <ignore>${noServer}</ignore>
+                     <args>
+                        <param>stop</param>
+                     </args>
+                  </configuration>
+               </execution>
+            </executions>
+            <dependencies>
+               <dependency>
+                  <groupId>org.apache.activemq.examples.broker</groupId>
+                  <artifactId>opentracing</artifactId>
+                  <version>${project.version}</version>
+               </dependency>
+            </dependencies>
+         </plugin>
+         <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-clean-plugin</artifactId>
+         </plugin>
+      </plugins>
+   </build>
+</project>
diff --git a/examples/features/standard/opentracing/readme.md b/examples/features/standard/opentracing/readme.md
new file mode 100644
index 0000000..7687166
--- /dev/null
+++ b/examples/features/standard/opentracing/readme.md
@@ -0,0 +1,68 @@
+# Opentracing Plugin Example
+
+This plugin
+embraces [OpenTelemetry Autoconfiguration](https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk-extensions/autoconfigure)
+using environment-based properties to configure OpenTelemetry SDK.
+
+## Run Opentracing Plugin Example
+
+[![Running the Example Demo](https://img.youtube.com/vi/MVGx7QrztZQ/0.jpg)](https://www.youtube.com/watch?v=MVGx7QrztZQ)
+
+To run the example, simply type **mvn verify** from this directory, or **mvn -PnoServer verify** if you want to start
+and create the broker manually.
+> **_NOTE:_**   You must have [jeager](https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk-extensions/autoconfigure#jaeger-exporter) running at `http://localhost:16686`. You can learn more about Jeager [here](https://www.jaegertracing.io/)
+
+> command to start your jeager instance `docker run -p 16686:16686 -p 14250:14250 jaegertracing/all-in-one:<your_version>`
+
+After seeing a **`Build Success`**, open the browser, connect to your Jeager running instance and check for spans.
+
+## Customise Opentracing Plugin Example
+
+The [`tracing.properties`](./src/main/resources/tracing.properties) has configuration properties that
+autoconfigure [Opentelemetry Exporter](https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk-extensions/autoconfigure#exporters)
+. We reconfigured it and used Jeager as the default exporter, sending data through at `http://localhost:14250`
+You can change this by choosing to use:
+
+- [otlp exporter](https://github.com/open-telemetry/opentelemetry-java/tree/1e073fcff20697fd5f2eb39bd6246d06a1231089/sdk-extensions/autoconfigure#otlp-exporter-both-span-and-metric-exporters)
+  , by uncommenting (removing `#`) the following
+    - otlp enabler: `otel.traces.exporter=otlp`
+    - otlp endpoint: `otel.exporter.otlp.endpoint=http://localhost:4317` Change port and host to match your running
+      instance.
+    - otlp traces-endpoint: `otel.exporter.otlp.traces.endpoint=http://localhost:4317` Change port and host to match
+      your running instance.
+
+
+- [Zipkin Exporter](https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk-extensions/autoconfigure#zipkin-exporter)
+  , by uncommenting (removing `#`) the following
+  - Zipkin enabler: `otel.traces.exporter=zipkin`
+  - Zipkin endpoint: `otel.exporter.zipkin.endpoint=http://localhost:9411/api/v2/spans`. Change port and host to match your
+    running instance.
+  > **Note:** command to start Zipkin instance `docker run -p 9411:9411 openzipkin/zipkin`
+ 
+  
+You can also change the default service name from `opentracing_plugin` to any string by changing the value
+of `otel.service.name`
+
+## How to start exporters
+- [Zipkin](https://zipkin.io/pages/quickstart): The quickest way is by use of docker.  
+  - Open the terminal, copy, paste and run the command `docker run -d -p 9411:9411 openzipkin/zipkin`
+  - open the browser, enter the url `http://localhost:9411` and on the page that appears, click the **Run Queries** button.
+
+
+- [Jeager](https://www.jaegertracing.io/docs/1.30/getting-started/): The quickest way is by use of docker.
+  - open the terminal and paste the command below 
+    ```
+    docker run -d --name jaeger \
+    e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
+    p 5775:5775/udp \
+    p 6831:6831/udp \
+    p 6832:6832/udp \
+    p 5778:5778 \
+    p 16686:16686 \
+    p 14250:14250 \
+    p 14268:14268 \
+    p 14269:14269 \
+    p 9411:9411 \
+    jaegertracing/all-in-one:1.30
+    ```
+  - open the browser, enter the url `http://localhost:16686/search`, click **Search**, select your service-name from the dropdown below the service name and finally click **Find Traces** Button.
diff --git a/examples/features/standard/opentracing/src/main/java/org/apache/activemq/artemis/jms/example/OpenTracingPlugin.java b/examples/features/standard/opentracing/src/main/java/org/apache/activemq/artemis/jms/example/OpenTracingPlugin.java
new file mode 100644
index 0000000..6668e9d
--- /dev/null
+++ b/examples/features/standard/opentracing/src/main/java/org/apache/activemq/artemis/jms/example/OpenTracingPlugin.java
@@ -0,0 +1,113 @@
+/*
+ * 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.activemq.artemis.jms.example;
+
+import java.io.InputStream;
+import java.util.Properties;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanBuilder;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.api.trace.StatusCode;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
+import org.apache.activemq.artemis.api.core.ActiveMQException;
+import org.apache.activemq.artemis.api.core.Message;
+import org.apache.activemq.artemis.core.postoffice.RoutingStatus;
+import org.apache.activemq.artemis.core.server.MessageReference;
+import org.apache.activemq.artemis.core.server.ServerConsumer;
+import org.apache.activemq.artemis.core.server.ServerSession;
+import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerPlugin;
+import org.apache.activemq.artemis.core.transaction.Transaction;
+
+public class OpenTracingPlugin implements ActiveMQServerPlugin {
+
+   private static final String OPERATION_NAME = "ArtemisMessageDelivery";
+   private static OpenTelemetrySdk sdk = initOpenTracing();
+   private static Tracer tracer = GlobalOpenTelemetry.getTracer(OpenTracingPlugin.class.getName());
+
+   public static OpenTelemetrySdk initOpenTracing() {
+      try {
+         InputStream input = OpenTracingPlugin.class.getClassLoader().getResourceAsStream("tracing.properties");
+         if (input == null) {
+            throw new NullPointerException("Unable to find tracing.properties file");
+         }
+         Properties prop = new Properties(System.getProperties());
+         prop.load(input);
+         System.setProperties(prop);
+
+         sdk = AutoConfiguredOpenTelemetrySdk.initialize().getOpenTelemetrySdk();
+
+      } catch (Throwable t) {
+         t.printStackTrace();
+      }
+      return sdk;
+   }
+
+   @Override
+   public void beforeSend(ServerSession session,
+                          Transaction tx,
+                          Message message,
+                          boolean direct,
+                          boolean noAutoCreateQueue) throws ActiveMQException {
+      SpanBuilder spanBuilder = getTracer().spanBuilder(OPERATION_NAME).setAttribute("message", message.toString()).setSpanKind(SpanKind.SERVER);
+      Span span = spanBuilder.startSpan();
+      message.setUserContext(Span.class, span);
+   }
+
+   @Override
+   public void afterSend(Transaction tx,
+                         Message message,
+                         boolean direct,
+                         boolean noAutoCreateQueue,
+                         RoutingStatus result) throws ActiveMQException {
+      Span span = getSpan(message);
+      span.addEvent("send " + result.name());
+   }
+
+   @Override
+   public void afterDeliver(ServerConsumer consumer, MessageReference reference) throws ActiveMQException {
+      Span span = (Span) reference.getMessage().getUserContext(Span.class);
+      span.addEvent("deliver " + consumer.getSessionName());
+      span.end();
+   }
+
+   @Override
+   public void onSendException(ServerSession session,
+                               Transaction tx,
+                               Message message,
+                               boolean direct,
+                               boolean noAutoCreateQueue,
+                               Exception e) throws ActiveMQException {
+      getSpan(message).setStatus(StatusCode.ERROR).recordException(e);
+   }
+
+   public Tracer getTracer() {
+      return tracer;
+   }
+
+   public void setTracer(Tracer myTracer) {
+      tracer = myTracer;
+   }
+
+   private Span getSpan(Message message) {
+      Span span = (Span) message.getUserContext(Span.class);
+      return span;
+   }
+}
\ No newline at end of file
diff --git a/examples/features/standard/opentracing/src/main/java/org/apache/activemq/artemis/jms/example/OpenTracingPluginExample.java b/examples/features/standard/opentracing/src/main/java/org/apache/activemq/artemis/jms/example/OpenTracingPluginExample.java
new file mode 100644
index 0000000..9690d32
--- /dev/null
+++ b/examples/features/standard/opentracing/src/main/java/org/apache/activemq/artemis/jms/example/OpenTracingPluginExample.java
@@ -0,0 +1,121 @@
+/*
+ * 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.activemq.artemis.jms.example;
+
+import javax.jms.Connection;
+import javax.jms.ConnectionFactory;
+import javax.jms.JMSException;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+
+import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
+import org.apache.activemq.artemis.jms.client.ActiveMQQueue;
+import org.apache.qpid.jms.JmsConnectionFactory;
+
+/**
+ * A simple example which shows how to use a QueueBrowser to look at messages of a queue without removing them from the queue
+ */
+public class OpenTracingPluginExample {
+
+   public static void main(final String[] args) throws Exception {
+
+      // This example will send and receive an AMQP message
+      sendConsumeAMQP();
+
+      // And it will also send and receive a Core message
+      sendConsumeCore();
+   }
+
+   private static void sendConsumeAMQP() throws JMSException {
+      Connection connection = null;
+      ConnectionFactory connectionFactory = new JmsConnectionFactory("amqp://localhost:5672");
+
+      try {
+
+         // Create an amqp qpid 1.0 connection
+         connection = connectionFactory.createConnection();
+
+         // Create a session
+         Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+         // Create a sender
+         Queue queue = session.createQueue("exampleQueue");
+         MessageProducer sender = session.createProducer(queue);
+
+         // send a few simple message
+         sender.send(session.createTextMessage("Hello world "));
+
+         connection.start();
+
+         // create a moving receiver, this means the message will be removed from the queue
+         MessageConsumer consumer = session.createConsumer(queue);
+
+         // receive the simple message
+         consumer.receive(5000);
+
+      } finally {
+         if (connection != null) {
+            // close the connection
+            connection.close();
+         }
+      }
+   }
+
+
+   private static void sendConsumeCore() throws JMSException {
+      Connection connection = null;
+      try {
+         // Perform a lookup on the Connection Factory
+         ConnectionFactory cf = new ActiveMQConnectionFactory("tcp://localhost:61616");
+
+         Queue queue = new ActiveMQQueue("exampleQueue");
+
+         // Create a JMS Connection
+         connection = cf.createConnection();
+
+         // Create a JMS Session
+         Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+         // Create a JMS Message Producer
+         MessageProducer producer = session.createProducer(queue);
+
+         // Create a Text Message
+         TextMessage message = session.createTextMessage("This is a text message");
+
+         // Send the Message
+         producer.send(message);
+
+         // Create a JMS Message Consumer
+         MessageConsumer messageConsumer = session.createConsumer(queue);
+
+         // Start the Connection
+         connection.start();
+
+         // Receive the message
+         messageConsumer.receive(5000);
+
+      } finally {
+         if (connection != null) {
+            connection.close();
+         }
+      }
+
+   }
+}
diff --git a/examples/features/standard/opentracing/src/main/resources/activemq/server0/broker.xml b/examples/features/standard/opentracing/src/main/resources/activemq/server0/broker.xml
new file mode 100644
index 0000000..667ce90
--- /dev/null
+++ b/examples/features/standard/opentracing/src/main/resources/activemq/server0/broker.xml
@@ -0,0 +1,199 @@
+<?xml version='1.0'?>
+<!--
+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 xmlns="urn:activemq"
+               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+               xsi:schemaLocation="urn:activemq /schema/artemis-configuration.xsd">
+
+   <core xmlns="urn:activemq:core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="urn:activemq:core ">
+
+      <name>0.0.0.0</name>
+
+      <persistence-enabled>true</persistence-enabled>
+
+      <!-- this could be ASYNCIO, MAPPED, NIO
+           ASYNCIO: Linux Libaio
+           MAPPED: mmap files
+           NIO: Plain Java Files
+       -->
+      <journal-type>NIO</journal-type>
+
+      <paging-directory>./data/paging</paging-directory>
+
+      <bindings-directory>./data/bindings</bindings-directory>
+
+      <journal-directory>./data/journal</journal-directory>
+
+      <large-messages-directory>./data/large-messages</large-messages-directory>
+
+      <journal-datasync>true</journal-datasync>
+
+      <journal-min-files>2</journal-min-files>
+
+      <journal-pool-files>-1</journal-pool-files>
+
+      <journal-file-size>10M</journal-file-size>
+            <!--
+        You can verify the network health of a particular NIC by specifying the <network-check-NIC> element.
+         <network-check-NIC>theNicName</network-check-NIC>
+        -->
+
+      <!--
+        Use this to use an HTTP server to validate the network
+         <network-check-URL-list>http://www.apache.org</network-check-URL-list> -->
+
+      <!-- <network-check-period>10000</network-check-period> -->
+      <!-- <network-check-timeout>1000</network-check-timeout> -->
+
+      <!-- this is a comma separated list, no spaces, just DNS or IPs
+           it should accept IPV6
+
+           Warning: Make sure you understand your network topology as this is meant to validate if your network is valid.
+                    Using IPs that could eventually disappear or be partially visible may defeat the purpose.
+                    You can use a list of multiple IPs, and if any successful ping will make the server OK to continue running -->
+      <!-- <network-check-list>10.0.0.1</network-check-list> -->
+
+      <!-- use this to customize the ping used for ipv4 addresses -->
+      <!-- <network-check-ping-command>ping -c 1 -t %d %s</network-check-ping-command> -->
+
+      <!-- use this to customize the ping used for ipv6 addresses -->
+      <!-- <network-check-ping6-command>ping6 -c 1 %2$s</network-check-ping6-command> -->
+
+
+
+
+      <!-- how often we are looking for how many bytes are being used on the disk in ms -->
+      <disk-scan-period>5000</disk-scan-period>
+
+      <!-- once the disk hits this limit the system will block, or close the connection in certain protocols
+           that won't support flow control. -->
+      <max-disk-usage>90</max-disk-usage>
+
+      <!-- should the broker detect dead locks and other issues -->
+      <critical-analyzer>true</critical-analyzer>
+
+      <critical-analyzer-timeout>120000</critical-analyzer-timeout>
+
+      <critical-analyzer-check-period>60000</critical-analyzer-check-period>
+
+      <critical-analyzer-policy>HALT</critical-analyzer-policy>
+
+      <!-- the system will enter into page mode once you hit this limit.
+           This is an estimate in bytes of how much the messages are using in memory
+
+            The system will use half of the available memory (-Xmx) by default for the global-max-size.
+            You may specify a different value here if you need to customize it to your needs.
+
+            <global-max-size>100Mb</global-max-size>
+
+      -->
+
+      <broker-plugins>
+         <broker-plugin class-name="org.apache.activemq.artemis.jms.example.OpenTracingPlugin"/>
+      </broker-plugins>
+
+      <acceptors>
+
+         <!-- useEpoll means: it will use Netty epoll if you are on a system (Linux) that supports it -->
+         <!-- amqpCredits: The number of credits sent to AMQP producers -->
+         <!-- amqpLowCredits: The server will send the # credits specified at amqpCredits at this low mark -->
+
+         <!-- Acceptor for every supported protocol -->
+         <acceptor name="artemis">tcp://0.0.0.0:61616?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;protocols=CORE,AMQP,STOMP,HORNETQ,MQTT,OPENWIRE;useEpoll=true;amqpCredits=1000;amqpLowCredits=300</acceptor>
+
+         <!-- AMQP Acceptor.  Listens on default AMQP port for AMQP traffic.-->
+         <acceptor name="amqp">tcp://0.0.0.0:5672?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;protocols=AMQP;useEpoll=true;amqpCredits=1000;amqpLowCredits=300</acceptor>
+
+         <!-- STOMP Acceptor. -->
+         <acceptor name="stomp">tcp://0.0.0.0:61613?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;protocols=STOMP;useEpoll=true</acceptor>
+
+         <!-- HornetQ Compatibility Acceptor.  Enables HornetQ Core and STOMP for legacy HornetQ clients. -->
+         <acceptor name="hornetq">tcp://0.0.0.0:5445?protocols=HORNETQ,STOMP;useEpoll=true</acceptor>
+
+         <!-- MQTT Acceptor -->
+         <acceptor name="mqtt">tcp://0.0.0.0:1883?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;protocols=MQTT;useEpoll=true</acceptor>
+
+      </acceptors>
+
+
+      <security-settings>
+         <security-setting match="#">
+            <permission type="createNonDurableQueue" roles="guest"/>
+            <permission type="deleteNonDurableQueue" roles="guest"/>
+            <permission type="createDurableQueue" roles="guest"/>
+            <permission type="deleteDurableQueue" roles="guest"/>
+            <permission type="createAddress" roles="guest"/>
+            <permission type="deleteAddress" roles="guest"/>
+            <permission type="consume" roles="guest"/>
+            <permission type="browse" roles="guest"/>
+            <permission type="send" roles="guest"/>
+            <!-- we need this otherwise ./artemis data imp wouldn't work -->
+            <permission type="manage" roles="guest"/>
+         </security-setting>
+      </security-settings>
+
+      <address-settings>
+         <!-- if you define auto-create on certain queues, management has to be auto-create -->
+         <address-setting match="activemq.management#">
+            <dead-letter-address>DLQ</dead-letter-address>
+            <expiry-address>ExpiryQueue</expiry-address>
+            <redelivery-delay>0</redelivery-delay>
+            <!-- with -1 only the global-max-size is in use for limiting -->
+            <max-size-bytes>-1</max-size-bytes>
+            <message-counter-history-day-limit>10</message-counter-history-day-limit>
+            <address-full-policy>PAGE</address-full-policy>
+            <auto-create-queues>true</auto-create-queues>
+            <auto-create-addresses>true</auto-create-addresses>
+            <auto-create-jms-queues>true</auto-create-jms-queues>
+            <auto-create-jms-topics>true</auto-create-jms-topics>
+         </address-setting>
+         <!--default for catch all-->
+         <address-setting match="#">
+            <dead-letter-address>DLQ</dead-letter-address>
+            <expiry-address>ExpiryQueue</expiry-address>
+            <redelivery-delay>0</redelivery-delay>
+            <!-- with -1 only the global-max-size is in use for limiting -->
+            <max-size-bytes>-1</max-size-bytes>
+            <message-counter-history-day-limit>10</message-counter-history-day-limit>
+            <address-full-policy>PAGE</address-full-policy>
+            <auto-create-queues>true</auto-create-queues>
+            <auto-create-addresses>true</auto-create-addresses>
+            <auto-create-jms-queues>true</auto-create-jms-queues>
+            <auto-create-jms-topics>true</auto-create-jms-topics>
+         </address-setting>
+      </address-settings>
+
+      <addresses>
+         <address name="DLQ">
+            <anycast>
+               <queue name="DLQ" />
+            </anycast>
+         </address>
+         <address name="ExpiryQueue">
+            <anycast>
+               <queue name="ExpiryQueue" />
+            </anycast>
+         </address>
+
+      </addresses>
+
+   </core>
+</configuration>
diff --git a/examples/features/standard/opentracing/src/main/resources/tracing.properties b/examples/features/standard/opentracing/src/main/resources/tracing.properties
new file mode 100644
index 0000000..f4eaee5
--- /dev/null
+++ b/examples/features/standard/opentracing/src/main/resources/tracing.properties
@@ -0,0 +1,37 @@
+#
+# 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.
+#activemq
+
+# using otlp exporter
+#otel.traces.exporter=otlp
+#otel.exporter.otlp.endpoint=http://localhost:4317
+#otel.exporter.otlp.traces.endpoint=http://localhost:4317
+#otel.exporter.otlp.timeout=10000
+#otel.exporter.otlp.traces.timeout=10000
+#otel.exporter.otlp.protocol=grpc
+#otel.exporter.otlp.traces.protocol=grpc
+
+# service name
+otel.service.name=artemis_opentracing
+
+# Jaeger exporter
+otel.traces.exporter=jaeger
+otel.exporter.jaeger.endpoint=http://localhost:14250
+otel.exporter.jaeger.timeout=10000
+
+# Zipkin exporter (default)
+#otel.traces.exporter=zipkin
+#otel.exporter.zipkin.endpoint=http://localhost:9411/api/v2/spans
diff --git a/examples/features/standard/opentracing/src/test/java/org/apache/activemq/artemis/jms/example/OpenTracingPluginTest.java b/examples/features/standard/opentracing/src/test/java/org/apache/activemq/artemis/jms/example/OpenTracingPluginTest.java
new file mode 100644
index 0000000..da25745
--- /dev/null
+++ b/examples/features/standard/opentracing/src/test/java/org/apache/activemq/artemis/jms/example/OpenTracingPluginTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.activemq.artemis.jms.example;
+
+import javax.jms.JMSException;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanBuilder;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.api.trace.StatusCode;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter;
+import org.apache.activemq.artemis.api.core.ActiveMQException;
+import org.apache.activemq.artemis.api.core.Message;
+import org.apache.activemq.artemis.core.postoffice.RoutingStatus;
+import org.apache.activemq.artemis.core.server.MessageReference;
+import org.apache.activemq.artemis.core.server.ServerConsumer;
+import org.apache.activemq.artemis.core.server.ServerSession;
+import org.apache.activemq.artemis.core.transaction.Transaction;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.validateMockitoUsage;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class OpenTracingPluginTest {
+
+   private final InMemorySpanExporter exporter = InMemorySpanExporter.create();
+   @InjectMocks
+   private OpenTracingPlugin plugin;
+
+   @Mock
+   private SpanBuilder spanBuilder;
+
+   @Mock
+   private Transaction tx;
+
+   @Mock
+   private Message message;
+
+   @Mock
+   private Span span;
+
+   @Test
+   public void assertBeforeMessage() throws JMSException, ActiveMQException {
+      Tracer tracer = mock(Tracer.class);
+      ServerSession session = mock(ServerSession.class);
+
+      Mockito.doReturn(spanBuilder).when(tracer).spanBuilder(anyString());
+      Mockito.doReturn(spanBuilder).when(spanBuilder).setAttribute(anyString(), anyString());
+      Mockito.doReturn(spanBuilder).when(spanBuilder).setSpanKind(any(SpanKind.class));
+      Mockito.doReturn(span).when(spanBuilder).startSpan();
+
+      plugin.setTracer(tracer);
+      plugin.beforeSend(session, tx, message, true, true);
+
+      verify(tracer, atLeastOnce()).spanBuilder(anyString());
+      verify(spanBuilder, atLeastOnce()).setAttribute(anyString(), anyString());
+      verify(spanBuilder, atLeastOnce()).setSpanKind(any(SpanKind.class));
+      verify(spanBuilder, atLeastOnce()).startSpan();
+      verify(message, atLeastOnce()).setUserContext(anyObject(), anyObject());
+   }
+
+   @Test
+   public void assetAfterSend() throws ActiveMQException {
+      when(message.getUserContext(Span.class)).thenReturn(span);
+
+      plugin.afterSend(tx, message, true, true, RoutingStatus.OK);
+      verify(span, atLeastOnce()).addEvent(anyString());
+   }
+
+   @Test
+   public void assertOnSendException() throws ActiveMQException {
+      ServerSession session = mock(ServerSession.class);
+      Exception exception = mock(Exception.class);
+
+      when(message.getUserContext(Span.class)).thenReturn(span);
+      when(span.setStatus(any(StatusCode.class))).thenReturn(span);
+      when(span.recordException(any(Exception.class))).thenReturn(span);
+
+      plugin.onSendException(session, tx, message, true, true, exception);
+
+      verify(span, atLeastOnce()).setStatus(any(StatusCode.class));
+      verify(span, atLeastOnce()).setStatus(any(StatusCode.class));
+      verify(span, atLeastOnce()).recordException(any(Exception.class));
+   }
+
+   @Test
+   public void assertAfterDeliver() throws ActiveMQException {
+      ServerConsumer consumer = mock(ServerConsumer.class);
+      MessageReference reference = mock(MessageReference.class);
+
+      when(reference.getMessage().getUserContext(Span.class)).thenReturn(span);
+
+      plugin.afterDeliver(consumer, reference);
+
+      verify(span, atLeastOnce()).addEvent(anyString());
+      verify(span, atLeastOnce()).end();
+   }
+
+   @After
+   public void cleanUp() {
+      exporter.reset();
+      GlobalOpenTelemetry.resetForTest();
+      validateMockitoUsage();
+   }
+}
diff --git a/examples/features/standard/pom.xml b/examples/features/standard/pom.xml
index a830d2d..89f8395 100644
--- a/examples/features/standard/pom.xml
+++ b/examples/features/standard/pom.xml
@@ -80,6 +80,7 @@ under the License.
             <module>message-priority</module>
             <module>netty-openssl</module>
             <module>no-consumer-buffering</module>
+            <module>opentracing</module>
             <module>paging</module>
             <module>pre-acknowledge</module>
             <module>producer-rate-limit</module>
@@ -156,6 +157,7 @@ under the License.
             <module>message-priority</module>
             <module>netty-openssl</module>
             <module>no-consumer-buffering</module>
+            <module>opentracing</module>
             <module>paging</module>
             <module>pre-acknowledge</module>
             <module>producer-rate-limit</module>
diff --git a/pom.xml b/pom.xml
index 06bf38a..f4e6385 100644
--- a/pom.xml
+++ b/pom.xml
@@ -118,6 +118,10 @@
       <curator.version>5.2.0</curator.version>
       <zookeeper.version>3.6.3</zookeeper.version>
 
+      <!-- tracing -->
+      <opentelemetry.version>1.10.0</opentelemetry.version>
+      <opentelemetry-alpha.version>1.10.0-alpha</opentelemetry-alpha.version>
+
       <!-- this is basically for tests -->
       <netty-tcnative-version>2.0.48.Final</netty-tcnative-version>
       <proton.version>0.33.10</proton.version>
@@ -549,6 +553,23 @@
          </dependency>
          <!-- ## End Examples Dependencies ## -->
 
+         <!-- ### START opentelemetry Dependencies ### -->
+         <dependency>
+            <groupId>io.opentelemetry</groupId>
+            <artifactId>opentelemetry-bom</artifactId>
+            <version>${opentelemetry.version}</version>
+            <type>pom</type>
+            <scope>import</scope>
+         </dependency>
+         <dependency>
+            <groupId>io.opentelemetry</groupId>
+            <artifactId>opentelemetry-bom-alpha</artifactId>
+            <version>${opentelemetry-alpha.version}</version>
+            <type>pom</type>
+            <scope>import</scope>
+         </dependency>
+         <!-- ### END opentelemetry Dependencies ### -->
+
          <!--needed to compile the bootstrap jar-->
          <dependency>
             <groupId>org.jgroups</groupId>
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/AcknowledgeTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/AcknowledgeTest.java
index 1803321..f56a738 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/AcknowledgeTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/client/AcknowledgeTest.java
@@ -844,5 +844,15 @@ public class AcknowledgeTest extends ActiveMQTestBase {
       @Override
       public void setOwner(Object object) {
       }
+
+      @Override
+      public Object getUserContext(Object key) {
+         return null;
+      }
+
+      @Override
+      public void setUserContext(Object key, Object value) {
+
+      }
    }
 }