From 92a12744d9432963c3120c66ee5131ea52a55ba9 Mon Sep 17 00:00:00 2001 From: novalisdenahi Date: Tue, 14 Apr 2026 11:55:25 +0200 Subject: [PATCH 1/5] Enhance logging to include CF-RAY ID in error messages for fetch failures. --- .../com/configcat/ConfigCatLogMessages.java | 29 +++++++++++++++---- .../java/com/configcat/ConfigFetcher.java | 19 ++++++------ .../java/com/configcat/ConfigFetcherTest.java | 23 +++++++++++++++ 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/configcat/ConfigCatLogMessages.java b/src/main/java/com/configcat/ConfigCatLogMessages.java index 1836296..0ea70df 100644 --- a/src/main/java/com/configcat/ConfigCatLogMessages.java +++ b/src/main/java/com/configcat/ConfigCatLogMessages.java @@ -20,15 +20,17 @@ final class ConfigCatLogMessages { * Log message for Config Service Cache Read error. The log eventId is 2200. */ public static final String CONFIG_SERVICE_CACHE_READ_ERROR = "Error occurred while reading the cache."; - /** - * Log message for Fetch Failed Due To Unexpected error. The log eventId is 1103. - */ - public static final String FETCH_FAILED_DUE_TO_UNEXPECTED_ERROR = "Unexpected error occurred while trying to fetch config JSON. It is most likely due to a local network issue. Please make sure your application can reach the ConfigCat CDN servers (or your proxy server) over HTTP."; /** * Log message for Fetch Failed Due To Invalid Sdk Key error. The log eventId is 1100. */ private static final String FETCH_FAILED_DUE_TO_INVALID_SDK_KEY_ERROR = "Your SDK Key seems to be wrong. You can find the valid SDK Key at https://app.configcat.com/sdkkey"; + + /** + * Log message for Fetch Failed Due To Unexpected error. The log eventId is 1103. + */ + private static final String FETCH_FAILED_DUE_TO_UNEXPECTED_ERROR = "Unexpected error occurred while trying to fetch config JSON. It is most likely due to a local network issue. Please make sure your application can reach the ConfigCat CDN servers (or your proxy server) over HTTP."; + /** * Log message for Fetch Failed Due To Redirect Loop error. The log eventId is 1104. */ @@ -167,12 +169,29 @@ public static FormattableLogMessage getFetchFailedDueToUnexpectedHttpResponse(fi * @param connectTimeoutMillis Connect timeout in milliseconds. * @param readTimeoutMillis Read timeout in milliseconds. * @param writeTimeoutMillis Write timeout in milliseconds. + * @param cfRayId The http response CF-RAY header value. * @return The formattable log message. */ - public static FormattableLogMessage getFetchFailedDueToRequestTimeout(final Integer connectTimeoutMillis, final Integer readTimeoutMillis, final Integer writeTimeoutMillis) { + public static FormattableLogMessage getFetchFailedDueToRequestTimeout(final Integer connectTimeoutMillis, final Integer readTimeoutMillis, final Integer writeTimeoutMillis, final String cfRayId) { + if (cfRayId != null) { + return new FormattableLogMessage("Request timed out while trying to fetch config JSON. Timeout values: [connect: %dms, read: %dms, write: %dms] %s", connectTimeoutMillis, readTimeoutMillis, writeTimeoutMillis, ConfigCatLogMessages.getCFRayIdPostFix(cfRayId)); + } return new FormattableLogMessage("Request timed out while trying to fetch config JSON. Timeout values: [connect: %dms, read: %dms, write: %dms]", connectTimeoutMillis, readTimeoutMillis, writeTimeoutMillis); } + /** + * Log message for Fetch Failed Due To Unexpected error. The log eventId is 1103. + * + * @param cfRayId The http response CF-RAY header value. + * @return The formattable log message. + */ + public static FormattableLogMessage getFetchFailedDueToUnexpectedError(final String cfRayId) { + if (cfRayId != null) { + return new FormattableLogMessage(FETCH_FAILED_DUE_TO_UNEXPECTED_ERROR + " %s", ConfigCatLogMessages.getCFRayIdPostFix(cfRayId)); + } + return new FormattableLogMessage(FETCH_FAILED_DUE_TO_UNEXPECTED_ERROR); + } + /** * Log message for Fetch Failed Due To Redirect Loop error. The log eventId is 1104. * diff --git a/src/main/java/com/configcat/ConfigFetcher.java b/src/main/java/com/configcat/ConfigFetcher.java index f35e571..9b97605 100644 --- a/src/main/java/com/configcat/ConfigFetcher.java +++ b/src/main/java/com/configcat/ConfigFetcher.java @@ -82,7 +82,7 @@ private CompletableFuture executeFetchAsync(int executionCount, S } } catch (Exception exception) { - this.logger.error(1103, ConfigCatLogMessages.FETCH_FAILED_DUE_TO_UNEXPECTED_ERROR, exception); + this.logger.error(1103, ConfigCatLogMessages.getFetchFailedDueToUnexpectedError(fetchResponse.cfRayId()), exception); return CompletableFuture.completedFuture(fetchResponse); } @@ -98,11 +98,11 @@ private CompletableFuture getResponseAsync(final String eTag) { @Override public void onFailure(@NotNull Call call, @NotNull IOException e) { int logEventId = 1103; - Object message = ConfigCatLogMessages.FETCH_FAILED_DUE_TO_UNEXPECTED_ERROR; + Object message = ConfigCatLogMessages.getFetchFailedDueToUnexpectedError(null); if (!isClosed.get()) { if (e instanceof SocketTimeoutException) { logEventId = 1102; - message = ConfigCatLogMessages.getFetchFailedDueToRequestTimeout(httpClient.connectTimeoutMillis(), httpClient.readTimeoutMillis(), httpClient.writeTimeoutMillis()); + message = ConfigCatLogMessages.getFetchFailedDueToRequestTimeout(httpClient.connectTimeoutMillis(), httpClient.readTimeoutMillis(), httpClient.writeTimeoutMillis(), null); } logger.error(logEventId, message, e); } @@ -111,8 +111,9 @@ public void onFailure(@NotNull Call call, @NotNull IOException e) { @Override public void onResponse(@NotNull Call call, @NotNull Response response) { + String cfRayId = null; try (ResponseBody body = response.body()) { - String cfRayId = response.header("CF-RAY"); + cfRayId = response.header("CF-RAY"); if (response.code() == 200) { String content = body != null ? body.string() : null; String eTag = response.header("ETag"); @@ -140,13 +141,13 @@ public void onResponse(@NotNull Call call, @NotNull Response response) { future.complete(FetchResponse.failed(formattableLogMessage, false, cfRayId)); } } catch (SocketTimeoutException e) { - FormattableLogMessage formattableLogMessage = ConfigCatLogMessages.getFetchFailedDueToRequestTimeout(httpClient.connectTimeoutMillis(), httpClient.readTimeoutMillis(), httpClient.writeTimeoutMillis()); + FormattableLogMessage formattableLogMessage = ConfigCatLogMessages.getFetchFailedDueToRequestTimeout(httpClient.connectTimeoutMillis(), httpClient.readTimeoutMillis(), httpClient.writeTimeoutMillis(), cfRayId); logger.error(1102, formattableLogMessage, e); - future.complete(FetchResponse.failed(formattableLogMessage, false, null)); + future.complete(FetchResponse.failed(formattableLogMessage, false, cfRayId)); } catch (Exception e) { - String message = ConfigCatLogMessages.FETCH_FAILED_DUE_TO_UNEXPECTED_ERROR; - logger.error(1103, message, e); - future.complete(FetchResponse.failed(message, false, null)); + FormattableLogMessage formattableLogMessage = ConfigCatLogMessages.getFetchFailedDueToUnexpectedError(cfRayId); + logger.error(1103, formattableLogMessage, e); + future.complete(FetchResponse.failed(formattableLogMessage, false, cfRayId)); } } }); diff --git a/src/test/java/com/configcat/ConfigFetcherTest.java b/src/test/java/com/configcat/ConfigFetcherTest.java index e825945..9e64863 100644 --- a/src/test/java/com/configcat/ConfigFetcherTest.java +++ b/src/test/java/com/configcat/ConfigFetcherTest.java @@ -85,6 +85,29 @@ public void fetchException() throws IOException, ExecutionException, Interrupted fetch.close(); } + @Test + public void fetchExceptionContainsCFRayIdIfPresented() throws IOException, ExecutionException, InterruptedException { + + ConfigFetcher fetch = new ConfigFetcher(new OkHttpClient.Builder() + .readTimeout(1, TimeUnit.SECONDS) + .build(), + logger, + "", + this.server.url("/").toString(), + false, + PollingModes.manualPoll().getPollingIdentifier()); + + this.server.enqueue(new MockResponse().setBody("test").setHeader("CF-RAY", "12345").setBodyDelay(2, TimeUnit.SECONDS)); + FetchResponse response = fetch.fetchAsync(null).get(); + assertTrue(response.isFailed()); + assertTrue(response.entry().isEmpty()); + assertTrue(response.entry().getConfig().isEmpty()); + + assertEquals("12345", response.cfRayId()); + + fetch.close(); + } + @Test public void fetchedETagNotUpdatesCache() throws Exception { this.server.enqueue(new MockResponse().setResponseCode(200).setBody(TEST_JSON).setHeader("ETag", "fakeETag")); From fec89afed98fc5574cf6eb51445d6e51165aec10 Mon Sep 17 00:00:00 2001 From: novalisdenahi Date: Tue, 14 Apr 2026 11:55:49 +0200 Subject: [PATCH 2/5] Bump version to 9.4.5 in Constants.java and gradle.properties --- gradle.properties | 2 +- src/main/java/com/configcat/Constants.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 0ee9a23..60ad51f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -version=9.4.4 +version=9.4.5 SONATYPE_CONNECT_TIMEOUT_SECONDS=120 \ No newline at end of file diff --git a/src/main/java/com/configcat/Constants.java b/src/main/java/com/configcat/Constants.java index 29e42c0..5879cfb 100644 --- a/src/main/java/com/configcat/Constants.java +++ b/src/main/java/com/configcat/Constants.java @@ -7,7 +7,7 @@ private Constants() { /* prevent from instantiation*/ } static final long DISTANT_PAST = 0; static final String CONFIG_JSON_NAME = "config_v6.json"; static final String SERIALIZATION_FORMAT_VERSION = "v2"; - static final String VERSION = "9.4.4"; + static final String VERSION = "9.4.5"; static final String SDK_KEY_PROXY_PREFIX = "configcat-proxy/"; static final String SDK_KEY_PREFIX = "configcat-sdk-1"; From 0531cd32cc391c95f98fccdc2c0190c80b953d56 Mon Sep 17 00:00:00 2001 From: novalisdenahi Date: Tue, 14 Apr 2026 13:46:05 +0200 Subject: [PATCH 3/5] Add tests to verify CF-RAY ID inclusion in timeout and unexpected error responses --- .../java/com/configcat/ConfigFetcherTest.java | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/configcat/ConfigFetcherTest.java b/src/test/java/com/configcat/ConfigFetcherTest.java index 9e64863..ed71596 100644 --- a/src/test/java/com/configcat/ConfigFetcherTest.java +++ b/src/test/java/com/configcat/ConfigFetcherTest.java @@ -5,6 +5,7 @@ import okhttp3.OkHttpClient; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.SocketPolicy; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -86,7 +87,7 @@ public void fetchException() throws IOException, ExecutionException, Interrupted } @Test - public void fetchExceptionContainsCFRayIdIfPresented() throws IOException, ExecutionException, InterruptedException { + public void fetchTimeOutExceptionContainsCFRayIdIfPresented() throws IOException, ExecutionException, InterruptedException { ConfigFetcher fetch = new ConfigFetcher(new OkHttpClient.Builder() .readTimeout(1, TimeUnit.SECONDS) @@ -103,6 +104,41 @@ public void fetchExceptionContainsCFRayIdIfPresented() throws IOException, Execu assertTrue(response.entry().isEmpty()); assertTrue(response.entry().getConfig().isEmpty()); + assertTrue(response.error().toString().contains("Request timed out while trying to fetch config JSON.")); + assertTrue(response.error().toString().contains("(Ray ID: 12345)")); + assertEquals("12345", response.cfRayId()); + + fetch.close(); + } + + @Test + public void fetchUnexpectedErrorExceptionContainsCFRayIdIfPresented() throws IOException, ExecutionException, InterruptedException { + + ConfigFetcher fetch = new ConfigFetcher( + new OkHttpClient + .Builder() + .readTimeout(1, TimeUnit.SECONDS) + .build(), + logger, + "", + this.server.url("/").toString(), + false, + PollingModes.manualPoll().getPollingIdentifier()); + + this.server.enqueue( + new MockResponse() + .setResponseCode(200) + .setHeader("CF-RAY", "12345") + .setBody("test") + .setSocketPolicy(SocketPolicy.DISCONNECT_DURING_RESPONSE_BODY)); + + FetchResponse response = fetch.fetchAsync(null).get(); + assertTrue(response.isFailed()); + assertTrue(response.entry().isEmpty()); + assertTrue(response.entry().getConfig().isEmpty()); + + assertTrue(response.error().toString().contains("Unexpected error occurred while trying to fetch config JSON.")); + assertTrue(response.error().toString().contains("(Ray ID: 12345)")); assertEquals("12345", response.cfRayId()); fetch.close(); From fd5035839ee4a0f72dc1b2b2cdc2fd9102998a11 Mon Sep 17 00:00:00 2001 From: novalisdenahi Date: Tue, 14 Apr 2026 16:25:16 +0200 Subject: [PATCH 4/5] Fixes based on review --- src/main/java/com/configcat/ConfigCatLogMessages.java | 2 +- src/main/java/com/configcat/ConfigFetcher.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/configcat/ConfigCatLogMessages.java b/src/main/java/com/configcat/ConfigCatLogMessages.java index 0ea70df..b6f9cdd 100644 --- a/src/main/java/com/configcat/ConfigCatLogMessages.java +++ b/src/main/java/com/configcat/ConfigCatLogMessages.java @@ -174,7 +174,7 @@ public static FormattableLogMessage getFetchFailedDueToUnexpectedHttpResponse(fi */ public static FormattableLogMessage getFetchFailedDueToRequestTimeout(final Integer connectTimeoutMillis, final Integer readTimeoutMillis, final Integer writeTimeoutMillis, final String cfRayId) { if (cfRayId != null) { - return new FormattableLogMessage("Request timed out while trying to fetch config JSON. Timeout values: [connect: %dms, read: %dms, write: %dms] %s", connectTimeoutMillis, readTimeoutMillis, writeTimeoutMillis, ConfigCatLogMessages.getCFRayIdPostFix(cfRayId)); + return new FormattableLogMessage("Request timed out while trying to fetch config JSON. Timeout values: [connect: %dms, read: %dms, write: %dms] %s", connectTimeoutMillis, readTimeoutMillis, writeTimeoutMillis, ConfigCatLogMessages.getCFRayIdPostFix(cfRayId)); } return new FormattableLogMessage("Request timed out while trying to fetch config JSON. Timeout values: [connect: %dms, read: %dms, write: %dms]", connectTimeoutMillis, readTimeoutMillis, writeTimeoutMillis); } diff --git a/src/main/java/com/configcat/ConfigFetcher.java b/src/main/java/com/configcat/ConfigFetcher.java index 9b97605..8afef11 100644 --- a/src/main/java/com/configcat/ConfigFetcher.java +++ b/src/main/java/com/configcat/ConfigFetcher.java @@ -111,9 +111,8 @@ public void onFailure(@NotNull Call call, @NotNull IOException e) { @Override public void onResponse(@NotNull Call call, @NotNull Response response) { - String cfRayId = null; + String cfRayId = response.header("CF-RAY"); try (ResponseBody body = response.body()) { - cfRayId = response.header("CF-RAY"); if (response.code() == 200) { String content = body != null ? body.string() : null; String eTag = response.header("ETag"); From 18689a328eda55440624a35fd386906a5d20a322 Mon Sep 17 00:00:00 2001 From: novalisdenahi Date: Thu, 16 Apr 2026 11:58:45 +0200 Subject: [PATCH 5/5] Retrieve CF-RAY ID from response header in onResponse method --- src/main/java/com/configcat/ConfigFetcher.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/configcat/ConfigFetcher.java b/src/main/java/com/configcat/ConfigFetcher.java index 8afef11..9b97605 100644 --- a/src/main/java/com/configcat/ConfigFetcher.java +++ b/src/main/java/com/configcat/ConfigFetcher.java @@ -111,8 +111,9 @@ public void onFailure(@NotNull Call call, @NotNull IOException e) { @Override public void onResponse(@NotNull Call call, @NotNull Response response) { - String cfRayId = response.header("CF-RAY"); + String cfRayId = null; try (ResponseBody body = response.body()) { + cfRayId = response.header("CF-RAY"); if (response.code() == 200) { String content = body != null ? body.string() : null; String eTag = response.header("ETag");