From db0026b7236a3ea4744406fdd7355c1e636273d5 Mon Sep 17 00:00:00 2001 From: Jorge Bornhausen Date: Wed, 18 Jun 2025 01:40:26 +0200 Subject: [PATCH] feat(tracing): add quarkus-tracing-service module --- pom.xml | 1 + quarkus-tracing-service/pom.xml | 44 ++++ .../tracing/LowerCaseStringConverter.java | 10 + .../commons/tracing/TracingConfiguration.java | 40 ++++ .../commons/tracing/TracingConstants.java | 18 ++ .../commons/tracing/TracingRequestFilter.java | 139 ++++++++++++ .../commons/tracing/TracingService.java | 24 +++ .../commons/tracing/TracingServiceImpl.java | 69 ++++++ .../quarkus/commons/tracing/ActorTest.java | 44 ++++ .../quarkus/commons/tracing/RawPathTest.java | 46 ++++ .../commons/tracing/RoutePatternTest.java | 204 ++++++++++++++++++ .../quarkus/commons/tracing/Test2Profile.java | 11 + .../resource/AuthenticatedResource.java | 15 ++ .../tracing/resource/BlankResource.java | 25 +++ .../LeadingAndNoTrailingResource.java | 29 +++ .../resource/LeadingAndTrailingResource.java | 29 +++ .../NoLeadingAndNoTrailingResource.java | 29 +++ .../NoLeadingAndTrailingResource.java | 29 +++ .../tracing/resource/SlashResource.java | 23 ++ .../src/test/resources/application.yaml | 38 ++++ 20 files changed, 867 insertions(+) create mode 100644 quarkus-tracing-service/pom.xml create mode 100644 quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/LowerCaseStringConverter.java create mode 100644 quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingConfiguration.java create mode 100644 quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingConstants.java create mode 100644 quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingRequestFilter.java create mode 100644 quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingService.java create mode 100644 quarkus-tracing-service/src/main/java/ch/phoenix/oss/quarkus/commons/tracing/TracingServiceImpl.java create mode 100644 quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/ActorTest.java create mode 100644 quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/RawPathTest.java create mode 100644 quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/RoutePatternTest.java create mode 100644 quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/Test2Profile.java create mode 100644 quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/AuthenticatedResource.java create mode 100644 quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/BlankResource.java create mode 100644 quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/LeadingAndNoTrailingResource.java create mode 100644 quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/LeadingAndTrailingResource.java create mode 100644 quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/NoLeadingAndNoTrailingResource.java create mode 100644 quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/NoLeadingAndTrailingResource.java create mode 100644 quarkus-tracing-service/src/test/java/ch/phoenix/oss/quarkus/commons/tracing/resource/SlashResource.java create mode 100644 quarkus-tracing-service/src/test/resources/application.yaml 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