Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare v9.3.0 release #96

Merged
merged 6 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
environment:
build_version: 9.2.0
build_version: 9.3.0
version: $(build_version)-{build}
image: Visual Studio 2022
configuration: Release
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -475,4 +475,29 @@ public async Task SpecialCharacters_Works(string settingKey, string userId, stri
var actual = await client.GetValueAsync(settingKey, "NOT_CAT", new User(userId));
Assert.AreEqual(expectedValue, actual);
}

[TestMethod]
public async Task ShouldIncludeRayIdInLogMessagesWhenHttpResponseIsNotSuccessful()
{
var logEvents = new List<LogEvent>();
var logger = LoggingHelper.CreateCapturingLogger(logEvents, LogLevel.Info);

using IConfigCatClient client = ConfigCatClient.Get("configcat-sdk-1/~~~~~~~~~~~~~~~~~~~~~~/~~~~~~~~~~~~~~~~~~~~~~", options =>
{
options.PollingMode = PollingModes.ManualPoll;
options.Logger = logger;
});

await client.ForceRefreshAsync();

var errors = logEvents.Where(evt => evt.EventId == 1100).ToArray();
Assert.AreEqual(1, errors.Length);

var error = errors[0].Message;
Assert.AreEqual(1, error.ArgValues.Length);
Assert.IsTrue(error.ArgValues[0] is string);

var rayId = (string)error.ArgValues[0]!;
StringAssert.Contains(errors[0].Message.InvariantFormattedMessage, rayId);
}
}
10 changes: 5 additions & 5 deletions src/ConfigCatClient/ConfigService/DefaultConfigFetcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ private async ValueTask<FetchResult> FetchInternalAsync(ProjectConfig lastConfig
if (config is null)
{
var exception = deserializedResponse.Exception;
logMessage = this.logger.FetchReceived200WithInvalidBody(exception);
logMessage = this.logger.FetchReceived200WithInvalidBody(response.RayId, exception);
return FetchResult.Failure(lastConfig, RefreshErrorCode.InvalidHttpResponseContent, logMessage.ToLazyString(), exception);
}

Expand All @@ -104,21 +104,21 @@ private async ValueTask<FetchResult> FetchInternalAsync(ProjectConfig lastConfig
case HttpStatusCode.NotModified:
if (lastConfig.IsEmpty)
{
logMessage = this.logger.FetchReceived304WhenLocalCacheIsEmpty((int)response.StatusCode, response.ReasonPhrase);
logMessage = this.logger.FetchReceived304WhenLocalCacheIsEmpty((int)response.StatusCode, response.ReasonPhrase, response.RayId);
return FetchResult.Failure(lastConfig, RefreshErrorCode.InvalidHttpResponseWhenLocalCacheIsEmpty, logMessage.ToLazyString());
}

return FetchResult.NotModified(lastConfig.With(ProjectConfig.GenerateTimeStamp()));

case HttpStatusCode.Forbidden:
case HttpStatusCode.NotFound:
logMessage = this.logger.FetchFailedDueToInvalidSdkKey();
logMessage = this.logger.FetchFailedDueToInvalidSdkKey(response.RayId);

// We update the timestamp for extra protection against flooding.
return FetchResult.Failure(lastConfig.With(ProjectConfig.GenerateTimeStamp()), RefreshErrorCode.InvalidSdkKey, logMessage.ToLazyString());

default:
logMessage = this.logger.FetchFailedDueToUnexpectedHttpResponse((int)response.StatusCode, response.ReasonPhrase);
logMessage = this.logger.FetchFailedDueToUnexpectedHttpResponse((int)response.StatusCode, response.ReasonPhrase, response.RayId);
return FetchResult.Failure(lastConfig, RefreshErrorCode.UnexpectedHttpResponse, logMessage.ToLazyString());
}
}
Expand Down Expand Up @@ -193,7 +193,7 @@ private async ValueTask<DeserializedResponse> FetchRequestAsync(string? httpETag

if (maxExecutionCount <= 1)
{
this.logger.FetchFailedDueToRedirectLoop();
this.logger.FetchFailedDueToRedirectLoop(response.RayId);
return new DeserializedResponse(response, config);
}

Expand Down
27 changes: 21 additions & 6 deletions src/ConfigCatClient/ConfigService/FetchResponse.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;

namespace ConfigCat.Client;

Expand All @@ -7,17 +10,27 @@ namespace ConfigCat.Client;
/// </summary>
public readonly struct FetchResponse
{
/// <summary>
/// Initializes a new instance of the <see cref="FetchResponse"/> struct.
/// </summary>
public FetchResponse(HttpStatusCode statusCode, string? reasonPhrase, string? eTag, string? body)
internal static FetchResponse From(HttpResponseMessage httpResponse, string? httpResponseBody = null)
{
return new FetchResponse(httpResponse.StatusCode, httpResponse.ReasonPhrase, httpResponse.Headers, httpResponseBody);
endret marked this conversation as resolved.
Show resolved Hide resolved
}

private readonly object? headersOrETag; // either null or a string or HttpResponseHeaders

private FetchResponse(HttpStatusCode statusCode, string? reasonPhrase, object? headersOrETag, string? body)
{
StatusCode = statusCode;
ReasonPhrase = reasonPhrase;
ETag = eTag;
this.headersOrETag = headersOrETag;
Body = body;
}

/// <summary>
/// Initializes a new instance of the <see cref="FetchResponse"/> struct.
/// </summary>
public FetchResponse(HttpStatusCode statusCode, string? reasonPhrase, string? eTag, string? body)
: this(statusCode, reasonPhrase, (object?)eTag, body) { }

/// <summary>
/// The HTTP status code.
/// </summary>
Expand All @@ -31,7 +44,9 @@ public FetchResponse(HttpStatusCode statusCode, string? reasonPhrase, string? eT
/// <summary>
/// The value of the <c>ETag</c> HTTP response header.
/// </summary>
public string? ETag { get; }
public string? ETag => this.headersOrETag is HttpResponseHeaders headers ? headers.ETag?.Tag : (string?)this.headersOrETag;

internal string? RayId => this.headersOrETag is HttpResponseHeaders headers && headers.TryGetValues("CF-RAY", out var values) ? values.FirstOrDefault() : null;

/// <summary>
/// The response body.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,11 @@ public async Task<FetchResponse> FetchAsync(FetchRequest request, CancellationTo
var httpResponseBody = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(TaskShim.ContinueOnCapturedContext);
#endif

return new FetchResponse(httpResponse.StatusCode, httpResponse.ReasonPhrase, httpResponse.Headers.ETag?.Tag, httpResponseBody);
return FetchResponse.From(httpResponse, httpResponseBody);
}
else
{
var response = new FetchResponse(httpResponse.StatusCode, httpResponse.ReasonPhrase, eTag: null, body: null);
var response = FetchResponse.From(httpResponse);
if (!response.IsExpected)
{
this.httpClient = null;
Expand Down
65 changes: 45 additions & 20 deletions src/ConfigCatClient/Logging/LogMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,24 @@ public static FormattableLogMessage ForceRefreshError(this LoggerWrapper logger,
$"Error occurred in the `{methodName}` method.",
"METHOD_NAME");

public static FormattableLogMessage FetchFailedDueToInvalidSdkKey(this LoggerWrapper logger) => logger.Log(
LogLevel.Error, 1100,
"Your SDK Key seems to be wrong. You can find the valid SDK Key at https://app.configcat.com/sdkkey");

public static FormattableLogMessage FetchFailedDueToUnexpectedHttpResponse(this LoggerWrapper logger, int statusCode, string? reasonPhrase) => logger.LogInterpolated(
LogLevel.Error, 1101,
$"Unexpected HTTP response was received while trying to fetch config JSON: {statusCode} {reasonPhrase}",
"STATUS_CODE", "REASON_PHRASE");
public static FormattableLogMessage FetchFailedDueToInvalidSdkKey(this LoggerWrapper logger, string? rayId) => rayId is not null
? logger.LogInterpolated(
LogLevel.Error, 1100,
$"Your SDK Key seems to be wrong. You can find the valid SDK Key at https://app.configcat.com/sdkkey (Ray ID: {rayId})",
"RAY_ID")
: logger.Log(
LogLevel.Error, 1100,
"Your SDK Key seems to be wrong. You can find the valid SDK Key at https://app.configcat.com/sdkkey");

public static FormattableLogMessage FetchFailedDueToUnexpectedHttpResponse(this LoggerWrapper logger, int statusCode, string? reasonPhrase, string? rayId) => rayId is not null
? logger.LogInterpolated(
LogLevel.Error, 1101,
$"Unexpected HTTP response was received while trying to fetch config JSON: {statusCode} {reasonPhrase} (Ray ID: {rayId})",
"STATUS_CODE", "REASON_PHRASE", "RAY_ID")
: logger.LogInterpolated(
LogLevel.Error, 1101,
$"Unexpected HTTP response was received while trying to fetch config JSON: {statusCode} {reasonPhrase}",
"STATUS_CODE", "REASON_PHRASE");

public static FormattableLogMessage FetchFailedDueToRequestTimeout(this LoggerWrapper logger, TimeSpan timeout, Exception ex) => logger.LogInterpolated(
LogLevel.Error, 1102, ex,
Expand All @@ -56,18 +66,33 @@ public static FormattableLogMessage FetchFailedDueToUnexpectedError(this LoggerW
LogLevel.Error, 1103, ex,
"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.");

public static FormattableLogMessage FetchFailedDueToRedirectLoop(this LoggerWrapper logger) => logger.Log(
LogLevel.Error, 1104,
"Redirection loop encountered while trying to fetch config JSON. Please contact us at https://configcat.com/support/");

public static FormattableLogMessage FetchReceived200WithInvalidBody(this LoggerWrapper logger, Exception? ex) => logger.Log(
LogLevel.Error, 1105, ex,
"Fetching config JSON was successful but the HTTP response content was invalid.");

public static FormattableLogMessage FetchReceived304WhenLocalCacheIsEmpty(this LoggerWrapper logger, int statusCode, string? reasonPhrase) => logger.LogInterpolated(
LogLevel.Error, 1106,
$"Unexpected HTTP response was received when no config JSON is cached locally: {statusCode} {reasonPhrase}",
"STATUS_CODE", "REASON_PHRASE");
public static FormattableLogMessage FetchFailedDueToRedirectLoop(this LoggerWrapper logger, string? rayId) => rayId is not null
? logger.LogInterpolated(
LogLevel.Error, 1104,
$"Redirection loop encountered while trying to fetch config JSON. Please contact us at https://configcat.com/support/ (Ray ID: {rayId})",
"RAY_ID")
: logger.Log(
LogLevel.Error, 1104,
"Redirection loop encountered while trying to fetch config JSON. Please contact us at https://configcat.com/support/");

public static FormattableLogMessage FetchReceived200WithInvalidBody(this LoggerWrapper logger, string? rayId, Exception? ex) => rayId is not null
? logger.LogInterpolated(
LogLevel.Error, 1105, ex,
$"Fetching config JSON was successful but the HTTP response content was invalid. (Ray ID: {rayId})",
"RAY_ID")
: logger.Log(
LogLevel.Error, 1105, ex,
"Fetching config JSON was successful but the HTTP response content was invalid.");

public static FormattableLogMessage FetchReceived304WhenLocalCacheIsEmpty(this LoggerWrapper logger, int statusCode, string? reasonPhrase, string? rayId) => rayId is not null
? logger.LogInterpolated(
LogLevel.Error, 1106,
$"Unexpected HTTP response was received when no config JSON is cached locally: {statusCode} {reasonPhrase} (Ray ID: {rayId})",
"STATUS_CODE", "REASON_PHRASE", "RAY_ID")
: logger.LogInterpolated(
LogLevel.Error, 1106,
$"Unexpected HTTP response was received when no config JSON is cached locally: {statusCode} {reasonPhrase}",
"STATUS_CODE", "REASON_PHRASE");

public static FormattableLogMessage AutoPollConfigServiceErrorDuringPolling(this LoggerWrapper logger, Exception ex) => logger.Log(
LogLevel.Error, 1200, ex,
Expand Down
2 changes: 1 addition & 1 deletion src/ConfigCatClient/Utils/SerializationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,4 +255,4 @@ private static string UnescapeAstralCodePoints(string json)
});
}
#endif
}
}
Loading