diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index cd299d57f590d6..b2cc1c57219281 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -2489,6 +2489,16 @@
quarkus-opentelemetry-exporter-otlp
${project.version}
+
+ io.quarkus
+ quarkus-resteasy-observability-deployment
+ ${project.version}
+
+
+ io.quarkus
+ quarkus-resteasy-observability
+ ${project.version}
+
diff --git a/extensions/micrometer/deployment/pom.xml b/extensions/micrometer/deployment/pom.xml
index ef33c1919987c0..876b4c21d78bac 100644
--- a/extensions/micrometer/deployment/pom.xml
+++ b/extensions/micrometer/deployment/pom.xml
@@ -81,6 +81,12 @@
test
+
+ io.quarkus
+ quarkus-resteasy-observability-deployment
+ test
+
+
io.quarkus
quarkus-undertow-deployment
@@ -113,7 +119,7 @@
maven-surefire-plugin
- INFO
+ DEBUG
diff --git a/extensions/micrometer/deployment/src/test/resources/test-logging.properties b/extensions/micrometer/deployment/src/test/resources/test-logging.properties
index 6eed6ab2596da2..7a2ecfd97109fe 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 3a38432c1f850f..cec6c00fb1227b 100644
--- a/extensions/micrometer/runtime/pom.xml
+++ b/extensions/micrometer/runtime/pom.xml
@@ -66,6 +66,11 @@
quarkus-resteasy-reactive
true
+
+ io.quarkus
+ quarkus-resteasy-observability
+ true
+
io.quarkus
diff --git a/extensions/pom.xml b/extensions/pom.xml
index 17ba5ac8418729..b49b67cdfd7e88 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -44,6 +44,7 @@
smallrye-opentracing
smallrye-fault-tolerance
jaeger
+ resteasy-observability
micrometer
micrometer-registry-prometheus
opentelemetry
diff --git a/extensions/resteasy-classic/resteasy-server-common/deployment/pom.xml b/extensions/resteasy-classic/resteasy-server-common/deployment/pom.xml
index b1940b0c732023..88a865a8fa7386 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 @@
io.quarkus
quarkus-undertow-spi
+
+ io.quarkus
+ quarkus-resteasy-observability-spi
+
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 ed472ecaf1eeaa..f30f125f94c9b9 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 proxyDefinition,
BuildProducer transformers,
BuildProducer resteasyServerConfig,
+ BuildProducer resteasyApplicationPath,
BuildProducer resteasyDeployment,
BuildProducer unremovableBeans,
BuildProducer annotationsTransformer,
@@ -398,6 +400,7 @@ public void build(
ArcUndeclaredThrowableException.class.getName());
resteasyServerConfig.produce(new ResteasyServerConfigBuildItem(rootPath, path, resteasyInitParameters));
+ resteasyApplicationPath.produce(new RestApplicationPathBuildItem(path));
Set 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 00000000000000..24917d8283563c
--- /dev/null
+++ b/extensions/resteasy-observability/deployment/pom.xml
@@ -0,0 +1,76 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-resteasy-observability-parent
+ 999-SNAPSHOT
+ ../pom.xml
+
+
+ quarkus-resteasy-observability-deployment
+ Quarkus - RESTEasy Observability - Deployment
+
+
+
+ io.quarkus
+ quarkus-core-deployment
+
+
+ io.quarkus
+ quarkus-arc-deployment
+
+
+ io.quarkus
+ quarkus-resteasy-observability
+
+
+ io.quarkus
+ quarkus-resteasy-observability-spi
+
+
+
+
+
+ io.quarkus
+ quarkus-junit5-internal
+ test
+
+
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
+
+
+
+
+ maven-surefire-plugin
+
+
+ INFO
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
\ 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 00000000000000..b1f2e05f3989a4
--- /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 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 00000000000000..504fc914a0ba84
--- /dev/null
+++ b/extensions/resteasy-observability/pom.xml
@@ -0,0 +1,24 @@
+
+
+ 4.0.0
+
+
+ quarkus-extensions-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../pom.xml
+
+
+
+ quarkus-resteasy-observability-parent
+ Quarkus - RESTEasy Observability
+ pom
+
+
+ spi
+ runtime
+ deployment
+
+
\ 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 00000000000000..72dfda4a6db085
--- /dev/null
+++ b/extensions/resteasy-observability/runtime/pom.xml
@@ -0,0 +1,51 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-resteasy-observability-parent
+ 999-SNAPSHOT
+ ../pom.xml
+
+
+ quarkus-resteasy-observability
+ Quarkus - RESTEasy Observability - Runtime
+ Telemetry for RESTEasy workloads
+
+
+
+ io.quarkus
+ quarkus-core
+
+
+
+ io.quarkus
+ quarkus-arc
+
+
+
+
+
+
+
+ io.quarkus
+ quarkus-bootstrap-maven-plugin
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
\ 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 00000000000000..f234a7e3948529
--- /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 00000000000000..ad689daa22a3cc
--- /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 annotations = (Set) 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 00000000000000..9b3c8169aec85e
--- /dev/null
+++ b/extensions/resteasy-observability/spi/pom.xml
@@ -0,0 +1,24 @@
+
+
+ 4.0.0
+
+
+ quarkus-resteasy-observability-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../pom.xml
+
+
+ quarkus-resteasy-observability-spi
+ Telemetry SPI for RESTEasy workloads
+
+
+
+ io.quarkus
+ quarkus-core-deployment
+
+
+
+
\ 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 00000000000000..43a2c19b5ac2b8
--- /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 a4a896e2a2f50f..79e3186aa06f9e 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 @@
io.quarkus
quarkus-resteasy-reactive-common-deployment
+
+ io.quarkus
+ quarkus-resteasy-observability-spi
+
io.quarkus
quarkus-security-deployment
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 9b893a9faaf08e..fad7a1c03cbce4 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 reflectiveClass,
BuildProducer reflectiveHierarchy,
BuildProducer routes,
+ BuildProducer 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();