Compare commits

...

29 commits
1.0.5 ... main

Author SHA1 Message Date
1e9ab13c97
ci(github-actions): simplify maven settings
All checks were successful
Build / build (push) Successful in 2m18s
2025-07-30 15:35:34 +02:00
58a53fe280
chore: prepare for next development iteration
Some checks failed
Build / build (push) Has been cancelled
2025-07-30 13:34:47 +00:00
584ffc85f2
chore: release 1.0.8
All checks were successful
Build / build (push) Successful in 2m10s
2025-07-30 13:34:45 +00:00
9621f913e3
chore: bump version to 1.0.8
All checks were successful
Release the current version / Execute the release (push) Successful in 1m46s
2025-07-30 15:33:00 +02:00
25ea6c556d
feat(deps): update quarkus.platform.version to 3.25.0
All checks were successful
Build / build (push) Successful in 2m14s
2025-07-30 15:27:53 +02:00
7a33e9cdbb
chore: prepare for next development iteration
All checks were successful
Build / build (push) Successful in 2m19s
2025-07-30 12:52:53 +00:00
6a3026de70
chore: release 1.0.7
All checks were successful
Build / build (push) Successful in 2m6s
2025-07-30 12:52:52 +00:00
8269687a6e Merge pull request 'chore: bump version to 1.0.7' (#100) from version into main
All checks were successful
Release the current version / Execute the release (push) Successful in 1m31s
Reviewed-on: #100
2025-07-30 12:51:24 +00:00
3ae16bfa25
chore: bump version to 1.0.7
All checks were successful
Validate release versions / release (pull_request) Successful in 4s
Build / build (pull_request) Successful in 2m0s
2025-07-30 14:49:00 +02:00
ee96894e87 Merge pull request 'feat(client-logger): redact headers based on configuration' (#99) from redacting into main
All checks were successful
Build / build (push) Successful in 2m9s
Reviewed-on: #99
2025-07-30 12:47:49 +00:00
21913626ad
feat(client-logger): redact headers based on configuration
All checks were successful
Build / build (pull_request) Successful in 2m9s
2025-07-30 14:44:49 +02:00
d1acb1a0ee
chore(maven): enable jacoco rules for audit and tracing modules
All checks were successful
Build / build (push) Successful in 1m57s
2025-07-24 11:53:27 +02:00
bc0110cc29
chore: more sonarqube improvements, rename tracing service method
All checks were successful
Build / build (push) Successful in 2m23s
2025-07-24 11:11:53 +02:00
f591d514ec Merge pull request 'refactor: apply some suggestions from SonarQube' (#98) from sonarqube into main
All checks were successful
Build / build (push) Successful in 2m21s
Reviewed-on: #98
2025-07-23 12:52:04 +00:00
331a830c2b
refactor: apply some suggestions from SonarQube
All checks were successful
Build / build (pull_request) Successful in 1m49s
2025-07-23 14:27:42 +02:00
7d2cda5b20
docs(README): update sonarqube badges 2025-07-22 22:36:43 +02:00
ca915c4bf5
feat(deps): update io.smallrye:jandex-maven-plugin to 3.4.0
All checks were successful
Build / build (push) Successful in 2m6s
2025-07-22 22:31:39 +02:00
cf81524d86
feat(deps): update com.diffplug.spotless:spotless-maven-plugin to 2.46.1
Some checks failed
Build / build (push) Has been cancelled
2025-07-22 22:30:56 +02:00
35d4e29a57
feat(deps): update palantir-java-format.version to 2.72.0
Some checks failed
Build / build (push) Has been cancelled
2025-07-22 22:30:44 +02:00
ed362d84b8
chore(maven): remove skip ci from release message
Some checks failed
Build / build (push) Has been cancelled
2025-07-22 22:30:15 +02:00
fc140833d5
chore: prepare for next development iteration [skip ci] 2025-07-18 12:03:03 +00:00
a0939d7729
chore: release 1.0.6
Some checks failed
Build / build (push) Failing after 1m43s
2025-07-18 12:03:02 +00:00
2d23835810
chore: bump version to 1.0.6
All checks were successful
Release the current version / Execute the release (push) Successful in 1m42s
2025-07-18 14:01:19 +02:00
5e8f8fefa8
ci(github-actions): fix jars not being published on tags
Some checks failed
Release the current version / Execute the release (push) Has been cancelled
Build / build (push) Successful in 1m44s
2025-07-18 14:01:02 +02:00
f6e85f2c00 Merge pull request 'ci(github-actions): revamp build.yaml to trigger on tags and do proper validation of version' (#96) from build into main
All checks were successful
Build / build (push) Successful in 2m7s
Reviewed-on: #96
2025-07-18 08:29:23 +00:00
a5e0c2672b
ci(github-actions): revamp build.yaml to trigger on tags and do proper validation of version
All checks were successful
Build / build (pull_request) Successful in 1m16s
2025-07-18 10:26:00 +02:00
1cc8a39c88 Merge pull request 'fix(deps): update quarkus.platform.version to 3.24.4' (#95) from quarkus-3.24.4 into main
All checks were successful
Build / build (push) Successful in 2m7s
Reviewed-on: #95
2025-07-18 08:04:57 +00:00
8aad75a493
fix(deps): update quarkus.platform.version to 3.24.4
All checks were successful
Build / build (pull_request) Successful in 2m17s
2025-07-18 09:43:50 +02:00
880df82728
chore: prepare for next development iteration [skip ci] 2025-07-12 16:29:59 +00:00
34 changed files with 574 additions and 248 deletions

View file

@ -1,5 +1,5 @@
name: Quarkus Commons
release:
current-version: "1.0.5"
next-version: "1.0.6-SNAPSHOT"
current-version: "1.0.8"
next-version: "1.0.9-SNAPSHOT"

View file

@ -4,6 +4,8 @@ on:
push:
branches:
- "main"
tags:
- '[0-9]+.[0-9]+.[0-9]+'
paths-ignore:
- '.gitattributes'
- '.gitignore'
@ -12,6 +14,7 @@ on:
- 'docs/**'
- 'README.md'
pull_request:
workflow_dispatch:
env:
COMMON_MAVEN_OPTS: "-e -B --fae"
@ -60,19 +63,24 @@ jobs:
servers: |
[{
"id": "phoenix-oss",
"configuration": {
"httpHeaders": {
"property": {
"name": "Authorization",
"value": "token ${{ secrets.ORG_PACKAGE_WRITER_TOKEN }}"
}
}
}
"username": "${{ vars.ORG_PACKAGE_WRITER_USERNAME }}",
"password": "${{ secrets.ORG_PACKAGE_WRITER_TOKEN }}"
}]
- name: Make maven wrapper executable
run: chmod +x mvnw
- name: Validate tag
if: startsWith(github.ref, 'refs/tags/')
run: |
TAG_NAME="${GITHUB_REF#refs/tags/}"
PROJECT_VERSION=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout)
if [[ "$PROJECT_VERSION" != "$TAG_NAME" ]]; then
echo "::error::pom.xml version '$PROJECT_VERSION' does not match tag '$TAG_NAME'"
exit 1
fi
- name: Download dependencies
run: ./mvnw $COMMON_MAVEN_OPTS quarkus:go-offline
@ -87,5 +95,5 @@ jobs:
run: ./mvnw $COMMON_MAVEN_OPTS org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=quarkus-commons -Dsonar.projectName='quarkus-commons' -Dsonar.coverage.jacoco.xmlReportPaths=../**/target/jacoco-report/jacoco.xml
- name: Publish jars
if: github.ref == 'refs/heads/main'
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
run: ./mvnw $COMMON_MAVEN_OPTS deploy -Dmaven.test.skip=true -Dmaven.javadoc.skip=true

View file

@ -2,12 +2,12 @@ Quarkus Commons
===============
[![Java version](https://img.shields.io/badge/Java%20version-21-brightgreen)](https://openjdk.org/projects/jdk/21/)
[![Coverage](https://sonarqube.pub.production.kvant.cloud/api/project_badges/measure?project=quarkus-commons&metric=coverage&token=sqb_b39e0a05145228a10eb07d8771fd073297800645)](https://sonarqube.pub.production.kvant.cloud/dashboard?id=quarkus-commons)
[![Duplicated Lines (%)](https://sonarqube.pub.production.kvant.cloud/api/project_badges/measure?project=quarkus-commons&metric=duplicated_lines_density&token=sqb_b39e0a05145228a10eb07d8771fd073297800645)](https://sonarqube.pub.production.kvant.cloud/dashboard?id=quarkus-commons)
[![Quality Gate Status](https://sonarqube.pub.production.kvant.cloud/api/project_badges/measure?project=quarkus-commons&metric=alert_status&token=sqb_b39e0a05145228a10eb07d8771fd073297800645)](https://sonarqube.pub.production.kvant.cloud/dashboard?id=quarkus-commons)
[![Security Rating](https://sonarqube.pub.production.kvant.cloud/api/project_badges/measure?project=quarkus-commons&metric=security_rating&token=sqb_b39e0a05145228a10eb07d8771fd073297800645)](https://sonarqube.pub.production.kvant.cloud/dashboard?id=quarkus-commons)
[![Reliability Rating](https://sonarqube.pub.production.kvant.cloud/api/project_badges/measure?project=quarkus-commons&metric=reliability_rating&token=sqb_b39e0a05145228a10eb07d8771fd073297800645)](https://sonarqube.pub.production.kvant.cloud/dashboard?id=quarkus-commons)
[![Maintainability Rating](https://sonarqube.pub.production.kvant.cloud/api/project_badges/measure?project=quarkus-commons&metric=sqale_rating&token=sqb_b39e0a05145228a10eb07d8771fd073297800645)](https://sonarqube.pub.production.kvant.cloud/dashboard?id=quarkus-commons)
[![Coverage](https://sonarqube.pub.basel.kvant.cloud/api/project_badges/measure?project=quarkus-commons&metric=coverage&token=sqb_b56d9ea175c7f51f522ce63acd7fe7807643ac9e)](https://sonarqube.pub.basel.kvant.cloud/dashboard?id=quarkus-commons)
[![Duplicated Lines (%)](https://sonarqube.pub.basel.kvant.cloud/api/project_badges/measure?project=quarkus-commons&metric=duplicated_lines_density&token=sqb_b56d9ea175c7f51f522ce63acd7fe7807643ac9e)](https://sonarqube.pub.basel.kvant.cloud/dashboard?id=quarkus-commons)
[![Quality Gate Status](https://sonarqube.pub.basel.kvant.cloud/api/project_badges/measure?project=quarkus-commons&metric=alert_status&token=sqb_b56d9ea175c7f51f522ce63acd7fe7807643ac9e)](https://sonarqube.pub.basel.kvant.cloud/dashboard?id=quarkus-commons)
[![Security Rating](https://sonarqube.pub.basel.kvant.cloud/api/project_badges/measure?project=quarkus-commons&metric=security_rating&token=sqb_b56d9ea175c7f51f522ce63acd7fe7807643ac9e)](https://sonarqube.pub.basel.kvant.cloud/dashboard?id=quarkus-commons)
[![Reliability Rating](https://sonarqube.pub.basel.kvant.cloud/api/project_badges/measure?project=quarkus-commons&metric=reliability_rating&token=sqb_b56d9ea175c7f51f522ce63acd7fe7807643ac9e)](https://sonarqube.pub.basel.kvant.cloud/dashboard?id=quarkus-commons)
[![Maintainability Rating](https://sonarqube.pub.basel.kvant.cloud/api/project_badges/measure?project=quarkus-commons&metric=sqale_rating&token=sqb_b56d9ea175c7f51f522ce63acd7fe7807643ac9e)](https://sonarqube.pub.basel.kvant.cloud/dashboard?id=quarkus-commons)
# Introduction

14
pom.xml
View file

@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>ch.phoenix.oss</groupId>
<artifactId>quarkus-commons</artifactId>
<version>1.0.5</version>
<version>1.0.9-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
@ -21,15 +21,15 @@
<!-- Quarkus properties -->
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.24.3</quarkus.platform.version>
<quarkus.platform.version>3.25.0</quarkus.platform.version>
<!-- Plugin versions -->
<compiler-plugin.version>3.14.0</compiler-plugin.version>
<surefire-plugin.version>3.5.3</surefire-plugin.version>
<spotless-plugin.version>2.45.0</spotless-plugin.version>
<palantir-java-format.version>2.70.0</palantir-java-format.version>
<spotless-plugin.version>2.46.1</spotless-plugin.version>
<palantir-java-format.version>2.72.0</palantir-java-format.version>
<jacoco-plugin.version>0.8.13</jacoco-plugin.version> <!-- Match with version from Quarkus BOM -->
<jandex-plugin.version>3.3.1</jandex-plugin.version>
<jandex-plugin.version>3.4.0</jandex-plugin.version>
<release-plugin.version>3.1.1</release-plugin.version>
<source-plugin.version>3.3.1</source-plugin.version>
@ -73,7 +73,7 @@
<connection>scm:git:ssh://git@git-ssh.kvant.cloud:2222/phoenix-oss/quarkus-commons.git</connection>
<developerConnection>scm:git:ssh://git@git-ssh.kvant.cloud:2222/phoenix-oss/quarkus-commons.git</developerConnection>
<url>https://git.kvant.cloud/phoenix-oss/quarkus-commons.git</url>
<tag>1.0.5</tag>
<tag>HEAD</tag>
</scm>
<dependencies>
@ -196,7 +196,7 @@
<tagNameFormat>@{project.version}</tagNameFormat>
<checkModificationExcludes>mvnw</checkModificationExcludes>
<scmReleaseCommitComment>chore: release @{releaseLabel}</scmReleaseCommitComment>
<scmDevelopmentCommitComment>chore: prepare for next development iteration [skip ci]</scmDevelopmentCommitComment>
<scmDevelopmentCommitComment>chore: prepare for next development iteration</scmDevelopmentCommitComment>
<autoVersionSubmodules>true</autoVersionSubmodules>
</configuration>
</plugin>

View file

@ -5,7 +5,7 @@
<parent>
<groupId>ch.phoenix.oss</groupId>
<artifactId>quarkus-commons</artifactId>
<version>1.0.5</version>
<version>1.0.9-SNAPSHOT</version>
</parent>
<artifactId>quarkus-audit-tools</artifactId>
@ -51,38 +51,38 @@
</dependency>
</dependencies>
<!-- <build>-->
<!-- <plugins>-->
<!-- <plugin>-->
<!-- <groupId>org.jacoco</groupId>-->
<!-- <artifactId>jacoco-maven-plugin</artifactId>-->
<!-- <version>${jacoco-plugin.version}</version>-->
<!-- <executions>-->
<!-- <execution>-->
<!-- <id>jacoco-check</id>-->
<!-- <goals>-->
<!-- <goal>check</goal>-->
<!-- </goals>-->
<!-- <phase>test</phase>-->
<!-- <configuration>-->
<!-- <dataFile>${project.build.directory}/jacoco-quarkus.exec</dataFile>-->
<!-- <rules>-->
<!-- <rule>-->
<!-- <element>BUNDLE</element>-->
<!-- <limits>-->
<!-- <limit>-->
<!-- <counter>INSTRUCTION</counter>-->
<!-- <value>COVEREDRATIO</value>-->
<!-- <minimum>1</minimum>-->
<!-- </limit>-->
<!-- </limits>-->
<!-- </rule>-->
<!-- </rules>-->
<!-- </configuration>-->
<!-- </execution>-->
<!-- </executions>-->
<!-- </plugin>-->
<!-- </plugins>-->
<!-- </build>-->
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco-plugin.version}</version>
<executions>
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<phase>test</phase>
<configuration>
<dataFile>${project.build.directory}/jacoco-quarkus.exec</dataFile>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>INSTRUCTION</counter>
<value>COVEREDRATIO</value>
<minimum>1</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -1,7 +1,6 @@
package ch.phoenix.oss.quarkus.commons.audit;
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mockStatic;
import io.quarkus.test.junit.QuarkusTest;

View file

@ -1,7 +1,6 @@
package ch.phoenix.oss.quarkus.commons.audit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
@ -17,12 +16,36 @@ class RevisionTest {
var r2 = new Revision();
r2.rev = 1;
assertThat(r1).as("Revisions should be equal").isEqualTo(r2);
var r3 = new Revision();
r3.rev = 2;
assertThat(r1)
.as("Revisions equality should should match expected value")
.isEqualTo(r1)
.isEqualTo(r2)
.isNotEqualTo(r3)
.isNotEqualTo(new Object());
}
@Test
void testHashCode() {}
void testHashCode() {
var r1 = new Revision();
r1.rev = 123;
var r2 = new Revision();
r2.rev = 123;
var r3 = new Revision();
r3.rev = 2;
assertThat(r1.hashCode()).isEqualTo(123).isEqualTo(r2.hashCode()).isNotEqualTo(r3.hashCode());
}
@Test
void testToString() {}
void testToString() {
var rev = new Revision();
rev.rev = 1;
assertThat(rev).as("Revision's toString should match expected value").hasToString("Revision{rev=1}");
}
}

View file

@ -5,7 +5,7 @@
<parent>
<groupId>ch.phoenix.oss</groupId>
<artifactId>quarkus-commons</artifactId>
<version>1.0.5</version>
<version>1.0.9-SNAPSHOT</version>
</parent>
<artifactId>quarkus-client-logger</artifactId>
@ -16,6 +16,16 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-config-yaml</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@ -40,7 +50,7 @@
<limit>
<counter>INSTRUCTION</counter>
<value>COVEREDRATIO</value>
<minimum>1</minimum>
<minimum>0.92</minimum>
</limit>
</limits>
</rule>

View file

@ -0,0 +1,10 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import org.eclipse.microprofile.config.spi.Converter;
public class LowerCaseStringConverter implements Converter<String> {
@Override
public String convert(String value) throws IllegalArgumentException, NullPointerException {
return value.toLowerCase();
}
}

View file

@ -6,23 +6,36 @@ import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpClientResponse;
import jakarta.enterprise.context.Dependent;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.inject.Inject;
import java.util.Map;
import java.util.Set;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.client.api.ClientLogger;
/**
* This is based on org.jboss.resteasy.reactive.client.logging.DefaultClientLogger,
* with the only change being that the value of "Authorization" header, when present,
* is redacted.
* with the only change being that headers are redacted based on the Set provided
* by the configuration.
*/
@Dependent
public class RedactingClientLogger implements ClientLogger {
private static final Logger log = Logger.getLogger(RedactingClientLogger.class);
private static final String REDACTED_VALUE = "*****";
private final Set<String> redactedHeaders;
private int bodySize;
@Inject
public RedactingClientLogger(RedactingClientLoggerConfiguration configuration) {
this.redactedHeaders = configuration
.headers()
.redact()
.orElse(RedactingClientLoggerConfiguration.Headers.DEFAULT_REDACTED_HEADERS);
}
@Override
public void setBodySize(int bodySize) {
this.bodySize = bodySize;
@ -97,7 +110,7 @@ public class RedactingClientLogger implements ClientLogger {
}
var key = entry.getKey();
var value = HttpHeaders.AUTHORIZATION.equalsIgnoreCase(key) ? "*****" : entry.getValue();
var value = redactedHeaders.contains(key.toLowerCase()) ? REDACTED_VALUE : entry.getValue();
sb.append(key).append('=').append(value);
}

View file

@ -0,0 +1,20 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithConverter;
import jakarta.ws.rs.core.HttpHeaders;
import java.util.Optional;
import java.util.Set;
@ConfigMapping(prefix = "phoenix.client-logger")
public interface RedactingClientLoggerConfiguration {
Headers headers();
interface Headers {
Set<String> DEFAULT_REDACTED_HEADERS = Set.of(HttpHeaders.AUTHORIZATION.toLowerCase());
Optional<Set<@WithConverter(LowerCaseStringConverter.class) String>> redact();
}
}

View file

@ -0,0 +1,12 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import io.quarkus.test.junit.QuarkusTestProfile;
import java.util.Map;
public class InfoLevelProfile implements QuarkusTestProfile {
@Override
public Map<String, String> getConfigOverrides() {
return Map.of("quarkus.log.category.\"ch.phoenix.oss.quarkus.commons.client.logger\".level", "INFO");
}
}

View file

@ -0,0 +1,44 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;
import jakarta.inject.Inject;
import java.net.URI;
import java.util.Optional;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.Test;
@QuarkusTest
@TestProfile(InfoLevelProfile.class)
class InfoLevelTest {
@Inject
@RestClient
TestClient injectedClient;
TestClient builtClient = QuarkusRestClientBuilder.newBuilder()
.clientLogger(new RedactingClientLogger(() -> Optional::empty))
.baseUri(URI.create("http://localhost:8087"))
.build(TestClient.class);
@Test
void getWithInjectedClient() {
injectedClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted");
}
@Test
void getWithBuiltClientAndEmptyConfig() {
builtClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted");
}
@Test
void postWithInjectedClient() {
injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", "body");
}
@Test
void postWithBuiltClientAndEmptyConfig() {
builtClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted", "");
}
}

View file

@ -0,0 +1,47 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import java.net.URI;
import java.util.Optional;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.Test;
@QuarkusTest
class RedactingClientLoggerTest {
@Inject
@RestClient
TestClient injectedClient;
TestClient builtClient = QuarkusRestClientBuilder.newBuilder()
.clientLogger(new RedactingClientLogger(() -> Optional::empty))
.baseUri(URI.create("http://localhost:8087"))
.build(TestClient.class);
@Test
void getWithInjectedClient() {
injectedClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted");
}
@Test
void getWithBuiltClientAndEmptyConfig() {
builtClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted");
}
@Test
void postWithInjectedClient() {
injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", "body");
}
@Test
void postWithInjectedClientAndNullBody() {
injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", null);
}
@Test
void postWithBuiltClientAndEmptyConfig() {
builtClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted", "");
}
}

View file

@ -0,0 +1,12 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import io.quarkus.test.junit.QuarkusTestProfile;
import java.util.Map;
public class ScopeNoneProfile implements QuarkusTestProfile {
@Override
public Map<String, String> getConfigOverrides() {
return Map.of("quarkus.rest-client.logging.scope", "none");
}
}

View file

@ -0,0 +1,44 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;
import jakarta.inject.Inject;
import java.net.URI;
import java.util.Optional;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.Test;
@QuarkusTest
@TestProfile(ScopeNoneProfile.class)
class ScopeNoneTest {
@Inject
@RestClient
TestClient injectedClient;
TestClient builtClient = QuarkusRestClientBuilder.newBuilder()
.clientLogger(new RedactingClientLogger(() -> Optional::empty))
.baseUri(URI.create("http://localhost:8087"))
.build(TestClient.class);
@Test
void getWithInjectedClient() {
injectedClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted");
}
@Test
void getWithBuiltClientAndEmptyConfig() {
builtClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted");
}
@Test
void postWithInjectedClient() {
injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", "body");
}
@Test
void postWithBuiltClientAndEmptyConfig() {
builtClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted", "");
}
}

View file

@ -0,0 +1,28 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@SuppressWarnings("UastIncorrectHttpHeaderInspection")
@RegisterRestClient(configKey = "test")
public interface TestClient {
@GET
@Path("/")
@Produces(MediaType.TEXT_PLAIN)
String get(
@HeaderParam("Authorization") String authorization,
@HeaderParam("X-Request-ID") String requestId,
@HeaderParam("X-Something-Else") String somethingElse);
@POST
@Path("/")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
String post(
@HeaderParam("Authorization") String authorization,
@HeaderParam("X-Request-ID") String requestId,
@HeaderParam("X-Something-Else") String somethingElse,
String body);
}

View file

@ -0,0 +1,21 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
@Path("/")
public class TestResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String get() {
return "get";
}
@POST
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
public String post(String body) {
return body;
}
}

View file

@ -0,0 +1,20 @@
quarkus:
http:
test-port: 8087
rest-client:
logging:
scope: request-response
body-limit: 10000
test:
url: http://localhost:${quarkus.http.test-port}
log:
category:
"ch.phoenix.oss.quarkus.commons.client.logger":
level: DEBUG
phoenix:
client-logger:
headers:
redact:
- AUTHORIZATION
- X-SOMETHING-ELSE

View file

@ -5,7 +5,7 @@
<parent>
<groupId>ch.phoenix.oss</groupId>
<artifactId>quarkus-commons</artifactId>
<version>1.0.5</version>
<version>1.0.9-SNAPSHOT</version>
</parent>
<artifactId>quarkus-clock-service</artifactId>

View file

@ -5,7 +5,7 @@
<parent>
<groupId>ch.phoenix.oss</groupId>
<artifactId>quarkus-commons</artifactId>
<version>1.0.5</version>
<version>1.0.9-SNAPSHOT</version>
</parent>
<artifactId>quarkus-json-service</artifactId>

View file

@ -5,7 +5,7 @@
<parent>
<groupId>ch.phoenix.oss</groupId>
<artifactId>quarkus-commons</artifactId>
<version>1.0.5</version>
<version>1.0.9-SNAPSHOT</version>
</parent>
<artifactId>quarkus-message-digest-service</artifactId>

View file

@ -5,7 +5,7 @@
<parent>
<groupId>ch.phoenix.oss</groupId>
<artifactId>quarkus-commons</artifactId>
<version>1.0.5</version>
<version>1.0.9-SNAPSHOT</version>
</parent>
<artifactId>quarkus-random-number-generator</artifactId>

View file

@ -5,7 +5,7 @@
<parent>
<groupId>ch.phoenix.oss</groupId>
<artifactId>quarkus-commons</artifactId>
<version>1.0.5</version>
<version>1.0.9-SNAPSHOT</version>
</parent>
<artifactId>quarkus-tracing-service</artifactId>
@ -41,4 +41,38 @@
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco-plugin.version}</version>
<executions>
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<phase>test</phase>
<configuration>
<dataFile>${project.build.directory}/jacoco-quarkus.exec</dataFile>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>INSTRUCTION</counter>
<value>COVEREDRATIO</value>
<minimum>0.95</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -8,7 +8,7 @@ public interface TracingService {
String getActor();
String getRequestPath();
String getRequestPathRaw();
String getRequestMethod();

View file

@ -33,7 +33,7 @@ class TracingServiceImpl implements TracingService {
}
@Override
public String getRequestPath() {
public String getRequestPathRaw() {
return (String) MDC.get(TracingConstants.REQUEST_PATH_RAW);
}

View file

@ -12,7 +12,7 @@ import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;
@QuarkusTest
public class ActorTest {
class ActorTest {
@InjectSpy
TracingService tracingService;

View file

@ -12,7 +12,7 @@ import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;
@QuarkusTest
public class QueryParamTest {
class QueryParamTest {
@InjectSpy
TracingService tracingService;

View file

@ -14,7 +14,7 @@ import org.junit.jupiter.api.Test;
@QuarkusTest
@TestProfile(Test2Profile.class)
public class RawPathTest {
class RawPathTest {
@InjectSpy
TracingService tracingService;

View file

@ -14,7 +14,7 @@ import org.junit.jupiter.api.Test;
@QuarkusTest
@TestProfile(Test2Profile.class)
public class RedactedTest {
class RedactedTest {
@InjectSpy
TracingService tracingService;

View file

@ -1,5 +1,6 @@
package ch.phoenix.oss.quarkus.commons.tracing;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.verify;
@ -10,7 +11,11 @@ import io.quarkus.test.junit.mockito.InjectSpy;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import java.util.Map;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@QuarkusTest
class RoutePatternTest {
@ -18,12 +23,46 @@ class RoutePatternTest {
@InjectSpy
TracingService tracingService;
@Test
void getBlankResource() {
var route = "/";
RestAssured.given().accept(ContentType.TEXT).when().get(route).then().statusCode(200);
static Stream<Arguments> get() {
return Stream.of(
arguments("/", Map.of()),
arguments("/leading-and-no-trailing", Map.of()),
arguments("/leading/{param}/{param2}", Map.of("param", "1", "param2", "2")),
arguments("/{param}/{param2}/trailing", Map.of("param", "1", "param2", "2")),
arguments("/leading-and-no-trailing/{param}", Map.of("param", "1")),
arguments("/leading-and-no-trailing/{param}/{param2}", Map.of("param", "1", "param2", "2")),
arguments("/leading-and-trailing", Map.of()),
arguments("/leading-and-trailing/{param}", Map.of("param", "1")),
arguments("/leading-and-trailing/{param}/{param2}", Map.of("param", "1", "param2", "2")),
arguments("/no-leading-and-no-trailing", Map.of()),
arguments("/no-leading-and-no-trailing/{param}", Map.of("param", "1")),
arguments("/no-leading-and-no-trailing/{param}/{param2}", Map.of("param", "1", "param2", "2")),
arguments("/no-leading-and-trailing", Map.of()),
arguments("/no-leading-and-trailing/{param}", Map.of("param", "1")),
arguments("/no-leading-and-trailing/{param}/{param2}", Map.of("param", "1", "param2", "2")));
}
verifyGetTracing(route, Map.of());
@MethodSource
@ParameterizedTest
void get(String route, Map<String, String> pathParams) {
RestAssured.given()
.accept(ContentType.TEXT)
.when()
.get(route, pathParams)
.then()
.statusCode(200);
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);
}
@Test
@ -45,160 +84,4 @@ class RoutePatternTest {
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<String, String> 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);
}
}

View file

@ -0,0 +1,98 @@
package ch.phoenix.oss.quarkus.commons.tracing;
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.instrumentation.annotations.WithSpan;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.jboss.logging.MDC;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@QuarkusTest
class TracingServiceImplTest {
@Inject
TracingService tracingService;
@Inject
Span span;
@BeforeEach
void setUp() {
MDC.clear();
}
@Test
void getActor() {
tracingService.trace("actor", "abc");
assertThat(tracingService.getActor())
.as("Actor should match expected value")
.isEqualTo("abc");
}
@Test
void getRequestPathRaw() {
tracingService.trace("request.path.raw", "/foo/bar");
assertThat(tracingService.getRequestPathRaw())
.as("Request Path Raw should match expected value")
.isEqualTo("/foo/bar");
}
@Test
void getRequestMethod() {
tracingService.trace("request.method", "GET");
assertThat(tracingService.getRequestMethod())
.as("Request Method should match expected value")
.isEqualTo("GET");
}
@Test
void getRequestId() {
tracingService.trace("request.headers.x-request-id", "ba458367-bfeb-46ba-87da-50b9343be8f9");
assertThat(tracingService.getRequestId())
.as("Request Id should match expected value")
.isEqualTo("ba458367-bfeb-46ba-87da-50b9343be8f9");
}
@Test
@WithSpan
void getTraceId() {
assertThat(tracingService.getTraceId())
.as("Request Trace Id should match expected value")
.isEqualTo(span.getSpanContext().getTraceId());
}
@Test
@WithSpan
void getSpanId() {
assertThat(tracingService.getSpanId())
.as("Request Span Id should match expected value")
.isEqualTo(span.getSpanContext().getSpanId());
}
@Test
void getClientIp() {
tracingService.trace("request.client.ip", "127.0.0.1");
assertThat(tracingService.getClientIp())
.as("Request Client Iü should match expected value")
.isEqualTo("127.0.0.1");
}
@Test
void getSchedulerJob() {
tracingService.trace("scheduler.job.name", "scheduler/abc");
assertThat(tracingService.getSchedulerJob())
.as("Scheduler Job Name should match expected value")
.isEqualTo("scheduler/abc");
}
@Test
void clearAll() {
tracingService.trace("aaa", "bbb");
assertThat(MDC.get("aaa")).isEqualTo("bbb");
tracingService.clearAll();
assertThat(MDC.get("aaa")).isNull();
}
}

View file

@ -10,14 +10,14 @@ import jakarta.ws.rs.core.MediaType;
public class SlashResource {
@GET
@Path("/leading/{id}/{anotherId}")
public String doubleLeading(int id, int anotherId) {
return "leading/" + id + "/" + anotherId;
@Path("/leading/{param}/{param2}")
public String doubleLeading(int param, int param2) {
return "leading/" + param + "/" + param2;
}
@GET
@Path("{id}/{anotherId}/trailing/")
public String doubleTrailing(int id, int anotherId) {
return id + "/" + anotherId + "/trailing";
@Path("{param}/{param2}/trailing/")
public String doubleTrailing(int param, int param2) {
return param + "/" + param2 + "/trailing";
}
}

View file

@ -5,7 +5,7 @@
<parent>
<groupId>ch.phoenix.oss</groupId>
<artifactId>quarkus-commons</artifactId>
<version>1.0.5</version>
<version>1.0.9-SNAPSHOT</version>
</parent>
<artifactId>quarkus-uuid-generator</artifactId>