From fea9a08a952a6f611747c6d7ab937cd66e76cb8b Mon Sep 17 00:00:00 2001
From: Erin Schnabel <ebullient@redhat.com>
Date: Thu, 3 Jun 2021 12:34:00 -0400
Subject: [PATCH] Add resteasy-observability module

---
 bom/application/pom.xml                       |  10 ++
 extensions/micrometer/deployment/pom.xml      |   8 +-
 .../test/resources/test-logging.properties    |   5 +-
 extensions/micrometer/runtime/pom.xml         |   5 +
 extensions/pom.xml                            |   1 +
 .../resteasy-server-common/deployment/pom.xml |   4 +
 .../ResteasyServerCommonProcessor.java        |   3 +
 .../resteasy-observability/deployment/pom.xml |  76 +++++++++++++
 .../RestPathAnnotationProcessor.java          | 100 ++++++++++++++++++
 extensions/resteasy-observability/pom.xml     |  24 +++++
 .../resteasy-observability/runtime/pom.xml    |  51 +++++++++
 .../runtime/QuarkusRestPathTemplate.java      |  17 +++
 .../QuarkusRestPathTemplateInterceptor.java   |  44 ++++++++
 extensions/resteasy-observability/spi/pom.xml |  24 +++++
 .../spi/RestApplicationPathBuildItem.java     |  23 ++++
 .../deployment/pom.xml                        |   4 +
 .../deployment/ResteasyReactiveProcessor.java |   4 +
 17 files changed, 400 insertions(+), 3 deletions(-)
 create mode 100644 extensions/resteasy-observability/deployment/pom.xml
 create mode 100644 extensions/resteasy-observability/deployment/src/main/java/io/quarkus/resteasy/observability/runtime/deployment/RestPathAnnotationProcessor.java
 create mode 100644 extensions/resteasy-observability/pom.xml
 create mode 100644 extensions/resteasy-observability/runtime/pom.xml
 create mode 100644 extensions/resteasy-observability/runtime/src/main/java/io/quarkus/resteasy/observability/runtime/QuarkusRestPathTemplate.java
 create mode 100644 extensions/resteasy-observability/runtime/src/main/java/io/quarkus/resteasy/observability/runtime/QuarkusRestPathTemplateInterceptor.java
 create mode 100644 extensions/resteasy-observability/spi/pom.xml
 create mode 100644 extensions/resteasy-observability/spi/src/main/java/io/quarkus/resteasy/observability/spi/RestApplicationPathBuildItem.java

diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index cd299d57f590d..b2cc1c5721928 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -2489,6 +2489,16 @@
                 <artifactId>quarkus-opentelemetry-exporter-otlp</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>io.quarkus</groupId>
+                <artifactId>quarkus-resteasy-observability-deployment</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.quarkus</groupId>
+                <artifactId>quarkus-resteasy-observability</artifactId>
+                <version>${project.version}</version>
+            </dependency>
 
             <!-- Quarkus test dependencies -->
             <dependency>
diff --git a/extensions/micrometer/deployment/pom.xml b/extensions/micrometer/deployment/pom.xml
index ef33c1919987c..876b4c21d78ba 100644
--- a/extensions/micrometer/deployment/pom.xml
+++ b/extensions/micrometer/deployment/pom.xml
@@ -81,6 +81,12 @@
             <scope>test</scope>
         </dependency>
 
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-resteasy-observability-deployment</artifactId>
+            <scope>test</scope>
+        </dependency>
+
         <dependency>
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-undertow-deployment</artifactId>
@@ -113,7 +119,7 @@
                 <artifactId>maven-surefire-plugin</artifactId>
                 <configuration>
                     <systemPropertyVariables>
-                        <quarkus.log.level>INFO</quarkus.log.level>
+                        <quarkus.log.level>DEBUG</quarkus.log.level>
                     </systemPropertyVariables>
                 </configuration>
             </plugin>
diff --git a/extensions/micrometer/deployment/src/test/resources/test-logging.properties b/extensions/micrometer/deployment/src/test/resources/test-logging.properties
index 6eed6ab2596da..7a2ecfd97109f 100644
--- a/extensions/micrometer/deployment/src/test/resources/test-logging.properties
+++ b/extensions/micrometer/deployment/src/test/resources/test-logging.properties
@@ -1,4 +1,5 @@
-#quarkus.log.category."io.quarkus.micrometer".level=DEBUG
+quarkus.log.category."io.quarkus.micrometer".level=DEBUG
+quarkus.log.category."io.quarkus.resteasy.observability".level=DEBUG
 quarkus.log.category."io.quarkus.bootstrap".level=INFO
-#quarkus.log.category."io.quarkus.arc".level=DEBUG
+quarkus.log.category."io.quarkus.arc".level=DEBUG
 quarkus.log.category."io.netty".level=INFO
diff --git a/extensions/micrometer/runtime/pom.xml b/extensions/micrometer/runtime/pom.xml
index 3a38432c1f850..cec6c00fb1227 100644
--- a/extensions/micrometer/runtime/pom.xml
+++ b/extensions/micrometer/runtime/pom.xml
@@ -66,6 +66,11 @@
             <artifactId>quarkus-resteasy-reactive</artifactId>
             <optional>true</optional>
         </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-resteasy-observability</artifactId>
+            <optional>true</optional>
+        </dependency>
 
         <dependency>
             <groupId>io.quarkus</groupId>
diff --git a/extensions/pom.xml b/extensions/pom.xml
index 17ba5ac841872..b49b67cdfd7e8 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -44,6 +44,7 @@
         <module>smallrye-opentracing</module>
         <module>smallrye-fault-tolerance</module>
         <module>jaeger</module>
+        <module>resteasy-observability</module>
         <module>micrometer</module>
         <module>micrometer-registry-prometheus</module>
         <module>opentelemetry</module>
diff --git a/extensions/resteasy-classic/resteasy-server-common/deployment/pom.xml b/extensions/resteasy-classic/resteasy-server-common/deployment/pom.xml
index b1940b0c73202..88a865a8fa738 100644
--- a/extensions/resteasy-classic/resteasy-server-common/deployment/pom.xml
+++ b/extensions/resteasy-classic/resteasy-server-common/deployment/pom.xml
@@ -37,6 +37,10 @@
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-undertow-spi</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-resteasy-observability-spi</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java b/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java
index ed472ecaf1eea..f30f125f94c9b 100755
--- a/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java
+++ b/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java
@@ -70,6 +70,7 @@
 import io.quarkus.resteasy.common.deployment.ResteasyCommonProcessor.ResteasyCommonConfig;
 import io.quarkus.resteasy.common.runtime.QuarkusInjectorFactory;
 import io.quarkus.resteasy.common.spi.ResteasyDotNames;
+import io.quarkus.resteasy.observability.spi.RestApplicationPathBuildItem;
 import io.quarkus.resteasy.server.common.runtime.QuarkusResteasyDeployment;
 import io.quarkus.resteasy.server.common.spi.AdditionalJaxRsResourceDefiningAnnotationBuildItem;
 import io.quarkus.resteasy.server.common.spi.AdditionalJaxRsResourceMethodAnnotationsBuildItem;
@@ -182,6 +183,7 @@ public void build(
             BuildProducer<NativeImageProxyDefinitionBuildItem> proxyDefinition,
             BuildProducer<BytecodeTransformerBuildItem> transformers,
             BuildProducer<ResteasyServerConfigBuildItem> resteasyServerConfig,
+            BuildProducer<RestApplicationPathBuildItem> resteasyApplicationPath,
             BuildProducer<ResteasyDeploymentBuildItem> resteasyDeployment,
             BuildProducer<UnremovableBeanBuildItem> unremovableBeans,
             BuildProducer<AnnotationsTransformerBuildItem> annotationsTransformer,
@@ -398,6 +400,7 @@ public void build(
                 ArcUndeclaredThrowableException.class.getName());
 
         resteasyServerConfig.produce(new ResteasyServerConfigBuildItem(rootPath, path, resteasyInitParameters));
+        resteasyApplicationPath.produce(new RestApplicationPathBuildItem(path));
 
         Set<DotName> autoInjectAnnotationNames = autoInjectAnnotations.stream().flatMap(a -> a.getAnnotationNames().stream())
                 .collect(Collectors.toSet());
diff --git a/extensions/resteasy-observability/deployment/pom.xml b/extensions/resteasy-observability/deployment/pom.xml
new file mode 100644
index 0000000000000..24917d8283563
--- /dev/null
+++ b/extensions/resteasy-observability/deployment/pom.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>io.quarkus</groupId>
+        <artifactId>quarkus-resteasy-observability-parent</artifactId>
+        <version>999-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>quarkus-resteasy-observability-deployment</artifactId>
+    <name>Quarkus - RESTEasy Observability - Deployment</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-core-deployment</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-arc-deployment</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-resteasy-observability</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-resteasy-observability-spi</artifactId>
+        </dependency>
+
+        <!-- test -->
+
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-junit5-internal</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        
+        <dependency>
+            <groupId>io.rest-assured</groupId>
+            <artifactId>rest-assured</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <systemPropertyVariables>
+                        <quarkus.log.level>INFO</quarkus.log.level>
+                    </systemPropertyVariables>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <annotationProcessorPaths>
+                        <path>
+                            <groupId>io.quarkus</groupId>
+                            <artifactId>quarkus-extension-processor</artifactId>
+                            <version>${project.version}</version>
+                        </path>
+                    </annotationProcessorPaths>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
\ No newline at end of file
diff --git a/extensions/resteasy-observability/deployment/src/main/java/io/quarkus/resteasy/observability/runtime/deployment/RestPathAnnotationProcessor.java b/extensions/resteasy-observability/deployment/src/main/java/io/quarkus/resteasy/observability/runtime/deployment/RestPathAnnotationProcessor.java
new file mode 100644
index 0000000000000..b1f2e05f3989a
--- /dev/null
+++ b/extensions/resteasy-observability/deployment/src/main/java/io/quarkus/resteasy/observability/runtime/deployment/RestPathAnnotationProcessor.java
@@ -0,0 +1,100 @@
+package io.quarkus.resteasy.observability.runtime.deployment;
+
+import java.util.Optional;
+import java.util.regex.Pattern;
+
+import org.jboss.jandex.AnnotationInstance;
+import org.jboss.jandex.AnnotationTarget;
+import org.jboss.jandex.AnnotationValue;
+import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.DotName;
+import org.jboss.jandex.MethodInfo;
+
+import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
+import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
+import io.quarkus.arc.processor.AnnotationsTransformer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.resteasy.observability.runtime.QuarkusRestPathTemplate;
+import io.quarkus.resteasy.observability.runtime.QuarkusRestPathTemplateInterceptor;
+import io.quarkus.resteasy.observability.spi.RestApplicationPathBuildItem;
+
+public class RestPathAnnotationProcessor {
+
+    static final DotName REST_PATH = DotName.createSimple("javax.ws.rs.Path");
+    static final DotName REGISTER_REST_CLIENT = DotName
+            .createSimple("org.eclipse.microprofile.rest.client.inject.RegisterRestClient");
+    static final DotName TEMPLATE_PATH = DotName.createSimple(QuarkusRestPathTemplate.class.getName());
+    static final DotName TEMPLATE_PATH_INTERCEPTOR = DotName.createSimple(QuarkusRestPathTemplateInterceptor.class.getName());
+
+    public static final Pattern MULTIPLE_SLASH_PATTERN = Pattern.compile("//+");
+
+    @BuildStep()
+    AdditionalBeanBuildItem registerBeanClasses() {
+        return AdditionalBeanBuildItem.builder()
+                .setUnremovable()
+                .addBeanClass(TEMPLATE_PATH.toString())
+                .addBeanClass(TEMPLATE_PATH_INTERCEPTOR.toString())
+                .build();
+    }
+
+    @BuildStep
+    AnnotationsTransformerBuildItem findRestPaths(
+            Optional<RestApplicationPathBuildItem> restApplicationPath) {
+
+        return new AnnotationsTransformerBuildItem(new AnnotationsTransformer() {
+            @Override
+            public boolean appliesTo(AnnotationTarget.Kind kind) {
+                return kind == AnnotationTarget.Kind.METHOD;
+            }
+
+            @Override
+            public void transform(TransformationContext ctx) {
+                AnnotationTarget target = ctx.getTarget();
+                MethodInfo methodInfo = target.asMethod();
+                ClassInfo classInfo = methodInfo.declaringClass();
+
+                AnnotationInstance annotation = methodInfo.annotation(REST_PATH);
+                if (annotation == null) {
+                    return;
+                }
+                StringBuilder stringBuilder = new StringBuilder(slashify(annotation.value().asString()));
+
+                // Look for @Path annotation on the class
+                annotation = classInfo.classAnnotation(REST_PATH);
+                if (annotation != null) {
+                    stringBuilder.insert(0, slashify(annotation.value().asString()));
+                }
+
+                // Add the @ApplicationPath (unless RestClient)
+                if (restApplicationPath.isPresent() && classInfo.classAnnotation(REGISTER_REST_CLIENT) == null) {
+                    stringBuilder.insert(0, slashify(restApplicationPath.get().getApplicationPath()));
+                }
+
+                // Now make sure there is a leading path, and no duplicates
+                String templatePath = MULTIPLE_SLASH_PATTERN.matcher('/' + stringBuilder.toString()).replaceAll("/");
+
+                // resulting path (used as observability tags) should start with a '/'
+                ctx.transform()
+                        .add(TEMPLATE_PATH, AnnotationValue.createStringValue("value", templatePath))
+                        .done();
+            }
+        });
+    }
+
+    String slashify(String path) {
+        // avoid doubles later. Empty for now
+        if (path == null || path.isEmpty() || "/".equals(path)) {
+            return "";
+        }
+        // remove doubles
+        path = MULTIPLE_SLASH_PATTERN.matcher(path).replaceAll("/");
+        // Label value consistency: result should not end with a slash
+        if (path.endsWith("/")) {
+            path = path.substring(0, path.length() - 1);
+        }
+        if (path.isEmpty() || path.startsWith("/")) {
+            return path;
+        }
+        return '/' + path;
+    }
+}
diff --git a/extensions/resteasy-observability/pom.xml b/extensions/resteasy-observability/pom.xml
new file mode 100644
index 0000000000000..504fc914a0ba8
--- /dev/null
+++ b/extensions/resteasy-observability/pom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>
+        <artifactId>quarkus-extensions-parent</artifactId>
+        <groupId>io.quarkus</groupId>
+        <version>999-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <!-- This extension works with BOTH resteasy-classic AND resteasy-reactive extensions -->
+    <artifactId>quarkus-resteasy-observability-parent</artifactId>
+    <name>Quarkus - RESTEasy Observability</name>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>spi</module>
+        <module>runtime</module>
+        <module>deployment</module>
+    </modules>
+</project>
\ No newline at end of file
diff --git a/extensions/resteasy-observability/runtime/pom.xml b/extensions/resteasy-observability/runtime/pom.xml
new file mode 100644
index 0000000000000..72dfda4a6db08
--- /dev/null
+++ b/extensions/resteasy-observability/runtime/pom.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>io.quarkus</groupId>
+        <artifactId>quarkus-resteasy-observability-parent</artifactId>
+        <version>999-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>quarkus-resteasy-observability</artifactId>
+    <name>Quarkus - RESTEasy Observability - Runtime</name>
+    <description>Telemetry for RESTEasy workloads</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-arc</artifactId>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>io.quarkus</groupId>
+                <artifactId>quarkus-bootstrap-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <annotationProcessorPaths>
+                        <path>
+                            <groupId>io.quarkus</groupId>
+                            <artifactId>quarkus-extension-processor</artifactId>
+                            <version>${project.version}</version>
+                        </path>
+                    </annotationProcessorPaths>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
\ No newline at end of file
diff --git a/extensions/resteasy-observability/runtime/src/main/java/io/quarkus/resteasy/observability/runtime/QuarkusRestPathTemplate.java b/extensions/resteasy-observability/runtime/src/main/java/io/quarkus/resteasy/observability/runtime/QuarkusRestPathTemplate.java
new file mode 100644
index 0000000000000..f234a7e394852
--- /dev/null
+++ b/extensions/resteasy-observability/runtime/src/main/java/io/quarkus/resteasy/observability/runtime/QuarkusRestPathTemplate.java
@@ -0,0 +1,17 @@
+package io.quarkus.resteasy.observability.runtime;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.interceptor.InterceptorBinding;
+
+@Inherited
+@InterceptorBinding
+@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface QuarkusRestPathTemplate {
+    String value() default "";
+}
diff --git a/extensions/resteasy-observability/runtime/src/main/java/io/quarkus/resteasy/observability/runtime/QuarkusRestPathTemplateInterceptor.java b/extensions/resteasy-observability/runtime/src/main/java/io/quarkus/resteasy/observability/runtime/QuarkusRestPathTemplateInterceptor.java
new file mode 100644
index 0000000000000..ad689daa22a3c
--- /dev/null
+++ b/extensions/resteasy-observability/runtime/src/main/java/io/quarkus/resteasy/observability/runtime/QuarkusRestPathTemplateInterceptor.java
@@ -0,0 +1,44 @@
+package io.quarkus.resteasy.observability.runtime;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+import javax.annotation.Priority;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+
+import org.jboss.logging.Logger;
+
+import io.quarkus.arc.ArcInvocationContext;
+
+@SuppressWarnings("unused")
+@QuarkusRestPathTemplate
+@Interceptor
+@Priority(Interceptor.Priority.LIBRARY_BEFORE + 10)
+public class QuarkusRestPathTemplateInterceptor {
+    private static final Logger log = Logger.getLogger(QuarkusRestPathTemplateInterceptor.class);
+
+    @AroundInvoke
+    Object restMethodInvoke(InvocationContext context) throws Exception {
+        QuarkusRestPathTemplate annotation = getAnnotation(context);
+        log.infof("QuarkusRestPathTemplate annotated method invoked with %s", annotation);
+
+        if (annotation != null) {
+
+        }
+        return context.proceed();
+    }
+
+    static QuarkusRestPathTemplate getAnnotation(InvocationContext context) {
+        Set<Annotation> annotations = (Set<Annotation>) context.getContextData()
+                .get(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS);
+
+        for (Annotation a : annotations) {
+            if (QuarkusRestPathTemplate.class.isInstance(a)) {
+                return QuarkusRestPathTemplate.class.cast(a);
+            }
+        }
+        return null;
+    }
+}
diff --git a/extensions/resteasy-observability/spi/pom.xml b/extensions/resteasy-observability/spi/pom.xml
new file mode 100644
index 0000000000000..9b3c8169aec85
--- /dev/null
+++ b/extensions/resteasy-observability/spi/pom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>
+        <artifactId>quarkus-resteasy-observability-parent</artifactId>
+        <groupId>io.quarkus</groupId>
+        <version>999-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>quarkus-resteasy-observability-spi</artifactId>
+    <description>Telemetry SPI for RESTEasy workloads</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-core-deployment</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/extensions/resteasy-observability/spi/src/main/java/io/quarkus/resteasy/observability/spi/RestApplicationPathBuildItem.java b/extensions/resteasy-observability/spi/src/main/java/io/quarkus/resteasy/observability/spi/RestApplicationPathBuildItem.java
new file mode 100644
index 0000000000000..43a2c19b5ac2b
--- /dev/null
+++ b/extensions/resteasy-observability/spi/src/main/java/io/quarkus/resteasy/observability/spi/RestApplicationPathBuildItem.java
@@ -0,0 +1,23 @@
+package io.quarkus.resteasy.observability.spi;
+
+import io.quarkus.builder.item.SimpleBuildItem;
+
+final public class RestApplicationPathBuildItem extends SimpleBuildItem {
+
+    private final String applicationPath;
+
+    /**
+     * The root path for Rest applications, set either via config or {@code @ApplicationPath}.
+     * This should not include the http root.
+     */
+    public RestApplicationPathBuildItem(String applicationPath) {
+        if (applicationPath.equals("/")) {
+            applicationPath = "";
+        }
+        this.applicationPath = applicationPath;
+    }
+
+    public String getApplicationPath() {
+        return applicationPath;
+    }
+}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/pom.xml b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/pom.xml
index a4a896e2a2f50..79e3186aa06f9 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/pom.xml
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/pom.xml
@@ -49,6 +49,10 @@
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-resteasy-reactive-common-deployment</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-resteasy-observability-spi</artifactId>
+        </dependency>
         <dependency>
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-security-deployment</artifactId>
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java
index 9b893a9faaf08..fad7a1c03cbce 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java
@@ -90,6 +90,7 @@
 import io.quarkus.gizmo.ClassCreator;
 import io.quarkus.gizmo.MethodCreator;
 import io.quarkus.netty.deployment.MinNettyAllocatorMaxOrderBuildItem;
+import io.quarkus.resteasy.observability.spi.RestApplicationPathBuildItem;
 import io.quarkus.resteasy.reactive.common.deployment.ApplicationResultBuildItem;
 import io.quarkus.resteasy.reactive.common.deployment.FactoryUtils;
 import io.quarkus.resteasy.reactive.common.deployment.QuarkusFactoryCreator;
@@ -269,6 +270,7 @@ public void setupEndpoints(Capabilities capabilities, BeanArchiveIndexBuildItem
             BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
             BuildProducer<ReflectiveHierarchyBuildItem> reflectiveHierarchy,
             BuildProducer<RouteBuildItem> routes,
+            BuildProducer<RestApplicationPathBuildItem> resteasyApplicationPath,
             ApplicationResultBuildItem applicationResultBuildItem,
             ResourceInterceptorsBuildItem resourceInterceptorsBuildItem,
             ExceptionMappersBuildItem exceptionMappersBuildItem,
@@ -527,6 +529,8 @@ private boolean hasAnnotation(MethodInfo method, short paramPosition, DotName an
             }
 
             String deploymentPath = sanitizeApplicationPath(applicationPath);
+            resteasyApplicationPath.produce(new RestApplicationPathBuildItem(deploymentPath));
+
             // Handler used for both the default and non-default deployment path (specified as application path or resteasyConfig.path)
             // Routes use the order VertxHttpRecorder.DEFAULT_ROUTE_ORDER + 1 to ensure the default route is called before the resteasy one
             Class<? extends Application> applicationClass = application == null ? Application.class : application.getClass();