Skip to content

Commit

Permalink
Cf Ray header (#60)
Browse files Browse the repository at this point in the history
* Added excludeLogEventIds options to the client. The logger will exclude logs if the log event ID is on the provided list.

* Prepare 10.3.0 release

* Added FormattableLogMessage to optimize log message format.

* Code clean up

* Fix typo

* Add CF-RAY header value to ConfigFetcher log if presented in the response.
  • Loading branch information
novalisdenahi authored Dec 17, 2024
1 parent febd74c commit 1bae02f
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 35 deletions.
76 changes: 65 additions & 11 deletions src/main/java/com/configcat/ConfigCatLogMessages.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.configcat;

import java.util.Iterator;
import java.util.Set;


Expand All @@ -22,15 +23,6 @@ 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 Received 200 With Invalid Body error. The log eventId is 1105.
*/
public static final String FETCH_RECEIVED_200_WITH_INVALID_BODY_ERROR = "Fetching config JSON was successful but the HTTP response content was invalid.";
/**
* Log message for Fetch Failed Due To Redirect Loop error. The log eventId is 1104.
*/
public static final String FETCH_FAILED_DUE_TO_REDIRECT_LOOP_ERROR = "Redirection loop encountered while trying to fetch config JSON. Please contact us at https://configcat.com/support/";

/**
* Log message for Fetch Failed Due To Unexpected error. The log eventId is 1103.
*/
Expand All @@ -39,7 +31,15 @@ final class ConfigCatLogMessages {
/**
* Log message for Fetch Failed Due To Invalid Sdk Key error. The log eventId is 1100.
*/
public 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";
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 Redirect Loop error. The log eventId is 1104.
*/
private static final String FETCH_FAILED_DUE_TO_REDIRECT_LOOP_ERROR = "Redirection loop encountered while trying to fetch config JSON. Please contact us at https://configcat.com/support/";
/**
* Log message for Fetch Received 200 With Invalid Body error. The log eventId is 1105.
*/
private static final String FETCH_RECEIVED_200_WITH_INVALID_BODY_ERROR = "Fetching config JSON was successful but the HTTP response content was invalid.";

private ConfigCatLogMessages() { /* prevent from instantiation*/ }

Expand Down Expand Up @@ -134,14 +134,31 @@ public static FormattableLogMessage getSettingForVariationIdIsNotPresent(final S
return new FormattableLogMessage("Could not find the setting for the specified variation ID: '%s'.", variationId);
}

/**
* Log message for Fetch Failed Due To Invalid Sdk Key error. The log eventId is 1100.
*
* @param cfRayId The http response CF-RAY header value.
* @return The formattable log message.
*/
public static FormattableLogMessage getFetchFailedDueToInvalidSDKKey(final String cfRayId) {
if (cfRayId != null) {
return new FormattableLogMessage(FETCH_FAILED_DUE_TO_INVALID_SDK_KEY_ERROR + " %s", ConfigCatLogMessages.getCFRayIdPostFix(cfRayId));
}
return new FormattableLogMessage(FETCH_FAILED_DUE_TO_INVALID_SDK_KEY_ERROR);
}

/**
* Log message for Fetch Failed Due To Unexpected Http Response error. The log eventId is 1101.
*
* @param responseCode The http response code.
* @param responseMessage The http response message.
* @param cfRayId The http response CF-RAY header value.
* @return The formattable log message.
*/
public static FormattableLogMessage getFetchFailedDueToUnexpectedHttpResponse(final int responseCode, final String responseMessage) {
public static FormattableLogMessage getFetchFailedDueToUnexpectedHttpResponse(final int responseCode, final String responseMessage, final String cfRayId) {
if (cfRayId != null) {
return new FormattableLogMessage("Unexpected HTTP response was received while trying to fetch config JSON: %d %s %s", responseCode, responseMessage, ConfigCatLogMessages.getCFRayIdPostFix(cfRayId));
}
return new FormattableLogMessage("Unexpected HTTP response was received while trying to fetch config JSON: %d %s", responseCode, responseMessage);
}

Expand All @@ -157,6 +174,32 @@ public static FormattableLogMessage getFetchFailedDueToRequestTimeout(final Inte
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 Redirect Loop error. The log eventId is 1104.
*
* @param cfRayId The http response CF-RAY header value.
* @return The formattable log message.
*/
public static FormattableLogMessage getFetchFailedDueToRedirectLoop(final String cfRayId) {
if (cfRayId != null) {
return new FormattableLogMessage(FETCH_FAILED_DUE_TO_REDIRECT_LOOP_ERROR + " %s", ConfigCatLogMessages.getCFRayIdPostFix(cfRayId));
}
return new FormattableLogMessage(FETCH_FAILED_DUE_TO_REDIRECT_LOOP_ERROR);
}

/**
* Log message for Fetch Received 200 With Invalid Body error. The log eventId is 1105.
*
* @param cfRayId The http response CF-RAY header value.
* @return The formattable log message.
*/
public static FormattableLogMessage getFetchReceived200WithInvalidBodyError(final String cfRayId) {
if (cfRayId != null) {
return new FormattableLogMessage(FETCH_RECEIVED_200_WITH_INVALID_BODY_ERROR + " %s", ConfigCatLogMessages.getCFRayIdPostFix(cfRayId));
}
return new FormattableLogMessage(FETCH_RECEIVED_200_WITH_INVALID_BODY_ERROR);
}

/**
* Log message for Client Is Already Created warning. The log eventId 3000.
*
Expand Down Expand Up @@ -256,4 +299,15 @@ public static FormattableLogMessage getConfigServiceStatusChanged(final String m
return new FormattableLogMessage("Switched to %s mode.", mode);
}


/**
* Get CF-RAY ID header post fix log message.
*
* @param rayId The HTTP response CF-RAY header value.
* @return The formattable log message.
*/
public static FormattableLogMessage getCFRayIdPostFix(String rayId) {
return new FormattableLogMessage("(Ray ID: %s)", rayId);
}

}
56 changes: 32 additions & 24 deletions src/main/java/com/configcat/ConfigFetcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public enum Status {
private final Entry entry;
private final Object error;
private final boolean fetchTimeUpdatable;
private final String cfRayId;

public boolean isFetched() {
return this.status == Status.FETCHED;
Expand All @@ -45,23 +46,26 @@ public Object error() {
return this.error;
}

FetchResponse(Status status, Entry entry, Object error, boolean fetchTimeUpdatable) {
public String cfRayId() {return this.cfRayId;}

FetchResponse(Status status, Entry entry, Object error, boolean fetchTimeUpdatable, String cfRayId) {
this.status = status;
this.entry = entry;
this.error = error;
this.fetchTimeUpdatable = fetchTimeUpdatable;
this.cfRayId = cfRayId;
}

public static FetchResponse fetched(Entry entry) {
return new FetchResponse(Status.FETCHED, entry == null ? Entry.EMPTY : entry, null, false);
public static FetchResponse fetched(Entry entry, String cfRayId) {
return new FetchResponse(Status.FETCHED, entry == null ? Entry.EMPTY : entry, null, false, cfRayId);
}

public static FetchResponse notModified() {
return new FetchResponse(Status.NOT_MODIFIED, Entry.EMPTY, null, true);
public static FetchResponse notModified(String cfRayId) {
return new FetchResponse(Status.NOT_MODIFIED, Entry.EMPTY, null, true, cfRayId);
}

public static FetchResponse failed(Object error, boolean fetchTimeUpdatable) {
return new FetchResponse(Status.FAILED, Entry.EMPTY, error, fetchTimeUpdatable);
public static FetchResponse failed(Object error, boolean fetchTimeUpdatable, String cfRayId) {
return new FetchResponse(Status.FAILED, Entry.EMPTY, error, fetchTimeUpdatable, cfRayId);
}
}

Expand Down Expand Up @@ -142,7 +146,7 @@ private CompletableFuture<FetchResponse> executeFetchAsync(int executionCount, S
return CompletableFuture.completedFuture(fetchResponse);
}

this.logger.error(1104, ConfigCatLogMessages.FETCH_FAILED_DUE_TO_REDIRECT_LOOP_ERROR);
this.logger.error(1104, ConfigCatLogMessages.getFetchFailedDueToRedirectLoop(fetchResponse.cfRayId()));
return CompletableFuture.completedFuture(fetchResponse);
});
}
Expand All @@ -158,47 +162,51 @@ public void onFailure(@NotNull Call call, @NotNull IOException e) {
if (e instanceof SocketTimeoutException) {
FormattableLogMessage message = ConfigCatLogMessages.getFetchFailedDueToRequestTimeout(httpClient.connectTimeoutMillis(), httpClient.readTimeoutMillis(), httpClient.writeTimeoutMillis());
logger.error(1102, message, e);
future.complete(FetchResponse.failed(message, false));
future.complete(FetchResponse.failed(message, false, null));
return;
}
logger.error(1103, generalMessage, e);
}
future.complete(FetchResponse.failed(generalMessage, false));
future.complete(FetchResponse.failed(generalMessage, false, null));
}

@Override
public void onResponse(@NotNull Call call, @NotNull Response response) {
try (ResponseBody body = response.body()) {
String cfRayId = response.header("CF-RAY");
if (response.isSuccessful() && body != null) {
String content = body.string();
String eTag = response.header("ETag");
Result<Config> result = deserializeConfig(content);
Result<Config> result = deserializeConfig(content, cfRayId);
if (result.error() != null) {
future.complete(FetchResponse.failed(result.error(), false));
future.complete(FetchResponse.failed(result.error(), false, cfRayId));
return;
}
logger.debug("Fetch was successful: new config fetched.");
future.complete(FetchResponse.fetched(new Entry(result.value(), eTag, content, System.currentTimeMillis())));
future.complete(FetchResponse.fetched(new Entry(result.value(), eTag, content, System.currentTimeMillis()), cfRayId));
} else if (response.code() == 304) {
logger.debug("Fetch was successful: config not modified.");
future.complete(FetchResponse.notModified());
if(cfRayId != null) {
logger.debug(String.format("Fetch was successful: config not modified. %s", ConfigCatLogMessages.getCFRayIdPostFix(cfRayId)));
} else {
logger.debug("Fetch was successful: config not modified.");
}
future.complete(FetchResponse.notModified(cfRayId));
} else if (response.code() == 403 || response.code() == 404) {
String message = ConfigCatLogMessages.FETCH_FAILED_DUE_TO_INVALID_SDK_KEY_ERROR;
logger.error(1100, message);
future.complete(FetchResponse.failed(message, true));
FormattableLogMessage message = ConfigCatLogMessages.getFetchFailedDueToInvalidSDKKey(cfRayId); logger.error(1100, message);
future.complete(FetchResponse.failed(message, true, cfRayId));
} else {
FormattableLogMessage message = ConfigCatLogMessages.getFetchFailedDueToUnexpectedHttpResponse(response.code(), response.message());
FormattableLogMessage message = ConfigCatLogMessages.getFetchFailedDueToUnexpectedHttpResponse(response.code(), response.message(), cfRayId);
logger.error(1101, message);
future.complete(FetchResponse.failed(message, false));
future.complete(FetchResponse.failed(message, false, cfRayId));
}
} catch (SocketTimeoutException e) {
FormattableLogMessage message = ConfigCatLogMessages.getFetchFailedDueToRequestTimeout(httpClient.connectTimeoutMillis(), httpClient.readTimeoutMillis(), httpClient.writeTimeoutMillis());
logger.error(1102, message, e);
future.complete(FetchResponse.failed(message, false));
future.complete(FetchResponse.failed(message, false, null));
} catch (Exception e) {
String message = ConfigCatLogMessages.FETCH_FAILED_DUE_TO_UNEXPECTED_ERROR;
logger.error(1103, message, e);
future.complete(FetchResponse.failed(message + " " + e.getMessage(), false));
future.complete(FetchResponse.failed(message + " " + e.getMessage(), false, null));
}
}
});
Expand Down Expand Up @@ -231,11 +239,11 @@ private Request getRequest(String eTag) {
return builder.url(requestUrl).build();
}

private Result<Config> deserializeConfig(String json) {
private Result<Config> deserializeConfig(String json, String cfRayId) {
try {
return Result.success(Utils.deserializeConfig(json));
} catch (Exception e) {
String message = ConfigCatLogMessages.FETCH_RECEIVED_200_WITH_INVALID_BODY_ERROR;
FormattableLogMessage message = ConfigCatLogMessages.getFetchReceived200WithInvalidBodyError(cfRayId);
this.logger.error(1105, message, e);
return Result.error(message, null);
}
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/com/configcat/FormattableLogMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import org.jetbrains.annotations.NotNull;

import java.util.Arrays;
import java.util.Objects;

class FormattableLogMessage {

private String cachedMessage;
Expand All @@ -25,4 +28,18 @@ public String toString() {
}
return cachedMessage;
}


@Override
public boolean equals(Object obj) {
if(obj instanceof FormattableLogMessage) {
return toString().equals(obj.toString());
}
return false;
}

@Override
public int hashCode() {
return Objects.hash(cachedMessage, message, Arrays.hashCode(args));
}
}
77 changes: 77 additions & 0 deletions src/test/java/com/configcat/ConfigFetcherTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
Expand Down Expand Up @@ -156,4 +157,80 @@ void testIntegration() throws IOException, ExecutionException, InterruptedExcept

fetch.close();
}

@Test
public void fetchedFail403ContainsCFRAY() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(403).setBody(TEST_JSON).setHeader("ETag", "fakeETag").setHeader("CF-RAY", "12345"));

Logger mockLogger = mock(Logger.class);

ConfigCatLogger localLogger = new ConfigCatLogger(mockLogger, LogLevel.DEBUG, null, null);

ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(),
localLogger,
"",
this.server.url("/").toString(),
false,
PollingModes.manualPoll().getPollingIdentifier());


FetchResponse response = fetcher.fetchAsync("fakeETag").get();
assertTrue(response.isFailed());
assertTrue(response.error().toString().contains("(Ray ID: 12345)"));

verify(mockLogger, times(1)).error(anyString(), eq(1100), eq(ConfigCatLogMessages.getFetchFailedDueToInvalidSDKKey("12345")));

fetcher.close();
}

@Test
public void fetchedNotModified304ContainsCFRAY() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(304).setHeader("CF-RAY", "12345"));

Logger mockLogger = mock(Logger.class);

ConfigCatLogger localLogger = new ConfigCatLogger(mockLogger, LogLevel.DEBUG, null, null);

ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(),
localLogger,
"",
this.server.url("/").toString(),
false,
PollingModes.manualPoll().getPollingIdentifier());


FetchResponse response = fetcher.fetchAsync("fakeETag").get();

assertTrue(response.isNotModified());

verify(mockLogger, times(1)).debug(anyString(), eq(0), eq(String.format("Fetch was successful: config not modified. %s", ConfigCatLogMessages.getCFRayIdPostFix("12345"))));

fetcher.close();
}

@Test
public void fetchedReceivedInvalidBodyContainsCFRAY() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(200).setHeader("CF-RAY", "12345").setBody("test"));

Logger mockLogger = mock(Logger.class);

ConfigCatLogger localLogger = new ConfigCatLogger(mockLogger, LogLevel.DEBUG, null, null);

ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(),
localLogger,
"",
this.server.url("/").toString(),
false,
PollingModes.manualPoll().getPollingIdentifier());


FetchResponse response = fetcher.fetchAsync("fakeETag").get();

assertTrue(response.isFailed());
assertTrue(response.error().toString().contains("(Ray ID: 12345)"));

verify(mockLogger, times(1)).error(anyString(), eq(1105), eq(ConfigCatLogMessages.getFetchReceived200WithInvalidBodyError("12345")), any(Exception.class));

fetcher.close();
}
}
Loading

0 comments on commit 1bae02f

Please sign in to comment.