Skip to content

Commit b57e1ba

Browse files
committed
improve logging, log status and guide user to doc
1 parent b0634b0 commit b57e1ba

File tree

4 files changed

+177
-43
lines changed

4 files changed

+177
-43
lines changed

src/main/java/com/uid2/shared/Const.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ public static class Config {
9191

9292
// Others
9393
public static final String SaltsExpiredShutdownHours = "salts_expired_shutdown_hours";
94+
public static final String KeysetKeyShutdownHours = "keyset_key_shutdown_hours";
9495
public static final String encryptionSupportVersion = "encryption_support_version";
9596
}
9697

src/main/java/com/uid2/shared/attest/UidCoreClient.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,12 @@ private InputStream internalDownload(String path) throws CloudStorageException {
8989
inputStream = getWithAttest(path);
9090
}
9191
return inputStream;
92+
} catch (CloudStorageException e) {
93+
throw e;
9294
} catch (Exception e) {
93-
throw new CloudStorageException("download error: " + e.getMessage(), e);
95+
throw new CloudStorageException(
96+
"Cannot download required files from UID2 core service, exception: " + e.getClass().getSimpleName() +
97+
", please visit UID2 guides for troubleshooting", e);
9498
}
9599
}
96100

@@ -108,7 +112,12 @@ private InputStream getWithAttest(String path) throws IOException, AttestationRe
108112
HttpResponse<String> httpResponse;
109113
httpResponse = sendHttpRequest(path, attestationToken);
110114
if (httpResponse.statusCode() != 200) {
111-
throw new CloudStorageException(String.format("Non-success response from core on request to %s. Status code: %d", path, httpResponse.statusCode()));
115+
// Don't log full path as it may contain sensitive information
116+
URI uri = URI.create(path);
117+
String safeEndpoint = uri.getHost() + uri.getPath();
118+
throw new CloudStorageException(String.format(
119+
"Cannot download required files from UID2 core service, HTTP response code %d, endpoint: %s, please visit UID2 guides for troubleshooting",
120+
httpResponse.statusCode(), safeEndpoint));
112121
}
113122
return Utils.convertHttpResponseToInputStream(httpResponse);
114123
}

src/main/java/com/uid2/shared/vertx/RotatingStoreVerticle.java

Lines changed: 54 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import java.util.concurrent.atomic.AtomicInteger;
1717
import java.util.concurrent.atomic.AtomicLong;
18+
import java.util.function.Consumer;
1819

1920
public class RotatingStoreVerticle extends AbstractVerticle {
2021
private static final Logger LOGGER = LoggerFactory.getLogger(RotatingStoreVerticle.class);
@@ -31,47 +32,54 @@ public class RotatingStoreVerticle extends AbstractVerticle {
3132
private final AtomicLong latestVersion = new AtomicLong(-1L);
3233
private final AtomicLong latestEntryCount = new AtomicLong(-1L);
3334
private final AtomicInteger storeRefreshIsFailing = new AtomicInteger(0);
35+
private final Consumer<Boolean> refreshCallback;
3436

3537
private final long refreshIntervalMs;
3638

3739
public RotatingStoreVerticle(String storeName, long refreshIntervalMs, IMetadataVersionedStore versionedStore) {
40+
this(storeName, refreshIntervalMs, versionedStore, null);
41+
}
42+
43+
public RotatingStoreVerticle(String storeName, long refreshIntervalMs, IMetadataVersionedStore versionedStore,
44+
Consumer<Boolean> refreshCallback) {
3845
this.healthComponent = HealthManager.instance.registerComponent(storeName + "-rotator");
3946
this.healthComponent.setHealthStatus(false, "not started");
4047

4148
this.storeName = storeName;
4249
this.counterStoreRefreshed = Counter
43-
.builder("uid2_config_store_refreshed_total")
44-
.tag("store", storeName)
45-
.description("counter for how many times " + storeName + " store is refreshed")
46-
.register(Metrics.globalRegistry);
50+
.builder("uid2_config_store_refreshed_total")
51+
.tag("store", storeName)
52+
.description("counter for how many times " + storeName + " store is refreshed")
53+
.register(Metrics.globalRegistry);
4754
this.counterStoreRefreshTimeMs = Counter
48-
.builder("uid2_config_store_refreshtime_ms_total")
49-
.tag("store", storeName)
50-
.description("counter for total time (ms) " + storeName + " store spend in refreshing")
51-
.register(Metrics.globalRegistry);
55+
.builder("uid2_config_store_refreshtime_ms_total")
56+
.tag("store", storeName)
57+
.description("counter for total time (ms) " + storeName + " store spend in refreshing")
58+
.register(Metrics.globalRegistry);
5259
this.counterStoreRefreshFailures = Counter
53-
.builder("uid2_config_store_refresh_failures_total")
54-
.tag("store", storeName)
55-
.description("counter for number of " + storeName + " store refresh failures")
56-
.register(Metrics.globalRegistry);
60+
.builder("uid2_config_store_refresh_failures_total")
61+
.tag("store", storeName)
62+
.description("counter for number of " + storeName + " store refresh failures")
63+
.register(Metrics.globalRegistry);
5764
this.gaugeStoreVersion = Gauge
58-
.builder("uid2_config_store_version", () -> this.latestVersion.get())
59-
.tag("store", storeName)
60-
.description("gauge for " + storeName + " store version")
61-
.register(Metrics.globalRegistry);
65+
.builder("uid2_config_store_version", () -> this.latestVersion.get())
66+
.tag("store", storeName)
67+
.description("gauge for " + storeName + " store version")
68+
.register(Metrics.globalRegistry);
6269
this.gaugeStoreEntryCount = Gauge
63-
.builder("uid2_config_store_entry_count", () -> this.latestEntryCount.get())
64-
.tag("store", storeName)
65-
.description("gauge for " + storeName + " store total entry count")
66-
.register(Metrics.globalRegistry);
70+
.builder("uid2_config_store_entry_count", () -> this.latestEntryCount.get())
71+
.tag("store", storeName)
72+
.description("gauge for " + storeName + " store total entry count")
73+
.register(Metrics.globalRegistry);
6774
this.gaugeConsecutiveRefreshFailures = Gauge
68-
.builder("uid2_config_store_consecutive_refresh_failures", () -> this.storeRefreshIsFailing.get())
69-
.tag("store", storeName)
70-
.description("gauge for number of consecutive " + storeName + " store refresh failures")
71-
.register(Metrics.globalRegistry);
75+
.builder("uid2_config_store_consecutive_refresh_failures", () -> this.storeRefreshIsFailing.get())
76+
.tag("store", storeName)
77+
.description("gauge for number of consecutive " + storeName + " store refresh failures")
78+
.register(Metrics.globalRegistry);
7279
this.versionedStore = versionedStore;
7380
this.refreshIntervalMs = refreshIntervalMs;
7481
this.storeRefreshTimer = Metrics.timer("uid2_store_refresh_duration", "store_name", storeName);
82+
this.refreshCallback = refreshCallback;
7583
}
7684

7785
@Override
@@ -98,7 +106,8 @@ private void startRefresh(Promise<Void> promise) {
98106
this.startBackgroundRefresh();
99107
} else {
100108
this.healthComponent.setHealthStatus(false, ar.cause().getMessage());
101-
LOGGER.error("Failed " + this.storeName + " loading. Trying again in " + refreshIntervalMs + "ms", ar.cause());
109+
LOGGER.error("Failed " + this.storeName + " loading. Trying again in " + refreshIntervalMs + "ms",
110+
ar.cause());
102111
vertx.setTimer(refreshIntervalMs, id -> this.startRefresh(promise));
103112
}
104113
});
@@ -109,23 +118,28 @@ private void startBackgroundRefresh() {
109118
final long start = System.nanoTime();
110119

111120
vertx.executeBlocking(() -> {
112-
this.refresh();
113-
return null;
114-
}).onComplete(asyncResult -> {
115-
final long end = System.nanoTime();
116-
final long elapsed = ((end - start) / 1000000);
117-
this.counterStoreRefreshTimeMs.increment(elapsed);
118-
if (asyncResult.failed()) {
119-
this.counterStoreRefreshFailures.increment();
120-
this.storeRefreshIsFailing.set(1);
121-
LOGGER.error("Failed to load " + this.storeName + ", " + elapsed + " ms", asyncResult.cause());
122-
} else {
123-
this.counterStoreRefreshed.increment();
124-
this.storeRefreshIsFailing.set(0);
125-
LOGGER.trace("Successfully refreshed " + this.storeName + ", " + elapsed + " ms");
121+
this.refresh();
122+
return null;
123+
}).onComplete(asyncResult -> {
124+
final long end = System.nanoTime();
125+
final long elapsed = ((end - start) / 1000000);
126+
this.counterStoreRefreshTimeMs.increment(elapsed);
127+
if (asyncResult.failed()) {
128+
this.counterStoreRefreshFailures.increment();
129+
this.storeRefreshIsFailing.set(1);
130+
LOGGER.error("Failed to load " + this.storeName + ", " + elapsed + " ms", asyncResult.cause());
131+
if (this.refreshCallback != null) {
132+
this.refreshCallback.accept(false);
133+
}
134+
} else {
135+
this.counterStoreRefreshed.increment();
136+
this.storeRefreshIsFailing.set(0);
137+
LOGGER.trace("Successfully refreshed " + this.storeName + ", " + elapsed + " ms");
138+
if (this.refreshCallback != null) {
139+
this.refreshCallback.accept(true);
126140
}
127141
}
128-
);
142+
});
129143
});
130144
}
131145

src/test/java/com/uid2/shared/attest/UidCoreClientTest.java

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public void Download_AttestInternalFail_ExceptionThrown() throws IOException, At
7272
CloudStorageException result = assertThrows(CloudStorageException.class, () -> {
7373
uidCoreClient.download("https://download");
7474
});
75-
String expectedExceptionMessage = "download error: AttestationResponseCode: AttestationFailure, test failure";
75+
String expectedExceptionMessage = "Cannot download required files from UID2 core service, exception: AttestationResponseHandlerException, please visit UID2 guides for troubleshooting";
7676
assertEquals(expectedExceptionMessage, result.getMessage());
7777
}
7878

@@ -100,4 +100,114 @@ void getJwtReturnsCoreToken() {
100100
when(mockAttestationResponseHandler.getCoreJWT()).thenReturn("coreJWT");
101101
Assertions.assertEquals("coreJWT", this.uidCoreClient.getJWT());
102102
}
103+
104+
@Test
105+
public void Download_Http403Error_LogsStatusCodeAndEndpoint() throws IOException, AttestationResponseHandlerException {
106+
HttpResponse<String> mockHttpResponse = mock(HttpResponse.class);
107+
when(mockHttpResponse.statusCode()).thenReturn(403);
108+
when(mockHttpClient.get(eq("https://core-prod.uidapi.com/sites/refresh"), any(HashMap.class))).thenReturn(mockHttpResponse);
109+
110+
CloudStorageException result = assertThrows(CloudStorageException.class, () -> {
111+
uidCoreClient.download("https://core-prod.uidapi.com/sites/refresh");
112+
});
113+
114+
assertAll(
115+
() -> assertTrue(result.getMessage().contains("HTTP response code 403"),
116+
"Should contain HTTP status code 403"),
117+
() -> assertTrue(result.getMessage().contains("Cannot download required files from UID2 core service"),
118+
"Should have customer-friendly message"),
119+
() -> assertTrue(result.getMessage().contains("core-prod.uidapi.com/sites/refresh"),
120+
"Should contain safe endpoint"),
121+
() -> assertTrue(result.getMessage().contains("please visit UID2 guides for troubleshooting"),
122+
"Should reference documentation")
123+
);
124+
}
125+
126+
@Test
127+
public void Download_Http404Error_LogsStatusCode() throws IOException, AttestationResponseHandlerException {
128+
HttpResponse<String> mockHttpResponse = mock(HttpResponse.class);
129+
when(mockHttpResponse.statusCode()).thenReturn(404);
130+
when(mockHttpClient.get(eq("https://core-prod.uidapi.com/keys/refresh"), any(HashMap.class))).thenReturn(mockHttpResponse);
131+
132+
CloudStorageException result = assertThrows(CloudStorageException.class, () -> {
133+
uidCoreClient.download("https://core-prod.uidapi.com/keys/refresh");
134+
});
135+
136+
assertAll(
137+
() -> assertTrue(result.getMessage().contains("HTTP response code 404"),
138+
"Should contain HTTP status code 404"),
139+
() -> assertTrue(result.getMessage().contains("core-prod.uidapi.com/keys/refresh"),
140+
"Should contain endpoint")
141+
);
142+
}
143+
144+
@Test
145+
public void Download_Http500Error_LogsStatusCode() throws IOException, AttestationResponseHandlerException {
146+
HttpResponse<String> mockHttpResponse = mock(HttpResponse.class);
147+
when(mockHttpResponse.statusCode()).thenReturn(500);
148+
when(mockHttpClient.get(eq("https://core-prod.uidapi.com/salts/refresh"), any(HashMap.class))).thenReturn(mockHttpResponse);
149+
150+
CloudStorageException result = assertThrows(CloudStorageException.class, () -> {
151+
uidCoreClient.download("https://core-prod.uidapi.com/salts/refresh");
152+
});
153+
154+
assertAll(
155+
() -> assertTrue(result.getMessage().contains("HTTP response code 500"),
156+
"Should contain HTTP status code 500"),
157+
() -> assertTrue(result.getMessage().contains("UID2 core service"),
158+
"Should identify source as UID2 core")
159+
);
160+
}
161+
162+
@Test
163+
public void Download_Http503Error_LogsStatusCode() throws IOException, AttestationResponseHandlerException {
164+
HttpResponse<String> mockHttpResponse = mock(HttpResponse.class);
165+
when(mockHttpResponse.statusCode()).thenReturn(503);
166+
when(mockHttpClient.get(eq("https://core-integ.uidapi.com/clients/refresh"), any(HashMap.class))).thenReturn(mockHttpResponse);
167+
168+
CloudStorageException result = assertThrows(CloudStorageException.class, () -> {
169+
uidCoreClient.download("https://core-integ.uidapi.com/clients/refresh");
170+
});
171+
172+
assertTrue(result.getMessage().contains("HTTP response code 503"),
173+
"Should contain HTTP status code 503");
174+
}
175+
176+
@Test
177+
public void Download_NetworkError_LogsExceptionType() throws IOException, AttestationResponseHandlerException {
178+
IOException networkException = new IOException("Connection timeout");
179+
when(mockHttpClient.get(anyString(), any(HashMap.class))).thenThrow(networkException);
180+
181+
CloudStorageException result = assertThrows(CloudStorageException.class, () -> {
182+
uidCoreClient.download("https://core-prod.uidapi.com/sites/refresh");
183+
});
184+
185+
assertAll(
186+
() -> assertTrue(result.getMessage().contains("Cannot download required files from UID2 core service"),
187+
"Should have customer-friendly message"),
188+
() -> assertTrue(result.getMessage().contains("exception: IOException"),
189+
"Should log exception type"),
190+
() -> assertTrue(result.getMessage().contains("please visit UID2 guides"),
191+
"Should reference documentation")
192+
);
193+
}
194+
195+
@Test
196+
public void Download_EndpointWithQueryParams_LogsOnlyHostAndPath() throws IOException, AttestationResponseHandlerException {
197+
HttpResponse<String> mockHttpResponse = mock(HttpResponse.class);
198+
when(mockHttpResponse.statusCode()).thenReturn(403);
199+
// URL with query params (simulating potential sensitive data)
200+
when(mockHttpClient.get(eq("https://core-prod.uidapi.com/sites/refresh?token=secret123"), any(HashMap.class))).thenReturn(mockHttpResponse);
201+
202+
CloudStorageException result = assertThrows(CloudStorageException.class, () -> {
203+
uidCoreClient.download("https://core-prod.uidapi.com/sites/refresh?token=secret123");
204+
});
205+
206+
assertAll(
207+
() -> assertTrue(result.getMessage().contains("core-prod.uidapi.com/sites/refresh"),
208+
"Should contain host and path"),
209+
() -> assertFalse(result.getMessage().contains("token=secret123"),
210+
"Should NOT contain query parameters with tokens")
211+
);
212+
}
103213
}

0 commit comments

Comments
 (0)