diff --git a/pom.xml b/pom.xml
index c27cc12ea..8bdf6c842 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.uid2
uid2-operator
- 5.39.11-alpha-130-SNAPSHOT
+ 5.39.9
UTF-8
diff --git a/src/main/java/com/uid2/operator/Const.java b/src/main/java/com/uid2/operator/Const.java
index a30e3d3ba..4d32b9034 100644
--- a/src/main/java/com/uid2/operator/Const.java
+++ b/src/main/java/com/uid2/operator/Const.java
@@ -28,5 +28,6 @@ public class Config extends com.uid2.shared.Const.Config {
public static final String OptOutStatusApiEnabled = "optout_status_api_enabled";
public static final String OptOutStatusMaxRequestSize = "optout_status_max_request_size";
public static final String MaxInvalidPaths = "logging_limit_max_invalid_paths_per_interval";
+ public static final String MaxVersionBucketsPerSite = "logging_limit_max_version_buckets_per_site";
}
}
diff --git a/src/main/java/com/uid2/operator/Main.java b/src/main/java/com/uid2/operator/Main.java
index dcbd5e729..dad32611d 100644
--- a/src/main/java/com/uid2/operator/Main.java
+++ b/src/main/java/com/uid2/operator/Main.java
@@ -365,7 +365,7 @@ private Future createAndDeployCloudSyncStoreVerticle(String name, ICloud
private Future createAndDeployStatsCollector() {
Promise promise = Promise.promise();
- StatsCollectorVerticle statsCollectorVerticle = new StatsCollectorVerticle(60000, config.getInteger(Const.Config.MaxInvalidPaths, 50));
+ StatsCollectorVerticle statsCollectorVerticle = new StatsCollectorVerticle(60000, config.getInteger(Const.Config.MaxInvalidPaths, 50), config.getInteger(Const.Config.MaxVersionBucketsPerSite, 50));
vertx.deployVerticle(statsCollectorVerticle, promise);
_statsCollectorQueue = statsCollectorVerticle;
return promise.future();
diff --git a/src/main/java/com/uid2/operator/model/StatsCollectorMessageItem.java b/src/main/java/com/uid2/operator/model/StatsCollectorMessageItem.java
index 409709911..bcf06a523 100644
--- a/src/main/java/com/uid2/operator/model/StatsCollectorMessageItem.java
+++ b/src/main/java/com/uid2/operator/model/StatsCollectorMessageItem.java
@@ -5,15 +5,18 @@ public class StatsCollectorMessageItem {
private String referer;
private String apiContact;
private Integer siteId;
+ private String clientVersion;
//USED by json serial
- public StatsCollectorMessageItem(){}
+ public StatsCollectorMessageItem() {
+ }
- public StatsCollectorMessageItem(String path, String referer, String apiContact, Integer siteId){
+ public StatsCollectorMessageItem(String path, String referer, String apiContact, Integer siteId, String clientVersion) {
this.path = path;
this.referer = referer;
this.apiContact = apiContact;
this.siteId = siteId;
+ this.clientVersion = clientVersion;
}
@@ -48,4 +51,12 @@ public Integer getSiteId() {
public void setSiteId(Integer siteId) {
this.siteId = siteId;
}
+
+ public String getClientVersion() {
+ return clientVersion;
+ }
+
+ public void setClientVersion(String clientVersion) {
+ this.clientVersion = clientVersion;
+ }
}
diff --git a/src/main/java/com/uid2/operator/monitoring/ClientVersionStatRecorder.java b/src/main/java/com/uid2/operator/monitoring/ClientVersionStatRecorder.java
new file mode 100644
index 000000000..5abeb41d2
--- /dev/null
+++ b/src/main/java/com/uid2/operator/monitoring/ClientVersionStatRecorder.java
@@ -0,0 +1,52 @@
+package com.uid2.operator.monitoring;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+
+public class ClientVersionStatRecorder {
+ private static final String NOT_RECORDED = "";
+ private final int siteClientBucketLimit;
+ private final Map> siteIdToVersionCounts = new HashMap<>();
+
+ public ClientVersionStatRecorder(int maxVersionBucketsPerSite) {
+ this.siteClientBucketLimit = maxVersionBucketsPerSite;
+ }
+
+ public Stream getStatsView() {
+ return siteIdToVersionCounts.entrySet().stream().map(entry -> new SiteClientVersionStat(entry.getKey(), entry.getValue()));
+ }
+
+ private void removeLowVersionCounts(int siteId) {
+ var versionCounts = siteIdToVersionCounts.get(siteId);
+ if (versionCounts == null) {
+ return;
+ }
+
+ // Remove 3 items to avoid a couple of new version values from continuously evicting each other
+ var lowestEntries = versionCounts.entrySet().stream()
+ .sorted(Map.Entry.comparingByValue())
+ .filter(entry -> !entry.getKey().equals(NOT_RECORDED))
+ .limit(3)
+ .toList();
+ for (var entry : lowestEntries) {
+ var notRecordedCount = versionCounts.getOrDefault(NOT_RECORDED, 0);
+ versionCounts.put(NOT_RECORDED, notRecordedCount + entry.getValue());
+ versionCounts.remove(entry.getKey());
+ }
+ }
+
+ public void add(Integer siteId, String clientVersion) {
+ if (siteId == null || clientVersion == null || clientVersion.isBlank()) {
+ return;
+ }
+
+ var clientVersionCounts = siteIdToVersionCounts.computeIfAbsent(siteId, k -> new HashMap<>());
+
+ var count = clientVersionCounts.getOrDefault(clientVersion, 0);
+ if (count == 0 && clientVersionCounts.size() >= siteClientBucketLimit) {
+ removeLowVersionCounts(siteId);
+ }
+ clientVersionCounts.put(clientVersion, count + 1);
+ }
+}
diff --git a/src/main/java/com/uid2/operator/monitoring/ILoggedStat.java b/src/main/java/com/uid2/operator/monitoring/ILoggedStat.java
new file mode 100644
index 000000000..2ea9f777f
--- /dev/null
+++ b/src/main/java/com/uid2/operator/monitoring/ILoggedStat.java
@@ -0,0 +1,6 @@
+package com.uid2.operator.monitoring;
+
+public interface ILoggedStat {
+ public String GetLogPrefix();
+ public Object GetValueToLog();
+}
diff --git a/src/main/java/com/uid2/operator/monitoring/SiteClientVersionStat.java b/src/main/java/com/uid2/operator/monitoring/SiteClientVersionStat.java
new file mode 100644
index 000000000..b9e953e49
--- /dev/null
+++ b/src/main/java/com/uid2/operator/monitoring/SiteClientVersionStat.java
@@ -0,0 +1,24 @@
+package com.uid2.operator.monitoring;
+
+import java.util.Map;
+import java.util.Objects;
+
+public final class SiteClientVersionStat implements ILoggedStat {
+ private final Integer siteId;
+ private final Map versionCounts;
+
+ public SiteClientVersionStat(Integer siteId, Map versionCounts) {
+ this.siteId = siteId;
+ this.versionCounts = versionCounts;
+ }
+
+ @Override
+ public String GetLogPrefix() {
+ return "version log; siteId=%d versions=".formatted(siteId);
+ }
+
+ @Override
+ public Object GetValueToLog() {
+ return versionCounts;
+ }
+}
diff --git a/src/main/java/com/uid2/operator/monitoring/StatsCollectorHandler.java b/src/main/java/com/uid2/operator/monitoring/StatsCollectorHandler.java
index c0b45dce3..04a36d9c1 100644
--- a/src/main/java/com/uid2/operator/monitoring/StatsCollectorHandler.java
+++ b/src/main/java/com/uid2/operator/monitoring/StatsCollectorHandler.java
@@ -1,6 +1,7 @@
package com.uid2.operator.monitoring;
import com.uid2.operator.model.StatsCollectorMessageItem;
+import com.uid2.shared.Const;
import com.uid2.shared.auth.ClientKey;
import com.uid2.shared.middleware.AuthMiddleware;
import io.vertx.core.Handler;
@@ -33,10 +34,19 @@ private void addStatsMessageToQueue(RoutingContext routingContext) {
final ClientKey clientKey = (ClientKey) AuthMiddleware.getAuthClient(routingContext);
final String apiContact = clientKey == null ? null : clientKey.getContact();
final Integer siteId = clientKey == null ? null : clientKey.getSiteId();
+ final String clientVersion = getClientVersion(routingContext);
- final StatsCollectorMessageItem messageItem = new StatsCollectorMessageItem(path, referer, apiContact, siteId);
+ final StatsCollectorMessageItem messageItem = new StatsCollectorMessageItem(path, referer, apiContact, siteId, clientVersion);
_statCollectorQueue.enqueue(vertx, messageItem);
}
+ private String getClientVersion(RoutingContext routingContext) {
+ String clientVersion = routingContext.request().headers().get(Const.Http.ClientVersionHeader);
+ if (clientVersion == null) {
+ clientVersion = !routingContext.queryParam("client").isEmpty() ? routingContext.queryParam("client").get(0) : null;
+ }
+ return clientVersion;
+ }
+
}
diff --git a/src/main/java/com/uid2/operator/monitoring/StatsCollectorVerticle.java b/src/main/java/com/uid2/operator/monitoring/StatsCollectorVerticle.java
index c764010b0..8e49ec1f8 100644
--- a/src/main/java/com/uid2/operator/monitoring/StatsCollectorVerticle.java
+++ b/src/main/java/com/uid2/operator/monitoring/StatsCollectorVerticle.java
@@ -19,11 +19,14 @@
import java.time.Instant;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Stream;
public class StatsCollectorVerticle extends AbstractVerticle implements IStatsCollectorQueue {
private static final Logger LOGGER = LoggerFactory.getLogger(StatsCollectorVerticle.class);
private HashMap pathMap;
+ private final ClientVersionStatRecorder clientVersionStat;
+
private static final int MAX_AVAILABLE = 1000;
private final int maxInvalidPaths;
@@ -41,7 +44,7 @@ public class StatsCollectorVerticle extends AbstractVerticle implements IStatsCo
private final ObjectMapper mapper;
private final Counter queueFullCounter;
- public StatsCollectorVerticle(long jsonIntervalMS, int maxInvalidPaths) {
+ public StatsCollectorVerticle(long jsonIntervalMS, int maxInvalidPaths, int maxVersionBucketsPerSite) {
pathMap = new HashMap<>();
_statsCollectorCount = new AtomicInteger();
@@ -65,6 +68,7 @@ public StatsCollectorVerticle(long jsonIntervalMS, int maxInvalidPaths) {
.register(Metrics.globalRegistry);
mapper = new ObjectMapper();
+ clientVersionStat = new ClientVersionStatRecorder(maxVersionBucketsPerSite);
}
@Override
@@ -121,6 +125,7 @@ public void handleMessage(Message message) {
pathMap.merge(path, endpointStat, this::mergeEndpoint);
}
+ clientVersionStat.add(siteId, messageItem.getClientVersion());
_statsCollectorCount.decrementAndGet();
@@ -133,7 +138,7 @@ public void handleMessage(Message message) {
if(pathMap.size() == this.maxInvalidPaths + validPaths.size()) {
LOGGER.error("max invalid paths reached; a large number of invalid paths have been requested from authenticated participants");
}
- Object[] stats = pathMap.values().toArray();
+ var stats = buildStatsList();
this.jsonSerializerExecutor.executeBlocking(
promise -> promise.complete(this.serializeToLogs(stats)),
res -> {
@@ -148,12 +153,12 @@ public void handleMessage(Message message) {
}
}
- private Void serializeToLogs(Object[] stats) {
+ private Void serializeToLogs(List stats) {
LOGGER.debug("Starting JSON Serialize");
- ObjectMapper mapper = new ObjectMapper();
- for (Object stat : stats) {
+ ObjectMapper statMapper = new ObjectMapper();
+ for (var stat : stats) {
try {
- String jsonString = mapper.writeValueAsString(stat);
+ String jsonString = "%s%s".formatted(stat.GetLogPrefix(), statMapper.writeValueAsString(stat.GetValueToLog()));
LOGGER.info(jsonString);
} catch (JsonProcessingException e) {
LOGGER.error(e.getMessage(), e);
@@ -167,19 +172,11 @@ private EndpointStat mergeEndpoint(EndpointStat a, EndpointStat b) {
return a;
}
-
- public String getEndpointStats() {
- Object[] stats = pathMap.values().toArray();
- StringBuilder completeStats = new StringBuilder();
- for (Object stat : stats) {
- try {
- String jsonString = mapper.writeValueAsString(stat);
- completeStats.append(jsonString).append("\n");
- } catch (JsonProcessingException e) {
- LOGGER.error(e.getMessage(), e);
- }
- }
- return completeStats.toString();
+ private List buildStatsList() {
+ Stream pathMapStream = pathMap.values().stream();
+ Stream clientVersionStream = clientVersionStat.getStatsView();
+ var stats = Stream.concat(pathMapStream, clientVersionStream);
+ return stats.toList();
}
@Override
@@ -225,7 +222,7 @@ public void merge(DomainStat d) {
}
}
- class EndpointStat {
+ class EndpointStat implements ILoggedStat {
private final String endpoint;
private final Integer siteId;
private final String apiVersion;
@@ -276,6 +273,16 @@ else if(domainList.size() < MaxDomains) {
domainMissedCounter.increment();
}
}
+
+ @Override
+ public String GetLogPrefix() {
+ return "";
+ }
+
+ @Override
+ public Object GetValueToLog() {
+ return this;
+ }
}
}
diff --git a/src/main/java/com/uid2/operator/vertx/ClientVersionCapturingHandler.java b/src/main/java/com/uid2/operator/vertx/ClientVersionCapturingHandler.java
deleted file mode 100644
index 2af4fb6b2..000000000
--- a/src/main/java/com/uid2/operator/vertx/ClientVersionCapturingHandler.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.uid2.operator.vertx;
-
-import com.uid2.shared.Const;
-import io.micrometer.core.instrument.Counter;
-import io.micrometer.core.instrument.Metrics;
-import io.vertx.core.Handler;
-import io.vertx.ext.web.RoutingContext;
-
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.Map;
-
-public class ClientVersionCapturingHandler implements Handler {
- private final Map _clientVersionCounters = new HashMap<>();
-
- public ClientVersionCapturingHandler(String dir, String whitelistGlob) throws IOException {
- try (DirectoryStream dirStream = Files.newDirectoryStream(Paths.get(dir), whitelistGlob)) {
- dirStream.forEach(path -> {
- final String version = getFileNameWithoutExtension(path);
- final Counter counter = Counter
- .builder("uid2.client_sdk_versions")
- .description("counter for how many http requests are processed per each client sdk version")
- .tags("client_version", version)
- .register(Metrics.globalRegistry);
- _clientVersionCounters.put(version, counter);
- });
- }
- }
- @Override
- public void handle(RoutingContext context) {
- String clientVersion = context.request().headers().get(Const.Http.ClientVersionHeader);
- if (clientVersion == null) {
- clientVersion = !context.queryParam("client").isEmpty() ? context.queryParam("client").get(0) : null;
- }
- if (clientVersion != null) {
- final Counter counter = _clientVersionCounters.get(clientVersion);
- if (counter != null) {
- counter.increment();
- }
- }
- context.next();
- }
-
- private static String getFileNameWithoutExtension(Path path) {
- final String fileName = path.getFileName().toString();
- return fileName.indexOf(".") > 0 ? fileName.substring(0, fileName.lastIndexOf(".")) : fileName;
- }
-}
diff --git a/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java b/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java
index 245aae445..210b08571 100644
--- a/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java
+++ b/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java
@@ -218,7 +218,6 @@ private Router createRoutesSetup() throws IOException {
router.allowForward(AllowForwardHeaders.X_FORWARD);
router.route().handler(new RequestCapturingHandler());
- router.route().handler(new ClientVersionCapturingHandler("static/js", "*.js"));
router.route().handler(CorsHandler.create()
.addRelativeOrigin(".*.")
.allowedMethod(io.vertx.core.http.HttpMethod.GET)
diff --git a/src/test/java/com/uid2/operator/StatsCollectorVerticleTest.java b/src/test/java/com/uid2/operator/StatsCollectorVerticleTest.java
index ac594c44c..d0f089da4 100644
--- a/src/test/java/com/uid2/operator/StatsCollectorVerticleTest.java
+++ b/src/test/java/com/uid2/operator/StatsCollectorVerticleTest.java
@@ -11,100 +11,125 @@
import io.vertx.core.Vertx;
import io.vertx.junit5.VertxExtension;
import io.vertx.junit5.VertxTestContext;
-import org.junit.jupiter.api.Assertions;
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.LoggerFactory;
+import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
@ExtendWith(VertxExtension.class)
public class StatsCollectorVerticleTest {
private static final int MAX_INVALID_PATHS = 5;
- private StatsCollectorVerticle verticle;
+ private static final int MAX_CLIENT_VERSION_BUCKETS = 8;
+ private static final int JSON_INTERVAL = 200;
+ private static final int LOG_WAIT_INTERVAL = 50;
+ private static final String CLIENT_VERSION = "uid2-sdk-3.0.0";
+ private final ObjectMapper mapper = new ObjectMapper();
+ private ListAppender logWatcher;
+ private Vertx vertx;
@BeforeEach
void deployVerticle(Vertx vertx, VertxTestContext testContext) {
- verticle = new StatsCollectorVerticle(1000, MAX_INVALID_PATHS);
+ this.vertx = vertx;
+ var verticle = new StatsCollectorVerticle(JSON_INTERVAL, MAX_INVALID_PATHS, MAX_CLIENT_VERSION_BUCKETS);
vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow()));
+
+ logWatcher = new ListAppender<>();
+ logWatcher.start();
+ ((Logger) LoggerFactory.getLogger(StatsCollectorVerticle.class)).addAppender(logWatcher);
}
+ @AfterEach
+ void cleanupLogger() {
+ logWatcher.stop();
+ ((Logger) LoggerFactory.getLogger(StatsCollectorVerticle.class)).detachAppender(logWatcher);
+ }
@Test
void verticleDeployed(Vertx vertx, VertxTestContext testContext) {
testContext.completeNow();
}
- @Test
- void testJSONSerializeWithV0AndV1Paths(Vertx vertx, VertxTestContext testContext) throws InterruptedException, JsonProcessingException {
- ObjectMapper mapper = new ObjectMapper();
- StatsCollectorMessageItem messageItem = new StatsCollectorMessageItem("/test", "https://test.com", "test", 1);
+ private List getMessages() {
+ return logWatcher.list.stream().map(ILoggingEvent::getFormattedMessage).collect(Collectors.toList());
+ }
+ private void sendStatMessage(StatsCollectorMessageItem messageItem) throws JsonProcessingException {
vertx.eventBus().send(Const.Config.StatsCollectorEventBus, mapper.writeValueAsString(messageItem));
- vertx.eventBus().send(Const.Config.StatsCollectorEventBus, mapper.writeValueAsString(messageItem));
- vertx.eventBus().send(Const.Config.StatsCollectorEventBus, mapper.writeValueAsString(messageItem));
-
- messageItem = new StatsCollectorMessageItem("/v1/test", "https://test.com", "test", 1);
+ }
- vertx.eventBus().send(Const.Config.StatsCollectorEventBus, mapper.writeValueAsString(messageItem));
- vertx.eventBus().send(Const.Config.StatsCollectorEventBus, mapper.writeValueAsString(messageItem));
+ private void triggerSerializeAndWait(VertxTestContext testContext) throws JsonProcessingException, InterruptedException {
+ StatsCollectorMessageItem triggerSerialize = new StatsCollectorMessageItem("/triggerSerialize", "https://test.com", "test", null, null);
+ sendStatMessage(triggerSerialize);
+ testContext.awaitCompletion(LOG_WAIT_INTERVAL, TimeUnit.MILLISECONDS);
+ }
- testContext.awaitCompletion(2000, TimeUnit.MILLISECONDS);
+ @Test
+ void testJSONSerializeWithV0AndV1Paths(Vertx vertx, VertxTestContext testContext) throws InterruptedException, JsonProcessingException {
+ StatsCollectorMessageItem messageItem = new StatsCollectorMessageItem("/test", "https://test.com", "test", 1, CLIENT_VERSION);
+ sendStatMessage(messageItem);
+ sendStatMessage(messageItem);
+ sendStatMessage(messageItem);
- String expected =
- "{\"endpoint\":\"test\",\"siteId\":1,\"apiVersion\":\"v1\",\"domainList\":[{\"domain\":\"test.com\",\"count\":2,\"apiContact\":\"test\"}]}\n"
- + "{\"endpoint\":\"test\",\"siteId\":1,\"apiVersion\":\"v0\",\"domainList\":[{\"domain\":\"test.com\",\"count\":3,\"apiContact\":\"test\"}]}\n";
+ messageItem = new StatsCollectorMessageItem("/v1/test", "https://test.com", "test", 1, CLIENT_VERSION);
+ sendStatMessage(messageItem);
+ sendStatMessage(messageItem);
+ waitForLogInterval(testContext);
- String results = verticle.getEndpointStats();
+ triggerSerializeAndWait(testContext);
- Assertions.assertEquals(results, expected);
+ var expectedList = List.of("{\"endpoint\":\"test\",\"siteId\":1,\"apiVersion\":\"v1\",\"domainList\":[{\"domain\":\"test.com\",\"count\":2,\"apiContact\":\"test\"}]}",
+ "{\"endpoint\":\"test\",\"siteId\":1,\"apiVersion\":\"v0\",\"domainList\":[{\"domain\":\"test.com\",\"count\":3,\"apiContact\":\"test\"}]}");
+ var messages = getMessages();
+ assertThat(messages).containsAll(expectedList);
testContext.completeNow();
}
+ private static void waitForLogInterval(VertxTestContext testContext) throws InterruptedException {
+ testContext.awaitCompletion(JSON_INTERVAL*2, TimeUnit.MILLISECONDS);
+ }
+
@Test
void testJSONSerializeWithV2AndUnknownPaths(Vertx vertx, VertxTestContext testContext) throws InterruptedException, JsonProcessingException {
- ObjectMapper mapper = new ObjectMapper();
- StatsCollectorMessageItem messageItem = new StatsCollectorMessageItem("/v2/test", "https://test.com", "test", 1);
-
- vertx.eventBus().send(Const.Config.StatsCollectorEventBus, mapper.writeValueAsString(messageItem));
- vertx.eventBus().send(Const.Config.StatsCollectorEventBus, mapper.writeValueAsString(messageItem));
- vertx.eventBus().send(Const.Config.StatsCollectorEventBus, mapper.writeValueAsString(messageItem));
-
- messageItem = new StatsCollectorMessageItem("/v2", "https://test.com", "test", 1);
-
- vertx.eventBus().send(Const.Config.StatsCollectorEventBus, mapper.writeValueAsString(messageItem));
- vertx.eventBus().send(Const.Config.StatsCollectorEventBus, mapper.writeValueAsString(messageItem));
-
- testContext.awaitCompletion(2000, TimeUnit.MILLISECONDS);
-
- String expected =
- "{\"endpoint\":\"test\",\"siteId\":1,\"apiVersion\":\"v2\",\"domainList\":[{\"domain\":\"test.com\",\"count\":3,\"apiContact\":\"test\"}]}\n"
- + "{\"endpoint\":\"v2\",\"siteId\":1,\"apiVersion\":\"unknown\",\"domainList\":[{\"domain\":\"test.com\",\"count\":2,\"apiContact\":\"test\"}]}\n";
-
- String results = verticle.getEndpointStats();
-
- Assertions.assertEquals(results, expected);
+ StatsCollectorMessageItem messageItem = new StatsCollectorMessageItem("/v2/test", "https://test.com", "test", 1, CLIENT_VERSION);
+ sendStatMessage(messageItem);
+ sendStatMessage(messageItem);
+ sendStatMessage(messageItem);
+
+ messageItem = new StatsCollectorMessageItem("/v2", "https://test.com", "test", 1, CLIENT_VERSION);
+ sendStatMessage(messageItem);
+ sendStatMessage(messageItem);
+ waitForLogInterval(testContext);
+ triggerSerializeAndWait(testContext);
+
+ var expectedList = List.of("{\"endpoint\":\"test\",\"siteId\":1,\"apiVersion\":\"v2\",\"domainList\":[{\"domain\":\"test.com\",\"count\":3,\"apiContact\":\"test\"}]}",
+ "{\"endpoint\":\"v2\",\"siteId\":1,\"apiVersion\":\"unknown\",\"domainList\":[{\"domain\":\"test.com\",\"count\":2,\"apiContact\":\"test\"}]}");
+ var messages = getMessages();
+ assertThat(messages).containsAll(expectedList);
testContext.completeNow();
}
@Test
void allValidPathsAllowed(Vertx vertx, VertxTestContext testContext) throws InterruptedException, JsonProcessingException {
- ObjectMapper mapper = new ObjectMapper();
Set validEndpoints = Endpoints.pathSet();
-
for(String endpoint : validEndpoints) {
- StatsCollectorMessageItem messageItem = new StatsCollectorMessageItem(endpoint, "https://test.com", "test", 1);
- vertx.eventBus().send(Const.Config.StatsCollectorEventBus, mapper.writeValueAsString(messageItem));
+ StatsCollectorMessageItem messageItem = new StatsCollectorMessageItem(endpoint, "https://test.com", "test", 1, CLIENT_VERSION);
+ sendStatMessage(messageItem);
}
- testContext.awaitCompletion(2000, TimeUnit.MILLISECONDS);
-
- String results = verticle.getEndpointStats();
+ waitForLogInterval(testContext);
+ triggerSerializeAndWait(testContext);
+ var messages = getMessages();
for(String endpoint: validEndpoints) {
String withoutVersion = endpoint;
if (endpoint.startsWith("/v1/") || endpoint.startsWith("/v2/")) {
@@ -114,7 +139,7 @@ void allValidPathsAllowed(Vertx vertx, VertxTestContext testContext) throws Inte
}
String expected = "{\"endpoint\":\"" + withoutVersion + "\",\"siteId\":1,";
- Assertions.assertTrue(results.contains(expected));
+ assertThat(messages).anyMatch(m -> m.contains(expected));
}
testContext.completeNow();
@@ -122,37 +147,59 @@ void allValidPathsAllowed(Vertx vertx, VertxTestContext testContext) throws Inte
@Test
void invalidPathsLimit(Vertx vertx, VertxTestContext testContext) throws InterruptedException, JsonProcessingException {
- ObjectMapper mapper = new ObjectMapper();
-
for(int i = 0; i < MAX_INVALID_PATHS + Endpoints.pathSet().size() + 5; i++) {
- StatsCollectorMessageItem messageItem = new StatsCollectorMessageItem("/bad" + i, "https://test.com", "test", 1);
- vertx.eventBus().send(Const.Config.StatsCollectorEventBus, mapper.writeValueAsString(messageItem));
+ StatsCollectorMessageItem messageItem = new StatsCollectorMessageItem("/bad" + i, "https://test.com", "test", 1, CLIENT_VERSION);
+ sendStatMessage(messageItem);
}
- testContext.awaitCompletion(2000, TimeUnit.MILLISECONDS);
- String results = verticle.getEndpointStats();
+ waitForLogInterval(testContext);
+ triggerSerializeAndWait(testContext);
+ var messages = getMessages();
// MAX_INVALID_PATHS is not the hard limit. The maximum paths that can be recorded, including valid ones, is MAX_INVALID_PATHS + validPaths.size * 2
for(int i = 0; i < MAX_INVALID_PATHS + Endpoints.pathSet().size(); i++) {
String expected = "{\"endpoint\":\"bad" + i + "\",\"siteId\":1,\"apiVersion\":\"v0\",\"domainList\":[{\"domain\":\"test.com\",\"count\":1,\"apiContact\":\"test\"}]}";
- Assertions.assertTrue(results.contains(expected));
+ assertThat(messages).contains(expected);
}
for(int i = MAX_INVALID_PATHS + Endpoints.pathSet().size(); i < MAX_INVALID_PATHS + 5; i++) {
String expected = "{\"endpoint\":\"bad" + i + "\",\"siteId\":1,\"apiVersion\":\"v0\",\"domainList\":[{\"domain\":\"test.com\",\"count\":1,\"apiContact\":\"test\"}]}";
- Assertions.assertFalse(results.contains(expected));
+ assertThat(messages).contains(expected);
}
+ assertThat(getMessages()).contains("max invalid paths reached; a large number of invalid paths have been requested from authenticated participants");
- ListAppender logWatcher = new ListAppender<>();
- logWatcher.start();
- ((Logger) LoggerFactory.getLogger(StatsCollectorVerticle.class)).addAppender(logWatcher);
+ testContext.completeNow();
+ }
- StatsCollectorMessageItem messageItem = new StatsCollectorMessageItem("/triggerSerialize", "https://test.com", "test", 1);
- vertx.eventBus().send(Const.Config.StatsCollectorEventBus, mapper.writeValueAsString(messageItem));
+ @Test
+ void clientVersionStats(Vertx vertx, VertxTestContext testContext) throws InterruptedException, JsonProcessingException {
+ for(int i = 0; i < 3; i++) {
+ StatsCollectorMessageItem messageItem = new StatsCollectorMessageItem("/test" + i, "https://test.com", "test", 1, CLIENT_VERSION + i);
+ sendStatMessage(messageItem);
+ }
+ for(int i = 0; i < 12; i++) {
+ StatsCollectorMessageItem messageItem = new StatsCollectorMessageItem("/test" + i, "https://test.com", "test", 2, CLIENT_VERSION + i);
+ for (int count = 0; count <= i; count++) {
+ sendStatMessage(messageItem);
+ }
+ }
+ sendStatMessage(new StatsCollectorMessageItem("/test", "https://test.com", "test", 2, CLIENT_VERSION + "single"));
+ sendStatMessage(new StatsCollectorMessageItem("/test", "https://test.com", "test", 2, null));
+
+ waitForLogInterval(testContext);
+ triggerSerializeAndWait(testContext);
- testContext.awaitCompletion(1000, TimeUnit.MILLISECONDS);
- Assertions.assertTrue(logWatcher.list.get(0).getFormattedMessage().contains("max invalid paths reached; a large number of invalid paths have been requested from authenticated participants"));
+ waitForLogInterval(testContext);
+ sendStatMessage(new StatsCollectorMessageItem("/test", "https://test.com", "test", 1, CLIENT_VERSION + 1));
+ triggerSerializeAndWait(testContext);
+
+ var expectedLogs = List.of(
+ "version log; siteId=1 versions={\"uid2-sdk-3.0.01\":1,\"uid2-sdk-3.0.02\":1,\"uid2-sdk-3.0.00\":1}",
+ "version log; siteId=1 versions={\"uid2-sdk-3.0.01\":2,\"uid2-sdk-3.0.02\":1,\"uid2-sdk-3.0.00\":1}",
+ "version log; siteId=2 versions={\"\":21,\"uid2-sdk-3.0.06\":7,\"uid2-sdk-3.0.07\":8,\"uid2-sdk-3.0.011\":12,\"uid2-sdk-3.0.08\":9,\"uid2-sdk-3.0.010\":11,\"uid2-sdk-3.0.09\":10,\"uid2-sdk-3.0.0single\":1}"
+ );
+ var messages = getMessages();
+ assertThat(messages).containsAll(expectedLogs);
testContext.completeNow();
}
-
}
diff --git a/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java b/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java
index 6b74a36bb..eafa14f9a 100644
--- a/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java
+++ b/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java
@@ -55,6 +55,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.slf4j.LoggerFactory;
+import static org.assertj.core.api.Assertions.*;
import javax.crypto.SecretKey;
import java.math.BigInteger;
@@ -3304,7 +3305,7 @@ void cstgDomainNameCheckFailsAndLogInvalidHttpOrigin(String httpOrigin, Vertx ve
assertFalse(respJson.containsKey("body"));
assertEquals("unexpected http origin", respJson.getString("message"));
assertEquals("invalid_http_origin", respJson.getString("status"));
- Assertions.assertTrue(logWatcher.list.get(0).getFormattedMessage().contains("InvalidHttpOriginAndAppName: site test (123): http://gototest.com"));
+ assertThat(logWatcher.list.stream().map(ILoggingEvent::getFormattedMessage).collect(Collectors.toList())).contains("InvalidHttpOriginAndAppName: site test (123): http://gototest.com");
assertTokenStatusMetrics(
clientSideTokenGenerateSiteId,
TokenResponseStatsCollector.Endpoint.ClientSideTokenGenerateV2,
@@ -5090,74 +5091,4 @@ void secureLinkValidationFailsReturnsIdentityError(Vertx vertx, VertxTestContext
testContext.completeNow();
});
}
-
- @ParameterizedTest // note that this test will be removed when we switch to logging versions
- @ValueSource(strings = {"euid-sdk-1.0.0", "openid-sdk-1.0", "uid2-esp-0.0.1a", "uid2-sdk-0.0.1a",
- "uid2-sdk-0.0.1b", "uid2-sdk-1.0.0", "uid2-sdk-2.0.0"})
- void clientVersionHeader(String clientVersion, Vertx vertx, VertxTestContext testContext) {
- WebClient client = WebClient.create(vertx);
- HttpRequest req = client.getAbs(getUrlForEndpoint("/any/endpoint"));
- req.putHeader("X-UID2-Client-Version", clientVersion);
- req.send(ar -> {
- assertEquals(404, ar.result().statusCode());
- final double actual = Metrics.globalRegistry
- .get("uid2.client_sdk_versions")
- .tag("client_version", clientVersion)
- .counter().count();
- assertEquals(1, actual);
- testContext.completeNow();
- });
- }
-
- @ParameterizedTest // note that this test will be removed when we switch to logging versions
- @ValueSource(strings = {"euid-sdk-1.0.0", "openid-sdk-1.0", "uid2-esp-0.0.1a", "uid2-sdk-0.0.1a",
- "uid2-sdk-0.0.1b", "uid2-sdk-1.0.0", "uid2-sdk-2.0.0"})
- void clientVersionQueryParameter(String clientVersion, Vertx vertx, VertxTestContext testContext) {
- WebClient client = WebClient.create(vertx);
- HttpRequest req = client.getAbs(getUrlForEndpoint("/any/endpoint?client=" + clientVersion));
- req.send(ar -> {
- assertEquals(404, ar.result().statusCode());
- final double actual = Metrics.globalRegistry
- .get("uid2.client_sdk_versions")
- .tag("client_version", clientVersion)
- .counter().count();
- assertEquals(1, actual);
- testContext.completeNow();
- });
- }
-
- @Test // note that this test will be removed when we switch to logging versions
- void clientVersionHeaderNotFound(Vertx vertx, VertxTestContext testContext) {
- WebClient client = WebClient.create(vertx);
- HttpRequest req = client.getAbs(getUrlForEndpoint("/any/endpoint"));
- String clientVersion = "invalid-sdk";
- req.putHeader("X-UID2-Client-Version", clientVersion);
- req.send(ar -> {
- assertEquals(404, ar.result().statusCode());
- assertThrows(MeterNotFoundException.class, () -> {
- Metrics.globalRegistry
- .get("uid2.client_sdk_versions")
- .tag("client_version", clientVersion)
- .counter();
- });
- testContext.completeNow();
- });
- }
-
- @Test // note that this test will be removed when we switch to logging versions
- void clientVersionQueryParameterNotFound(Vertx vertx, VertxTestContext testContext) {
- WebClient client = WebClient.create(vertx);
- String clientVersion = "invalid-sdk";
- HttpRequest req = client.getAbs(getUrlForEndpoint("/any/endpoint?client=" + clientVersion));
- req.send(ar -> {
- assertEquals(404, ar.result().statusCode());
- assertThrows(MeterNotFoundException.class, () -> {
- Metrics.globalRegistry
- .get("uid2.client_sdk_versions")
- .tag("client_version", clientVersion)
- .counter();
- });
- testContext.completeNow();
- });
- }
}