diff --git a/pom.xml b/pom.xml
index 63a16b0..70703dc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,6 +11,7 @@
quarkus-json-service
quarkus-message-digest-service
quarkus-random-number-generator
+ quarkus-tracing-service
quarkus-uuid-generator
diff --git a/quarkus-tracing-service/pom.xml b/quarkus-tracing-service/pom.xml
new file mode 100644
index 0000000..b95f87d
--- /dev/null
+++ b/quarkus-tracing-service/pom.xml
@@ -0,0 +1,44 @@
+
+
+ 4.0.0
+
+
+ ch.phoenix.oss
+ quarkus-commons
+ 1.0.1-SNAPSHOT
+
+
+ quarkus-tracing-service
+ jar
+
+
+
+ io.quarkus
+ quarkus-rest-jackson
+
+
+ io.quarkus
+ quarkus-opentelemetry
+
+
+ io.quarkus
+ quarkus-security
+
+
+ io.quarkus
+ quarkus-elytron-security-properties-file
+ test
+
+
+ io.quarkus
+ quarkus-config-yaml
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
+
diff --git a/quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/LowerCaseStringConverter.java b/quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/LowerCaseStringConverter.java
new file mode 100644
index 0000000..ede1518
--- /dev/null
+++ b/quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/LowerCaseStringConverter.java
@@ -0,0 +1,10 @@
+package ch.phoenix.oss.quarkus.commons.tracing;
+
+import org.eclipse.microprofile.config.spi.Converter;
+
+public class LowerCaseStringConverter implements Converter {
+ @Override
+ public String convert(String value) throws IllegalArgumentException, NullPointerException {
+ return value.toLowerCase();
+ }
+}
diff --git a/quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingConfiguration.java b/quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingConfiguration.java
new file mode 100644
index 0000000..46e2854
--- /dev/null
+++ b/quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingConfiguration.java
@@ -0,0 +1,40 @@
+package ch.phoenix.oss.quarkus.commons.tracing;
+
+import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.WithConverter;
+import io.smallrye.config.WithDefault;
+import java.util.Optional;
+import java.util.Set;
+
+@ConfigMapping(prefix = "phoenix.commons.tracing")
+public interface TracingConfiguration {
+
+ RequestFilterConfiguration requestFilter();
+
+ interface RequestFilterConfiguration {
+
+ Headers headers();
+
+ interface Headers {
+
+ Optional> redact();
+ }
+
+ Path path();
+
+ interface Path {
+
+ @WithDefault("false")
+ boolean includeRaw();
+ }
+
+ Query query();
+
+ interface Query {
+ @WithDefault("false")
+ boolean includeRaw();
+
+ Optional> redact();
+ }
+ }
+}
diff --git a/quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingConstants.java b/quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingConstants.java
new file mode 100644
index 0000000..7022a22
--- /dev/null
+++ b/quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingConstants.java
@@ -0,0 +1,18 @@
+package ch.phoenix.oss.quarkus.commons.tracing;
+
+public class TracingConstants {
+
+ public static final String ACTOR = "actor";
+ public static final String ANONYMOUS = "anonymous";
+ public static final String REQUEST_ROUTE = "request.route";
+ public static final String REQUEST_METHOD = "request.method";
+ public static final String REQUEST_PATH_RAW = "request.path.raw";
+ public static final String REQUEST_PATH_PARAMS = "request.path.params";
+ public static final String REQUEST_QUERY_RAW = "request.query.raw";
+ public static final String REQUEST_QUERY_PARAMS = "request.query.params";
+ public static final String REQUEST_HEADERS = "request.headers";
+ public static final String REQUEST_CLIENT_IP = "request.client.ip";
+ public static final String SCHEDULER_JOB_NAME = "scheduler.job.name";
+
+ private TracingConstants() {}
+}
diff --git a/quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingRequestFilter.java b/quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingRequestFilter.java
new file mode 100644
index 0000000..4a1cbd4
--- /dev/null
+++ b/quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingRequestFilter.java
@@ -0,0 +1,139 @@
+package ch.phoenix.oss.quarkus.commons.tracing;
+
+import io.quarkus.arc.Unremovable;
+import io.quarkus.logging.Log;
+import io.quarkus.security.identity.SecurityIdentity;
+import io.vertx.ext.web.RoutingContext;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ResourceInfo;
+import java.util.List;
+import java.util.Set;
+import org.jboss.resteasy.reactive.server.ServerRequestFilter;
+
+@Unremovable
+public class TracingRequestFilter {
+
+ private final RoutingContext routingContext;
+ private final TracingService tracingService;
+ private final SecurityIdentity securityIdentity;
+ private final TracingConfiguration configuration;
+
+ @Inject
+ public TracingRequestFilter(
+ RoutingContext routingContext,
+ TracingService tracingService,
+ SecurityIdentity securityIdentity,
+ TracingConfiguration configuration) {
+ this.routingContext = routingContext;
+ this.tracingService = tracingService;
+ this.securityIdentity = securityIdentity;
+ this.configuration = configuration;
+ }
+
+ @ServerRequestFilter
+ public void filter(ContainerRequestContext requestContext, ResourceInfo resourceInfo) {
+ if (securityIdentity.isAnonymous()) {
+ tracingService.trace(TracingConstants.ACTOR, TracingConstants.ANONYMOUS);
+ } else {
+ tracingService.trace(
+ TracingConstants.ACTOR, securityIdentity.getPrincipal().getName());
+ }
+
+ var method = requestContext.getMethod();
+ tracingService.trace(TracingConstants.REQUEST_METHOD, method);
+
+ var routePattern = getRoutePattern(resourceInfo);
+ tracingService.trace(TracingConstants.REQUEST_ROUTE, routePattern);
+
+ var uriInfo = requestContext.getUriInfo();
+ uriInfo.getPathParameters()
+ .forEach((key, value) ->
+ tracingService.trace(TracingConstants.REQUEST_PATH_PARAMS + '.' + key, joinStrings(value)));
+
+ if (configuration.requestFilter().path().includeRaw()) {
+ tracingService.trace(
+ TracingConstants.REQUEST_PATH_RAW, uriInfo.getAbsolutePath().getRawPath());
+ }
+
+ requestContext.getHeaders().forEach((key, value) -> {
+ var lowerCaseKey = key.toLowerCase();
+ var property = TracingConstants.REQUEST_HEADERS + '.' + lowerCaseKey;
+ if (configuration
+ .requestFilter()
+ .headers()
+ .redact()
+ .orElse(Set.of())
+ .contains(lowerCaseKey)) {
+ tracingService.trace(property, "********");
+ } else {
+ tracingService.trace(property, joinStrings(value));
+ }
+ });
+
+ uriInfo.getQueryParameters()
+ .forEach((key, value) ->
+ tracingService.trace(TracingConstants.REQUEST_QUERY_PARAMS + '.' + key, joinStrings(value)));
+
+ if (configuration.requestFilter().query().includeRaw()) {
+ var rawQuery = uriInfo.getRequestUri().getRawQuery();
+ if (rawQuery != null && !rawQuery.isBlank()) {
+ tracingService.trace(TracingConstants.REQUEST_QUERY_RAW, rawQuery);
+ }
+ }
+
+ tracingService.trace(
+ TracingConstants.REQUEST_CLIENT_IP,
+ routingContext.request().connection().remoteAddress(true).hostAddress());
+
+ if (Log.isTraceEnabled()) {
+ Log.tracef(
+ "Incoming request: %s %s", method, uriInfo.getAbsolutePath().getRawPath());
+ }
+ }
+
+ private static String joinStrings(List value) {
+ return String.join(", ", value);
+ }
+
+ private String getRoutePattern(ResourceInfo resourceInfo) {
+ String classPath = getPathValue(resourceInfo.getResourceClass().getAnnotation(Path.class));
+ String methodPath = getPathValue(resourceInfo.getResourceMethod().getAnnotation(Path.class));
+
+ if (!classPath.isEmpty()) {
+ if (methodPath.isEmpty()) {
+ return "/" + classPath;
+ } else {
+ return "/" + classPath + "/" + methodPath;
+ }
+ } else {
+ if (methodPath.isEmpty()) {
+ return "/";
+ } else {
+ return "/" + methodPath;
+ }
+ }
+ }
+
+ private static String getPathValue(Path path) {
+ if (path == null) {
+ return "";
+ }
+ return trimSlashes(path.value());
+ }
+
+ private static String trimSlashes(String segment) {
+ if (segment.isEmpty() || "/".equals(segment)) {
+ return "";
+ }
+
+ // Assuming that it's not possible for a segment to contain //,
+ // thus it's possible to avoid extra ifs and whiles, as well
+ // as the use of regexes
+ int start = (segment.charAt(0) == '/') ? 1 : 0;
+ int end = (segment.charAt(segment.length() - 1) == '/') ? segment.length() - 1 : segment.length();
+
+ return segment.substring(start, end);
+ }
+}
diff --git a/quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingService.java b/quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingService.java
new file mode 100644
index 0000000..2b1ac38
--- /dev/null
+++ b/quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingService.java
@@ -0,0 +1,24 @@
+package ch.phoenix.oss.quarkus.commons.tracing;
+
+public interface TracingService {
+
+ void clearAll();
+
+ void trace(String key, Object value);
+
+ String getActor();
+
+ String getRequestPath();
+
+ String getRequestMethod();
+
+ String getRequestId();
+
+ String getTraceId();
+
+ String getSpanId();
+
+ String getClientIp();
+
+ String getSchedulerJob();
+}
diff --git a/quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingServiceImpl.java b/quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingServiceImpl.java
new file mode 100644
index 0000000..0970b35
--- /dev/null
+++ b/quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingServiceImpl.java
@@ -0,0 +1,69 @@
+package ch.phoenix.oss.quarkus.commons.tracing;
+
+import io.opentelemetry.api.trace.Span;
+import io.quarkus.arc.DefaultBean;
+import io.quarkus.logging.Log;
+import jakarta.enterprise.context.ApplicationScoped;
+import org.jboss.logging.MDC;
+
+@DefaultBean
+@ApplicationScoped
+class TracingServiceImpl implements TracingService {
+
+ private final Span span;
+
+ TracingServiceImpl(Span span) {
+ this.span = span;
+ }
+
+ @Override
+ public void clearAll() {
+ MDC.clear();
+ }
+
+ @Override
+ public void trace(final String key, final Object value) {
+ Log.infof("tracing key=%s value=%s", key, value);
+ MDC.put(key, value);
+ }
+
+ @Override
+ public String getActor() {
+ return (String) MDC.get(TracingConstants.ACTOR);
+ }
+
+ @Override
+ public String getRequestPath() {
+ return (String) MDC.get(TracingConstants.REQUEST_PATH_RAW);
+ }
+
+ @Override
+ public String getRequestMethod() {
+ return (String) MDC.get(TracingConstants.REQUEST_METHOD);
+ }
+
+ @Override
+ public String getRequestId() {
+ return (String) MDC.get(TracingConstants.REQUEST_HEADERS + ".x-request-id");
+ }
+
+ @Override
+ public String getTraceId() {
+ return span.getSpanContext().getTraceId();
+ }
+
+ @Override
+ public String getSpanId() {
+ return span.getSpanContext().getSpanId();
+ }
+
+ @Override
+ public String getClientIp() {
+ return (String) MDC.get(TracingConstants.REQUEST_CLIENT_IP);
+ }
+
+ @Override
+ public String getSchedulerJob() {
+ return (String) MDC.get(TracingConstants.SCHEDULER_JOB_NAME);
+ }
+}
diff --git a/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/ActorTest.java b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/ActorTest.java
new file mode 100644
index 0000000..2190561
--- /dev/null
+++ b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/ActorTest.java
@@ -0,0 +1,44 @@
+package ch.phoenix.oss.quarkus.commons.tracing;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.startsWith;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.mockito.InjectSpy;
+import io.restassured.RestAssured;
+import io.restassured.http.ContentType;
+import org.junit.jupiter.api.Test;
+
+@QuarkusTest
+public class ActorTest {
+
+ @InjectSpy
+ TracingService tracingService;
+
+ @Test
+ void getAuthenticated() {
+ var route = "/authenticated";
+ RestAssured.given()
+ .auth()
+ .basic("jon", "doe")
+ .accept(ContentType.TEXT)
+ .when()
+ .get(route)
+ .then()
+ .statusCode(200);
+
+ verify(tracingService).trace("actor", "jon");
+ verify(tracingService).trace("request.method", "GET");
+ verify(tracingService).trace("request.route", route);
+ verify(tracingService).trace("request.headers.accept", "text/plain");
+ verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
+ verify(tracingService).trace("request.headers.authorization", "Basic am9uOmRvZQ==");
+ verify(tracingService).trace("request.headers.connection", "Keep-Alive");
+ verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
+ verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));
+ verify(tracingService).trace("request.client.ip", "127.0.0.1");
+ verifyNoMoreInteractions(tracingService);
+ }
+}
diff --git a/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/RawPathTest.java b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/RawPathTest.java
new file mode 100644
index 0000000..96d8956
--- /dev/null
+++ b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/RawPathTest.java
@@ -0,0 +1,46 @@
+package ch.phoenix.oss.quarkus.commons.tracing;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.startsWith;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.TestProfile;
+import io.quarkus.test.junit.mockito.InjectSpy;
+import io.restassured.RestAssured;
+import io.restassured.http.ContentType;
+import org.junit.jupiter.api.Test;
+
+@QuarkusTest
+@TestProfile(Test2Profile.class)
+public class RawPathTest {
+
+ @InjectSpy
+ TracingService tracingService;
+
+ @Test
+ void getWithRawPath() {
+ var route = "/no-leading-and-trailing/{param}/{param2}";
+ RestAssured.given()
+ .accept(ContentType.TEXT)
+ .when()
+ .get(route, 1, 2)
+ .then()
+ .statusCode(200);
+
+ verify(tracingService).trace("actor", "anonymous");
+ verify(tracingService).trace("request.method", "GET");
+ verify(tracingService).trace("request.route", route);
+ verify(tracingService).trace("request.path.params.param", "1");
+ verify(tracingService).trace("request.path.params.param2", "2");
+ verify(tracingService).trace("request.path.raw", "/no-leading-and-trailing/1/2");
+ verify(tracingService).trace("request.headers.accept", "text/plain");
+ verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
+ verify(tracingService).trace("request.headers.connection", "Keep-Alive");
+ verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
+ verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));
+ verify(tracingService).trace("request.client.ip", "127.0.0.1");
+ verifyNoMoreInteractions(tracingService);
+ }
+}
diff --git a/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/RoutePatternTest.java b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/RoutePatternTest.java
new file mode 100644
index 0000000..11f1689
--- /dev/null
+++ b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/RoutePatternTest.java
@@ -0,0 +1,204 @@
+package ch.phoenix.oss.quarkus.commons.tracing;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.startsWith;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.mockito.InjectSpy;
+import io.restassured.RestAssured;
+import io.restassured.http.ContentType;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+@QuarkusTest
+class RoutePatternTest {
+
+ @InjectSpy
+ TracingService tracingService;
+
+ @Test
+ void getBlankResource() {
+ var route = "/";
+ RestAssured.given().accept(ContentType.TEXT).when().get(route).then().statusCode(200);
+
+ verifyGetTracing(route, Map.of());
+ }
+
+ @Test
+ void postBlankResource() {
+ var route = "/";
+ RestAssured.given().accept(ContentType.TEXT).when().post(route).then().statusCode(200);
+
+ verify(tracingService).trace("actor", "anonymous");
+ verify(tracingService).trace("request.method", "POST");
+ verify(tracingService).trace("request.route", route);
+ verify(tracingService).trace("request.headers.accept", "text/plain");
+ verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
+ verify(tracingService).trace("request.headers.connection", "Keep-Alive");
+ verify(tracingService).trace("request.headers.content-length", "0");
+ verify(tracingService)
+ .trace("request.headers.content-type", "application/x-www-form-urlencoded; charset=ISO-8859-1");
+ verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
+ verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));
+ verify(tracingService).trace("request.client.ip", "127.0.0.1");
+ verifyNoMoreInteractions(tracingService);
+ }
+
+ @Test
+ void getLeadingResource() {
+ var route = "/leading/{id}/{anotherId}";
+ RestAssured.given()
+ .accept(ContentType.TEXT)
+ .when()
+ .get(route, 1, 2)
+ .then()
+ .statusCode(200);
+
+ verifyGetTracing(route, Map.of("id", "1", "anotherId", "2"));
+ }
+
+ @Test
+ void getTrailingResource() {
+ var route = "/{id}/{anotherId}/trailing";
+ RestAssured.given()
+ .accept(ContentType.TEXT)
+ .when()
+ .get(route, 1, 2)
+ .then()
+ .statusCode(200);
+
+ verifyGetTracing(route, Map.of("id", "1", "anotherId", "2"));
+ }
+
+ @Test
+ void getLeadingAndNoTrailingResource() {
+ var route = "/leading-and-no-trailing";
+ RestAssured.given().accept(ContentType.TEXT).when().get(route).then().statusCode(200);
+
+ verifyGetTracing(route, Map.of());
+ }
+
+ @Test
+ void getLeadingAndNoTrailingWithSingleParamResource() {
+ var route = "/leading-and-no-trailing/{param}";
+ RestAssured.given().accept(ContentType.TEXT).when().get(route, 1).then().statusCode(200);
+
+ verifyGetTracing(route, Map.of("param", "1"));
+ }
+
+ @Test
+ void getLeadingAndNoTrailingWithMultiParamResource() {
+ var route = "/leading-and-no-trailing/{param}/{param2}";
+ RestAssured.given()
+ .accept(ContentType.TEXT)
+ .when()
+ .get(route, 1, 2)
+ .then()
+ .statusCode(200);
+
+ verifyGetTracing(route, Map.of("param", "1", "param2", "2"));
+ }
+
+ @Test
+ void getLeadingAndTrailingResource() {
+ var route = "/leading-and-trailing";
+ RestAssured.given().accept(ContentType.TEXT).when().get(route).then().statusCode(200);
+
+ verifyGetTracing(route, Map.of());
+ }
+
+ @Test
+ void getLeadingAndTrailingWithSingleParamResource() {
+ var route = "/leading-and-trailing/{param}";
+ RestAssured.given().accept(ContentType.TEXT).when().get(route, 1).then().statusCode(200);
+
+ verifyGetTracing(route, Map.of("param", "1"));
+ }
+
+ @Test
+ void getLeadingAndTrailingWithMultiParamResource() {
+ var route = "/leading-and-trailing/{param}/{param2}";
+ RestAssured.given()
+ .accept(ContentType.TEXT)
+ .when()
+ .get(route, 1, 2)
+ .then()
+ .statusCode(200);
+
+ verifyGetTracing(route, Map.of("param", "1", "param2", "2"));
+ }
+
+ @Test
+ void getNoLeadingAndNoTrailingResource() {
+ var route = "/no-leading-and-no-trailing";
+ RestAssured.given().accept(ContentType.TEXT).when().get(route).then().statusCode(200);
+
+ verifyGetTracing(route, Map.of());
+ }
+
+ @Test
+ void geNoLeadingAndNoTrailingWithSingleParamResource() {
+ var route = "/no-leading-and-no-trailing/{param}";
+ RestAssured.given().accept(ContentType.TEXT).when().get(route, 1).then().statusCode(200);
+
+ verifyGetTracing(route, Map.of("param", "1"));
+ }
+
+ @Test
+ void getNoLeadingAndNoTrailingWithMultiParamResource() {
+ var route = "/no-leading-and-no-trailing/{param}/{param2}";
+ RestAssured.given()
+ .accept(ContentType.TEXT)
+ .when()
+ .get(route, 1, 2)
+ .then()
+ .statusCode(200);
+
+ verifyGetTracing(route, Map.of("param", "1", "param2", "2"));
+ }
+
+ @Test
+ void getNoLeadingAndTrailingResource() {
+ var route = "/no-leading-and-trailing";
+ RestAssured.given().accept(ContentType.TEXT).when().get(route).then().statusCode(200);
+
+ verifyGetTracing(route, Map.of());
+ }
+
+ @Test
+ void getNoLeadingAndTrailingWithSingleParamResource() {
+ var route = "/no-leading-and-trailing/{param}";
+ RestAssured.given().accept(ContentType.TEXT).when().get(route, 1).then().statusCode(200);
+
+ verifyGetTracing(route, Map.of("param", "1"));
+ }
+
+ @Test
+ void getNoLeadingAndTrailingWithMultiParamResource() {
+ var route = "/no-leading-and-trailing/{param}/{param2}";
+ RestAssured.given()
+ .accept(ContentType.TEXT)
+ .when()
+ .get(route, 1, 2)
+ .then()
+ .statusCode(200);
+
+ verifyGetTracing(route, Map.of("param", "1", "param2", "2"));
+ }
+
+ private void verifyGetTracing(String route, Map pathParams) {
+ verify(tracingService).trace("actor", "anonymous");
+ verify(tracingService).trace("request.method", "GET");
+ verify(tracingService).trace("request.route", route);
+ pathParams.forEach((key, value) -> verify(tracingService).trace("request.path.params." + key, value));
+ verify(tracingService).trace("request.headers.accept", "text/plain");
+ verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
+ verify(tracingService).trace("request.headers.connection", "Keep-Alive");
+ verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
+ verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));
+ verify(tracingService).trace("request.client.ip", "127.0.0.1");
+ verifyNoMoreInteractions(tracingService);
+ }
+}
diff --git a/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/Test2Profile.java b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/Test2Profile.java
new file mode 100644
index 0000000..f98dcff
--- /dev/null
+++ b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/Test2Profile.java
@@ -0,0 +1,11 @@
+package ch.phoenix.oss.quarkus.commons.tracing;
+
+import io.quarkus.test.junit.QuarkusTestProfile;
+
+public class Test2Profile implements QuarkusTestProfile {
+
+ @Override
+ public String getConfigProfile() {
+ return "test2";
+ }
+}
diff --git a/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/AuthenticatedResource.java b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/AuthenticatedResource.java
new file mode 100644
index 0000000..d18b967
--- /dev/null
+++ b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/AuthenticatedResource.java
@@ -0,0 +1,15 @@
+package ch.phoenix.oss.quarkus.commons.tracing.resource;
+
+import io.quarkus.security.Authenticated;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+
+@Authenticated
+@Path("authenticated")
+public class AuthenticatedResource {
+
+ @GET
+ public String get() {
+ return "Hello user";
+ }
+}
diff --git a/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/BlankResource.java b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/BlankResource.java
new file mode 100644
index 0000000..04c5df9
--- /dev/null
+++ b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/BlankResource.java
@@ -0,0 +1,25 @@
+package ch.phoenix.oss.quarkus.commons.tracing.resource;
+
+import jakarta.annotation.security.PermitAll;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("")
+@Produces(MediaType.TEXT_PLAIN)
+public class BlankResource {
+
+ @GET
+ @PermitAll
+ public String get() {
+ return "get";
+ }
+
+ @POST
+ @Path("")
+ public String post() {
+ return "post";
+ }
+}
diff --git a/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/LeadingAndNoTrailingResource.java b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/LeadingAndNoTrailingResource.java
new file mode 100644
index 0000000..b03a7ae
--- /dev/null
+++ b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/LeadingAndNoTrailingResource.java
@@ -0,0 +1,29 @@
+package ch.phoenix.oss.quarkus.commons.tracing.resource;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("/leading-and-no-trailing")
+@Produces(MediaType.TEXT_PLAIN)
+public class LeadingAndNoTrailingResource {
+
+ @GET
+ @Path("")
+ public String root() {
+ return "root";
+ }
+
+ @GET
+ @Path("/{param}")
+ public String singleParam(String param) {
+ return param;
+ }
+
+ @GET
+ @Path("/{param}/{param2}")
+ public String multiParam(String param, String param2) {
+ return param + "/" + param2;
+ }
+}
diff --git a/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/LeadingAndTrailingResource.java b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/LeadingAndTrailingResource.java
new file mode 100644
index 0000000..1dbc175
--- /dev/null
+++ b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/LeadingAndTrailingResource.java
@@ -0,0 +1,29 @@
+package ch.phoenix.oss.quarkus.commons.tracing.resource;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("/leading-and-trailing/")
+@Produces(MediaType.TEXT_PLAIN)
+public class LeadingAndTrailingResource {
+
+ @GET
+ @Path("/")
+ public String root() {
+ return "root";
+ }
+
+ @GET
+ @Path("/{param}/")
+ public String singleParam(String param) {
+ return param;
+ }
+
+ @GET
+ @Path("/{param}/{param2}/")
+ public String multiParam(String param, String param2) {
+ return param + "/" + param2;
+ }
+}
diff --git a/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/NoLeadingAndNoTrailingResource.java b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/NoLeadingAndNoTrailingResource.java
new file mode 100644
index 0000000..d75dade
--- /dev/null
+++ b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/NoLeadingAndNoTrailingResource.java
@@ -0,0 +1,29 @@
+package ch.phoenix.oss.quarkus.commons.tracing.resource;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("no-leading-and-no-trailing")
+@Produces(MediaType.TEXT_PLAIN)
+public class NoLeadingAndNoTrailingResource {
+
+ @GET
+ @Path("")
+ public String root() {
+ return "root";
+ }
+
+ @GET
+ @Path("{param}")
+ public String singleParam(String param) {
+ return param;
+ }
+
+ @GET
+ @Path("{param}/{param2}")
+ public String multiParam(String param, String param2) {
+ return param + "/" + param2;
+ }
+}
diff --git a/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/NoLeadingAndTrailingResource.java b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/NoLeadingAndTrailingResource.java
new file mode 100644
index 0000000..9c9805a
--- /dev/null
+++ b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/NoLeadingAndTrailingResource.java
@@ -0,0 +1,29 @@
+package ch.phoenix.oss.quarkus.commons.tracing.resource;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("no-leading-and-trailing/")
+@Produces(MediaType.TEXT_PLAIN)
+public class NoLeadingAndTrailingResource {
+
+ @GET
+ @Path("/")
+ public String root() {
+ return "root";
+ }
+
+ @GET
+ @Path("{param}/")
+ public String singleParam(String param) {
+ return param;
+ }
+
+ @GET
+ @Path("{param}/{param2}/")
+ public String multiParam(String param, String param2) {
+ return param + "/" + param2;
+ }
+}
diff --git a/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/SlashResource.java b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/SlashResource.java
new file mode 100644
index 0000000..690c84a
--- /dev/null
+++ b/quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/SlashResource.java
@@ -0,0 +1,23 @@
+package ch.phoenix.oss.quarkus.commons.tracing.resource;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("/")
+@Produces(MediaType.TEXT_PLAIN)
+public class SlashResource {
+
+ @GET
+ @Path("/leading/{id}/{anotherId}")
+ public String doubleLeading(int id, int anotherId) {
+ return "leading/" + id + "/" + anotherId;
+ }
+
+ @GET
+ @Path("{id}/{anotherId}/trailing/")
+ public String doubleTrailing(int id, int anotherId) {
+ return id + "/" + anotherId + "/trailing";
+ }
+}
diff --git a/quarkus-tracing-service/src/test/resources/application.yaml b/quarkus-tracing-service/src/test/resources/application.yaml
new file mode 100644
index 0000000..9d0943f
--- /dev/null
+++ b/quarkus-tracing-service/src/test/resources/application.yaml
@@ -0,0 +1,38 @@
+quarkus:
+ http:
+ test-port: 0
+ log:
+ category:
+ "org.apache.http.wire":
+ level: DEBUG # set to DEBUG when RestAssured logs are necessary to understand test failures
+ otel:
+ traces:
+ exporter: none
+ security:
+ users:
+ embedded:
+ enabled: true
+ plain-text: true
+ users:
+ jon: doe
+
+"%test2":
+ quarkus:
+ log:
+ min-level: TRACE
+ category:
+ "ch.phoenix.oss.quarkus.commons.tracing":
+ level: TRACE
+ phoenix:
+ commons:
+ tracing:
+ request-filter:
+ path:
+ include-raw: true
+ headers:
+ redact:
+ - AUTHORIZATION
+ query:
+ include-raw: true
+ redact:
+ - ACCESS_TOKEN
\ No newline at end of file