Skip to content

Commit bb35f16

Browse files
committed
Include Cloudflare Ray ID into log messages when the HTTP response is not successful
1 parent 11c047e commit bb35f16

File tree

5 files changed

+101
-28
lines changed

5 files changed

+101
-28
lines changed

src/ConfigCatLogger.ts

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -179,19 +179,27 @@ export class LoggerWrapper implements IConfigCatLogger {
179179
);
180180
}
181181

182-
fetchFailedDueToInvalidSdkKey(): LogMessage {
182+
fetchFailedDueToInvalidSdkKey(rayId: string | undefined): LogMessage {
183183
return this.log(
184184
LogLevel.Error, 1100,
185-
"Your SDK Key seems to be wrong. You can find the valid SDK Key at https://app.configcat.com/sdkkey"
185+
rayId == null
186+
? "Your SDK Key seems to be wrong. You can find the valid SDK Key at https://app.configcat.com/sdkkey"
187+
: FormattableLogMessage.from(
188+
"RAY_ID"
189+
)`Your SDK Key seems to be wrong. You can find the valid SDK Key at https://app.configcat.com/sdkkey (Ray ID: ${rayId})`
186190
);
187191
}
188192

189-
fetchFailedDueToUnexpectedHttpResponse(statusCode: number, reasonPhrase: string): LogMessage {
193+
fetchFailedDueToUnexpectedHttpResponse(statusCode: number, reasonPhrase: string, rayId: string | undefined): LogMessage {
190194
return this.log(
191195
LogLevel.Error, 1101,
192-
FormattableLogMessage.from(
193-
"STATUS_CODE", "REASON_PHRASE"
194-
)`Unexpected HTTP response was received while trying to fetch config JSON: ${statusCode} ${reasonPhrase}`
196+
rayId == null
197+
? FormattableLogMessage.from(
198+
"STATUS_CODE", "REASON_PHRASE"
199+
)`Unexpected HTTP response was received while trying to fetch config JSON: ${statusCode} ${reasonPhrase}`
200+
: FormattableLogMessage.from(
201+
"STATUS_CODE", "REASON_PHRASE", "RAY_ID"
202+
)`Unexpected HTTP response was received while trying to fetch config JSON: ${statusCode} ${reasonPhrase} (Ray ID: ${rayId})`
195203
);
196204
}
197205

@@ -212,27 +220,39 @@ export class LoggerWrapper implements IConfigCatLogger {
212220
);
213221
}
214222

215-
fetchFailedDueToRedirectLoop(): LogMessage {
223+
fetchFailedDueToRedirectLoop(rayId: string | undefined): LogMessage {
216224
return this.log(
217225
LogLevel.Error, 1104,
218-
"Redirection loop encountered while trying to fetch config JSON. Please contact us at https://configcat.com/support/"
226+
rayId == null
227+
? "Redirection loop encountered while trying to fetch config JSON. Please contact us at https://configcat.com/support/"
228+
: FormattableLogMessage.from(
229+
"RAY_ID"
230+
)`Redirection loop encountered while trying to fetch config JSON. Please contact us at https://configcat.com/support/ (Ray ID: ${rayId})`
219231
);
220232
}
221233

222-
fetchReceived200WithInvalidBody(ex: any): LogMessage {
234+
fetchReceived200WithInvalidBody(rayId: string | undefined, ex: any): LogMessage {
223235
return this.log(
224236
LogLevel.Error, 1105,
225-
"Fetching config JSON was successful but the HTTP response content was invalid.",
237+
rayId == null
238+
? "Fetching config JSON was successful but the HTTP response content was invalid."
239+
: FormattableLogMessage.from(
240+
"RAY_ID"
241+
)`Fetching config JSON was successful but the HTTP response content was invalid. (Ray ID: ${rayId})`,
226242
ex
227243
);
228244
}
229245

230-
fetchReceived304WhenLocalCacheIsEmpty(statusCode: number, reasonPhrase: string): LogMessage {
246+
fetchReceived304WhenLocalCacheIsEmpty(statusCode: number, reasonPhrase: string, rayId: string | undefined): LogMessage {
231247
return this.log(
232248
LogLevel.Error, 1106,
233-
FormattableLogMessage.from(
234-
"STATUS_CODE", "REASON_PHRASE"
235-
)`Unexpected HTTP response was received when no config JSON is cached locally: ${statusCode} ${reasonPhrase}`
249+
rayId == null
250+
? FormattableLogMessage.from(
251+
"STATUS_CODE", "REASON_PHRASE"
252+
)`Unexpected HTTP response was received when no config JSON is cached locally: ${statusCode} ${reasonPhrase}`
253+
: FormattableLogMessage.from(
254+
"STATUS_CODE", "REASON_PHRASE", "RAY_ID"
255+
)`Unexpected HTTP response was received when no config JSON is cached locally: ${statusCode} ${reasonPhrase} (Ray ID: ${rayId})`
236256
);
237257
}
238258

src/ConfigFetcher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class FetchResponse {
5252
/** The value of the `ETag` HTTP response header. */
5353
readonly eTag?: string;
5454

55-
protected readonly rayId?: string;
55+
private readonly rayId?: string;
5656

5757
constructor(
5858
/** The HTTP status code. */

src/ConfigServiceBase.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export abstract class ConfigServiceBase<TOptions extends OptionsBase> {
171171
switch (response.statusCode) {
172172
case 200: // OK
173173
if (!(configOrError instanceof Config)) {
174-
errorMessage = options.logger.fetchReceived200WithInvalidBody(configOrError).toString();
174+
errorMessage = options.logger.fetchReceived200WithInvalidBody(response["rayId"], configOrError).toString();
175175
options.logger.debug(`ConfigServiceBase.fetchLogicAsync(): ${response.statusCode} ${response.reasonPhrase} was received but the HTTP response content was invalid. Returning null.`);
176176
return FetchResult.error(lastConfig, errorMessage, configOrError);
177177
}
@@ -181,7 +181,7 @@ export abstract class ConfigServiceBase<TOptions extends OptionsBase> {
181181

182182
case 304: // Not Modified
183183
if (lastConfig.isEmpty) {
184-
errorMessage = options.logger.fetchReceived304WhenLocalCacheIsEmpty(response.statusCode, response.reasonPhrase).toString();
184+
errorMessage = options.logger.fetchReceived304WhenLocalCacheIsEmpty(response.statusCode, response.reasonPhrase, response["rayId"]).toString();
185185
options.logger.debug(`ConfigServiceBase.fetchLogicAsync(): ${response.statusCode} ${response.reasonPhrase} was received when no config is cached locally. Returning null.`);
186186
return FetchResult.error(lastConfig, errorMessage);
187187
}
@@ -191,12 +191,12 @@ export abstract class ConfigServiceBase<TOptions extends OptionsBase> {
191191

192192
case 403: // Forbidden
193193
case 404: // Not Found
194-
errorMessage = options.logger.fetchFailedDueToInvalidSdkKey().toString();
194+
errorMessage = options.logger.fetchFailedDueToInvalidSdkKey(response["rayId"]).toString();
195195
options.logger.debug("ConfigServiceBase.fetchLogicAsync(): fetch was unsuccessful. Returning last config (if any) with updated timestamp.");
196196
return FetchResult.error(lastConfig.with(ProjectConfig.generateTimestamp()), errorMessage);
197197

198198
default:
199-
errorMessage = options.logger.fetchFailedDueToUnexpectedHttpResponse(response.statusCode, response.reasonPhrase).toString();
199+
errorMessage = options.logger.fetchFailedDueToUnexpectedHttpResponse(response.statusCode, response.reasonPhrase, response["rayId"]).toString();
200200
options.logger.debug("ConfigServiceBase.fetchLogicAsync(): fetch was unsuccessful. Returning null.");
201201
return FetchResult.error(lastConfig, errorMessage);
202202
}
@@ -273,7 +273,7 @@ export abstract class ConfigServiceBase<TOptions extends OptionsBase> {
273273
}
274274

275275
if (retryNumber >= maxRetryCount) {
276-
options.logger.fetchFailedDueToRedirectLoop();
276+
options.logger.fetchFailedDueToRedirectLoop(response["rayId"]);
277277
return [response, config];
278278
}
279279
}

test/ConfigCatConfigFetcherTests.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { assert } from "chai";
1+
import { assert, expect } from "chai";
22
import { FakeConfigFetcherWithTwoKeys, FakeLogger } from "./helpers/fakes";
33
import { platform } from "./helpers/platform";
4-
import { FetchRequest, FetchResponse, IConfigCatConfigFetcher } from "#lib";
4+
import { FetchRequest, FetchResponse, FormattableLogMessage, IConfigCatConfigFetcher } from "#lib";
5+
import { ConfigCatClient } from "#lib/ConfigCatClient";
6+
import { OptionsBase } from "#lib/ConfigCatClientOptions";
57

68
describe("ConfigCatConfigFetcherTests", () => {
79

@@ -25,7 +27,7 @@ describe("ConfigCatConfigFetcherTests", () => {
2527
}();
2628

2729
const client = platform().createClientWithManualPoll(
28-
"test-67890123456789012/1234567890123456789012",
30+
"test-ccf-s-23456789012/1234567890123456789012",
2931
{ configFetcher }
3032
);
3133

@@ -55,8 +57,9 @@ describe("ConfigCatConfigFetcherTests", () => {
5557

5658
const fakeLogger = new FakeLogger();
5759

60+
const rayId = "CF-12345";
5861
const responseHeaders: [string, string][] = [
59-
["CF-RAY", "CF-12345"],
62+
["CF-RAY", rayId],
6063
];
6164

6265
const configFetcherRequests: FetchRequest[] = [];
@@ -68,7 +71,7 @@ describe("ConfigCatConfigFetcherTests", () => {
6871
}();
6972

7073
const client = platform().createClientWithManualPoll(
71-
"test-67890123456789012/1234567890123456789012",
74+
"test-ccf-f-23456789012/1234567890123456789012",
7275
{ configFetcher, logger: fakeLogger }
7376
);
7477

@@ -84,8 +87,25 @@ describe("ConfigCatConfigFetcherTests", () => {
8487
assert.strictEqual(configFetcherRequests.length, 1);
8588
assert.isUndefined(configFetcherRequests[0].lastETag);
8689

90+
// TODO: Change CORS settings so we can access the CF-RAY header? (https://stackoverflow.com/a/15043027)
91+
const clientVersion: string = (((client as ConfigCatClient)["options"]) as OptionsBase)["clientVersion"];
92+
if (clientVersion.includes("ConfigCat-UnifiedJS-Browser") || clientVersion.includes("ConfigCat-UnifiedJS-ChromiumExtension")) {
93+
return;
94+
}
95+
8796
const errors = fakeLogger.events.filter(([, eventId]) => eventId === 1100);
8897
assert.strictEqual(errors.length, 1);
98+
99+
const [[, , error]] = errors;
100+
assert.instanceOf(error, FormattableLogMessage);
101+
102+
assert.strictEqual(error.argNames.length, 1);
103+
assert.strictEqual(error.argNames[0], "RAY_ID");
104+
105+
assert.strictEqual(error.argValues.length, 1);
106+
assert.strictEqual(error.argValues[0], rayId);
107+
108+
expect(error.toString()).to.contain(rayId);
89109
});
90110

91111
});

test/IntegrationTests.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { assert } from "chai";
1+
import { assert, expect } from "chai";
2+
import { FakeLogger } from "./helpers/fakes";
23
import { platform } from "./helpers/platform";
3-
import { IConfigCatClient, IEvaluationDetails, IOptions, LogLevel, OverrideBehaviour, PollingMode, SettingKeyValue, User } from "#lib";
4-
import { createConsoleLogger, createFlagOverridesFromMap } from "#lib/index.pubternals";
4+
import { FormattableLogMessage, IConfigCatClient, IEvaluationDetails, IOptions, LogLevel, OverrideBehaviour, PollingMode, SettingKeyValue, User } from "#lib";
5+
import { createConsoleLogger, createFlagOverridesFromMap, OptionsBase } from "#lib/index.pubternals";
6+
import { ConfigCatClient } from "#lib/ConfigCatClient";
57

68
const sdkKey = "PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A";
79

@@ -288,4 +290,35 @@ describe("Integration tests - Other cases", () => {
288290
} finally { clientOverride.dispose(); }
289291
});
290292

293+
it("Should include ray ID in log messages when http response is not successful", async function () {
294+
const fakeLogger = new FakeLogger();
295+
296+
const client: IConfigCatClient = platform().getClient("configcat-sdk-1/~~~~~~~~~~~~~~~~~~~~~~/~~~~~~~~~~~~~~~~~~~~~~", PollingMode.ManualPoll, { logger: fakeLogger });
297+
298+
// TODO: Change CORS settings so we can access the CF-RAY header? (https://stackoverflow.com/a/15043027)
299+
const clientVersion: string = (((client as ConfigCatClient)["options"]) as OptionsBase)["clientVersion"];
300+
if (clientVersion.includes("ConfigCat-UnifiedJS-Browser") || clientVersion.includes("ConfigCat-UnifiedJS-ChromiumExtension")) {
301+
this.skip();
302+
}
303+
304+
await client.forceRefreshAsync();
305+
306+
const errors = fakeLogger.events.filter(([, eventId]) => eventId === 1100);
307+
assert.strictEqual(errors.length, 1);
308+
309+
const [[, , error]] = errors;
310+
assert.instanceOf(error, FormattableLogMessage);
311+
312+
assert.strictEqual(error.argNames.length, 1);
313+
assert.strictEqual(error.argNames[0], "RAY_ID");
314+
315+
assert.strictEqual(error.argValues.length, 1);
316+
const [rayId] = error.argValues;
317+
assert.isString(rayId);
318+
319+
expect(error.toString()).to.contain(rayId);
320+
321+
client.dispose();
322+
});
323+
291324
});

0 commit comments

Comments
 (0)