From 30cd86b184ba38713c3b484cb78d5804b46756af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Tue, 17 Oct 2023 16:29:54 +0200 Subject: [PATCH 01/37] [Docs] Fix component name --- docs/src/pages/who-is-using-foal.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/pages/who-is-using-foal.jsx b/docs/src/pages/who-is-using-foal.jsx index c0f4a5b536..cce16bd27e 100644 --- a/docs/src/pages/who-is-using-foal.jsx +++ b/docs/src/pages/who-is-using-foal.jsx @@ -14,7 +14,7 @@ const posts = [ }, ]; -export default function Newsletter() { +export default function WhoIsUsingFoal() { return ( From 43d47c9fb88cb6a459e574e4b1c3895df2ab9c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 18 Oct 2023 10:33:14 +0200 Subject: [PATCH 02/37] Add Foal logger --- packages/core/src/common/index.ts | 1 + packages/core/src/common/logging/index.ts | 1 + .../core/src/common/logging/logger.spec.ts | 298 ++++++++++++++++++ packages/core/src/common/logging/logger.ts | 170 ++++++++++ packages/core/src/index.ts | 1 + 5 files changed, 471 insertions(+) create mode 100644 packages/core/src/common/logging/index.ts create mode 100644 packages/core/src/common/logging/logger.spec.ts create mode 100644 packages/core/src/common/logging/logger.ts diff --git a/packages/core/src/common/index.ts b/packages/core/src/common/index.ts index 0bb0e9d37b..61e6d65bdb 100644 --- a/packages/core/src/common/index.ts +++ b/packages/core/src/common/index.ts @@ -1,6 +1,7 @@ export * from './auth'; export * from './encoding'; export * from './file'; +export * from './logging'; export * from './templates'; export * from './tokens'; export * from './utils'; diff --git a/packages/core/src/common/logging/index.ts b/packages/core/src/common/logging/index.ts new file mode 100644 index 0000000000..0a9e39b0e0 --- /dev/null +++ b/packages/core/src/common/logging/index.ts @@ -0,0 +1 @@ +export { Logger } from './logger'; \ No newline at end of file diff --git a/packages/core/src/common/logging/logger.spec.ts b/packages/core/src/common/logging/logger.spec.ts new file mode 100644 index 0000000000..89e1722cd5 --- /dev/null +++ b/packages/core/src/common/logging/logger.spec.ts @@ -0,0 +1,298 @@ +import { deepStrictEqual, strictEqual, throws } from 'assert'; +import { Logger } from './logger'; +import { Config } from '../../core'; + +const testStack = `Error: aaa + at createTestParams (/somewhere/logger.spec.ts:6:11) + at Context. (/somewhere/logger.spec.ts:129:13)`; + +function createTestParams() { + const err = new Error('aaa'); + err.stack = testStack; + return { + myBoolean: false, + myNull: null, + myUndefined: undefined, + myNumber: 0, + myString: 'xxx', + mySymbol: Symbol('yyy'), + myObject: { foo: 'bar' }, + err + }; +} + +function createLogger(): { + logger: Logger, + mocks: { logWithConsoleHasBeenCalledWith: any }, + now: Date, + isoNow: string, + localTimeNow: string +} { + const mocks: { logWithConsoleHasBeenCalledWith: any } = { logWithConsoleHasBeenCalledWith: undefined }; + + const isoNow = '2023-02-03T01:12:03.000Z'; + const localTimeNow = '2:12:03 AM'; + const now = new Date(isoNow); + + class Logger2 extends Logger { + logWithConsole(message: string): void { + mocks.logWithConsoleHasBeenCalledWith = message; + } + + getNow(): Date { + return now; + } + } + + const logger = new Logger2(); + + return { + logger, + mocks, + now, + isoNow, + localTimeNow, + }; +} + +describe('Logger', () => { + + afterEach(() => { + Config.remove('settings.logger.format'); + }) + + describe('when debug is called', () => { + context('given the configuration "settings.logger.format" is NOT defined', () => { + it('should log a text with an exhaustive timestamp, with params but with no colors', () => { + const { logger, mocks, isoNow } = createLogger(); + + const message = 'Hello world'; + const params = createTestParams(); + + logger.debug(message, params); + + const actual = mocks.logWithConsoleHasBeenCalledWith; + const expected = `[${isoNow}] [DEBUG] Hello world` + + `\n myBoolean: false` + + `\n myNull: null` + + `\n myNumber: 0` + + `\n myString: "xxx"` + + `\n myObject: {` + + `\n "foo": "bar"` + + `\n }` + + `\n err: {` + + `\n name: "Error"` + + `\n message: "aaa"` + + `\n stack: Error: aaa` + + `\n at createTestParams (/somewhere/logger.spec.ts:6:11)` + + `\n at Context. (/somewhere/logger.spec.ts:129:13)` + + `\n }`; + + strictEqual(actual, expected); + }); + }); + + context('given the configuration "settings.logger.format" is "raw"', () => { + beforeEach(() => { + Config.set('settings.logger.format', 'raw'); + }); + + it('should log a text with an exhaustive timestamp, with params but with no colors', () => { + const { logger, mocks, isoNow } = createLogger(); + + const message = 'Hello world'; + const params = createTestParams(); + + logger.debug(message, params); + + const actual = mocks.logWithConsoleHasBeenCalledWith; + const expected = `[${isoNow}] [DEBUG] Hello world` + + `\n myBoolean: false` + + `\n myNull: null` + + `\n myNumber: 0` + + `\n myString: "xxx"` + + `\n myObject: {` + + `\n "foo": "bar"` + + `\n }` + + `\n err: {` + + `\n name: "Error"` + + `\n message: "aaa"` + + `\n stack: Error: aaa` + + `\n at createTestParams (/somewhere/logger.spec.ts:6:11)` + + `\n at Context. (/somewhere/logger.spec.ts:129:13)` + + `\n }`; + + strictEqual(actual, expected); + }); + }); + + + context('given the configuration "settings.logger.format" is "dev"', () => { + beforeEach(() => { + Config.set('settings.logger.format', 'dev'); + }); + + it('should log a text with a short timestamp, with colors but with no params (except the "err" param)', () => { + const { logger, mocks, localTimeNow } = createLogger(); + + const message = 'Hello world'; + const params = createTestParams(); + + logger.debug(message, params); + + const actual = mocks.logWithConsoleHasBeenCalledWith; + // Pink color + const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[35mDEBUG\u001b[39m Hello world` + + `\n err: {` + + `\n name: "Error"` + + `\n message: "aaa"` + + `\n stack: Error: aaa` + + `\n at createTestParams (/somewhere/logger.spec.ts:6:11)` + + `\n at Context. (/somewhere/logger.spec.ts:129:13)` + + `\n }`; + + strictEqual(actual, expected); + }); + }); + + context('given the configuration "settings.logger.format" is "json"', () => { + beforeEach(() => { + Config.set('settings.logger.format', 'json'); + }); + + it('should log a JSON', () => { + const { logger, mocks, isoNow } = createLogger(); + + const message = 'Hello world'; + const params = createTestParams(); + + logger.debug(message, params); + + const actual = JSON.parse(mocks.logWithConsoleHasBeenCalledWith); + const expected = { + message, + timestamp: isoNow, + level: 'debug', + myBoolean: false, + myNull: null, + myNumber: 0, + myString: 'xxx', + myObject: { foo: 'bar' }, + err: { + name: 'Error', + message: 'aaa', + stack: testStack, + } + }; + + deepStrictEqual(actual, expected); + }); + }); + + context('given the configuration "settings.logger.format" is "none"', () => { + beforeEach(() => { + Config.set('settings.logger.format', 'none'); + }); + + it('should log nothing', () => { + const { logger, mocks, isoNow } = createLogger(); + + const message = 'Hello world'; + const params = createTestParams(); + + logger.debug(message, params); + + const actual = mocks.logWithConsoleHasBeenCalledWith; + const expected = undefined; + + strictEqual(actual, expected); + }); + }); + + context('given the configuration "settings.logger.format" has an incorrect value', () => { + beforeEach(() => { + Config.set('settings.logger.format', 'foobar'); + }); + + it('should throw an error', () => { + const { logger } = createLogger(); + + const message = 'Hello world'; + + throws( + () => logger.debug(message), + (error: any) => error.message === '[CONFIG] Invalid "settings.logger.format" configuration value: "foobar"' + ); + }); + }); + }); + + describe('when info is called', () => { + context('given the configuration "settings.logger.format" is "dev"', () => { + beforeEach(() => { + Config.set('settings.logger.format', 'dev'); + }); + + it('should log a text with the proper color on the log level', () => { + const { logger, mocks, localTimeNow } = createLogger(); + + const message = 'Hello world'; + const params = createTestParams(); + + logger.info(message, params); + + const actual = mocks.logWithConsoleHasBeenCalledWith; + // Cyan color + const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m Hello world`; + + strictEqual(actual.split('\n')[0], expected); + }); + }); + }); + + describe('when warn is called', () => { + context('given the configuration "settings.logger.format" is "dev"', () => { + beforeEach(() => { + Config.set('settings.logger.format', 'dev'); + }); + + it('should log a text with the proper color on the log level', () => { + const { logger, mocks, localTimeNow } = createLogger(); + + const message = 'Hello world'; + const params = createTestParams(); + + logger.warn(message, params); + + const actual = mocks.logWithConsoleHasBeenCalledWith; + // Yellow color + const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[33mWARN\u001b[39m Hello world`; + + strictEqual(actual.split('\n')[0], expected); + }); + }); + }); + + describe('when error is called', () => { + context('given the configuration "settings.logger.format" is "dev"', () => { + beforeEach(() => { + Config.set('settings.logger.format', 'dev'); + }); + + it('should log a text with the proper color on the log level', () => { + const { logger, mocks, localTimeNow } = createLogger(); + + const message = 'Hello world'; + const params = createTestParams(); + + logger.error(message, params); + + const actual = mocks.logWithConsoleHasBeenCalledWith; + // Red color + const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[31mERROR\u001b[39m Hello world`; + + strictEqual(actual.split('\n')[0], expected); + }); + }); + }); +}); diff --git a/packages/core/src/common/logging/logger.ts b/packages/core/src/common/logging/logger.ts new file mode 100644 index 0000000000..f78064c4a0 --- /dev/null +++ b/packages/core/src/common/logging/logger.ts @@ -0,0 +1,170 @@ +import { Config } from '../../core'; + +type Level = 'debug'|'info'|'warn'|'error'; + +function formatObjectToJson(obj: Record): string { + if (obj.err instanceof Error) { + return JSON.stringify({ + ...obj, + err: { + name: obj.err.name, + message: obj.err.message, + stack: obj.err.stack, + }, + }); + }; + return JSON.stringify(obj); +} + +function formatObjectToTextParameters(obj: Record): string { + const tab = ' '; + const space = ' '; + const spaceSize = space.length; + + return Object + .entries(obj) + .reduce((lines, [key, value]) => { + if (value instanceof Error) { + return [ + ...lines, + `${key}: {`, + `${space}name: "${value.name}"`, + `${space}message: "${value.message}"`, + `${space}stack: ${value.stack?.split('\n').join(`\n${space}${space}`)}`, + `}`, + ]; + } + + const formattedValue = JSON.stringify(value, null, spaceSize)?.split('\n').join(`\n${tab}`); + if (formattedValue !== undefined) { + return [ + ...lines, + `${key}: ${formattedValue}`, + ]; + } + + return lines; + }, [] as string[]) + .map(str => `\n${tab}${str}`) + .join(''); +} + +export class Logger { + + private formatMessageToJson( + now: Date, + level: Level, + message: string, + params: { [name: string]: any } + ): string { + const json = { + timestamp: now, + level, + message, + ...params, + }; + + return formatObjectToJson(json); + } + + private formatMessageToRawText( + now: Date, + level: Level, + message: string, + params: { [name: string]: any } + ): string { + const timestamp = `[${now.toISOString()}]`; + const logLevel = `[${level.toUpperCase()}]`; + const parameters = formatObjectToTextParameters(params); + + return `${timestamp} ${logLevel} ${message}${parameters}`; + } + + private formatMessageToDevText( + now: Date, + level: Level, + message: string, + params: { [name: string]: any } + ): string { + const colors = { + debug: '\u001b[35m', // Pink + info: '\u001b[36m', // Cyan + warn: '\u001b[33m', // Yellow + error: '\u001b[31m', // Red + }; + const timestampColor = '\u001b[90m'; // Gray + const endTagColor = '\u001b[39m'; + + const timestamp = `${timestampColor}[${now.toLocaleTimeString()}]${endTagColor}`; + const logLevel = `${colors[level]}${level.toUpperCase()}${endTagColor}`; + const parameters = formatObjectToTextParameters({ err: params.err }); + + return `${timestamp} ${logLevel} ${message}${parameters}`; + } + + private formatMessage( + level: Level, + message: string, + params: { [name: string]: any }, + now: Date, + format: string, + ): string|null { + switch (format) { + case 'raw': + return this.formatMessageToRawText(now, level, message, params); + case 'dev': + return this.formatMessageToDevText(now, level, message, params); + case 'json': + return this.formatMessageToJson(now, level, message, params); + case 'none': + return null; + default: + throw new Error(`[CONFIG] Invalid "settings.logger.format" configuration value: "${format}"`); + } + } + + private log( + level: Level, + message: string, + params: { [name: string]: any } + ): void { + const format = Config.get('settings.logger.format', 'string', 'raw'); + const now = this.getNow(); + + const messageFormatted = this.formatMessage( + level, + message, + params, + now, + format, + ); + + if (messageFormatted !== null) { + this.logWithConsole(messageFormatted); + } + } + + protected logWithConsole(message: string): void { + console.log(message); + } + + protected getNow(): Date { + return new Date(); + } + + debug(message: string, params: { err?: Error, [name: string]: any } = {}): void { + this.log('debug', message, params); + } + + info(message: string, params: { err?: Error, [name: string]: any } = {}): void { + this.log('info', message, params); + } + + warn(message: string, params: { err?: Error, [name: string]: any } = {}): void { + this.log('warn', message, params); + } + + error(message: string, params: { err?: Error, [name: string]: any } = {}): void { + this.log('error', message, params); + } +} \ No newline at end of file diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 63d808c3c1..e341aa9eb8 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -36,6 +36,7 @@ export { hashPassword, passwordHashNeedsToBeRefreshed, isInFile, + Logger, render, renderToString, renderError, From 5fc71a05ee38eb6a4f6a1090a57b87e8dac4d568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 18 Oct 2023 10:39:47 +0200 Subject: [PATCH 03/37] Remove incorrect console.log --- packages/graphql/src/acceptance-test.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/graphql/src/acceptance-test.spec.ts b/packages/graphql/src/acceptance-test.spec.ts index 3c5bf8f643..dcc809af5e 100644 --- a/packages/graphql/src/acceptance-test.spec.ts +++ b/packages/graphql/src/acceptance-test.spec.ts @@ -244,7 +244,6 @@ describe('[Acceptance test] GraphQLController', () => { data += chunk; }); resp.on('end', () => { - console.log(data); resolve(JSON.parse(data)); }); }).on('error', err => { From 41786922d7317d1514b1214e93496e5157cf93ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 18 Oct 2023 12:27:33 +0200 Subject: [PATCH 04/37] Rename err to error --- .../core/src/common/logging/logger.spec.ts | 16 +++++++-------- packages/core/src/common/logging/logger.ts | 20 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/core/src/common/logging/logger.spec.ts b/packages/core/src/common/logging/logger.spec.ts index 89e1722cd5..1ff5f7ac6c 100644 --- a/packages/core/src/common/logging/logger.spec.ts +++ b/packages/core/src/common/logging/logger.spec.ts @@ -7,8 +7,8 @@ const testStack = `Error: aaa at Context. (/somewhere/logger.spec.ts:129:13)`; function createTestParams() { - const err = new Error('aaa'); - err.stack = testStack; + const error = new Error('aaa'); + error.stack = testStack; return { myBoolean: false, myNull: null, @@ -17,7 +17,7 @@ function createTestParams() { myString: 'xxx', mySymbol: Symbol('yyy'), myObject: { foo: 'bar' }, - err + error }; } @@ -80,7 +80,7 @@ describe('Logger', () => { + `\n myObject: {` + `\n "foo": "bar"` + `\n }` - + `\n err: {` + + `\n error: {` + `\n name: "Error"` + `\n message: "aaa"` + `\n stack: Error: aaa` @@ -114,7 +114,7 @@ describe('Logger', () => { + `\n myObject: {` + `\n "foo": "bar"` + `\n }` - + `\n err: {` + + `\n error: {` + `\n name: "Error"` + `\n message: "aaa"` + `\n stack: Error: aaa` @@ -132,7 +132,7 @@ describe('Logger', () => { Config.set('settings.logger.format', 'dev'); }); - it('should log a text with a short timestamp, with colors but with no params (except the "err" param)', () => { + it('should log a text with a short timestamp, with colors but with no params (except the "error" param)', () => { const { logger, mocks, localTimeNow } = createLogger(); const message = 'Hello world'; @@ -143,7 +143,7 @@ describe('Logger', () => { const actual = mocks.logWithConsoleHasBeenCalledWith; // Pink color const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[35mDEBUG\u001b[39m Hello world` - + `\n err: {` + + `\n error: {` + `\n name: "Error"` + `\n message: "aaa"` + `\n stack: Error: aaa` @@ -178,7 +178,7 @@ describe('Logger', () => { myNumber: 0, myString: 'xxx', myObject: { foo: 'bar' }, - err: { + error: { name: 'Error', message: 'aaa', stack: testStack, diff --git a/packages/core/src/common/logging/logger.ts b/packages/core/src/common/logging/logger.ts index f78064c4a0..0290b50f90 100644 --- a/packages/core/src/common/logging/logger.ts +++ b/packages/core/src/common/logging/logger.ts @@ -3,13 +3,13 @@ import { Config } from '../../core'; type Level = 'debug'|'info'|'warn'|'error'; function formatObjectToJson(obj: Record): string { - if (obj.err instanceof Error) { + if (obj.error instanceof Error) { return JSON.stringify({ ...obj, - err: { - name: obj.err.name, - message: obj.err.message, - stack: obj.err.stack, + error: { + name: obj.error.name, + message: obj.error.message, + stack: obj.error.stack, }, }); }; @@ -97,7 +97,7 @@ export class Logger { const timestamp = `${timestampColor}[${now.toLocaleTimeString()}]${endTagColor}`; const logLevel = `${colors[level]}${level.toUpperCase()}${endTagColor}`; - const parameters = formatObjectToTextParameters({ err: params.err }); + const parameters = formatObjectToTextParameters({ error: params.error }); return `${timestamp} ${logLevel} ${message}${parameters}`; } @@ -152,19 +152,19 @@ export class Logger { return new Date(); } - debug(message: string, params: { err?: Error, [name: string]: any } = {}): void { + debug(message: string, params: { error?: Error, [name: string]: any } = {}): void { this.log('debug', message, params); } - info(message: string, params: { err?: Error, [name: string]: any } = {}): void { + info(message: string, params: { error?: Error, [name: string]: any } = {}): void { this.log('info', message, params); } - warn(message: string, params: { err?: Error, [name: string]: any } = {}): void { + warn(message: string, params: { error?: Error, [name: string]: any } = {}): void { this.log('warn', message, params); } - error(message: string, params: { err?: Error, [name: string]: any } = {}): void { + error(message: string, params: { error?: Error, [name: string]: any } = {}): void { this.log('error', message, params); } } \ No newline at end of file From 9d3e72fa0432cc5aa093be70e30e302339ec341a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 18 Oct 2023 12:28:50 +0200 Subject: [PATCH 05/37] Remove useless try/catch --- .../additional/authentication/jwt.jwks.spec.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/acceptance-tests/src/additional/authentication/jwt.jwks.spec.ts b/packages/acceptance-tests/src/additional/authentication/jwt.jwks.spec.ts index 76a5009cc3..0198edecbf 100644 --- a/packages/acceptance-tests/src/additional/authentication/jwt.jwks.spec.ts +++ b/packages/acceptance-tests/src/additional/authentication/jwt.jwks.spec.ts @@ -68,17 +68,13 @@ describe('[Authentication|JWT|JWKS] Users can be authenticated with a JWKS retre server = (await createApp(AppController)).listen(3000); - try { - const response = await superagent - .get('http://localhost:3000/api/users/me') - .set('Authorization', 'Bearer ' + sign({}, privateKey, { algorithm: 'RS256', header: { kid: 'aaa' } })); - deepStrictEqual(response.body, { - name: 'Alix' - }); - } catch (error: any) { - console.log(error); - throw error; - } + const response = await superagent + .get('http://localhost:3000/api/users/me') + .set('Authorization', 'Bearer ' + sign({}, privateKey, { algorithm: 'RS256', header: { kid: 'aaa' } })); + + deepStrictEqual(response.body, { + name: 'Alix' + }); }); }); From 5ed120300d126b562fe1b90c299f99176509be06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 18 Oct 2023 12:56:25 +0200 Subject: [PATCH 06/37] Remove unused variable --- packages/core/src/common/logging/logger.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/common/logging/logger.spec.ts b/packages/core/src/common/logging/logger.spec.ts index 1ff5f7ac6c..7430fbee35 100644 --- a/packages/core/src/common/logging/logger.spec.ts +++ b/packages/core/src/common/logging/logger.spec.ts @@ -195,7 +195,7 @@ describe('Logger', () => { }); it('should log nothing', () => { - const { logger, mocks, isoNow } = createLogger(); + const { logger, mocks } = createLogger(); const message = 'Hello world'; const params = createTestParams(); From 0f0a02c20b4ce180a12a81e76f36710a680456f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 18 Oct 2023 13:04:17 +0200 Subject: [PATCH 07/37] Use Foal logger when an error is thrown in HTTP controller --- .../routes/convert-error-to-response.spec.ts | 42 +++++++++++++------ .../core/routes/convert-error-to-response.ts | 9 +++- packages/core/src/core/routes/get-response.ts | 11 +++-- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/packages/core/src/core/routes/convert-error-to-response.spec.ts b/packages/core/src/core/routes/convert-error-to-response.spec.ts index fe3ba303c3..b6a2f9899b 100644 --- a/packages/core/src/core/routes/convert-error-to-response.spec.ts +++ b/packages/core/src/core/routes/convert-error-to-response.spec.ts @@ -22,12 +22,20 @@ describe('convertErrorToResponse', () => { context('given the configuration settings.logErrors is true or not defined', () => { it('should log the error stack.', async () => { - let message = ''; - const log = (msg: string) => message = msg; + let actualMessage: string|undefined; + let actualError: Error|undefined; - await convertErrorToResponse(error, ctx, new AppController(), log); + const logger = { + error(message: string, { error }: { error: Error }): void { + actualMessage = message; + actualError = error; + } + } + + await convertErrorToResponse(error, ctx, new AppController(), logger); - strictEqual(message, error.stack); + strictEqual(actualMessage, error.message); + strictEqual(actualError, error); }); }); @@ -38,13 +46,21 @@ describe('convertErrorToResponse', () => { afterEach(() => Config.remove('settings.logErrors')); - it('should not log the error stack.', async () => { - let message = ''; - const log = (msg: string) => message = msg; + it('should not log the error.', async () => { + let actualMessage: string|undefined; + let actualError: Error|undefined; + + const logger = { + error(message: string, { error }: { error: Error }): void { + actualMessage = message; + actualError = error; + } + } - await convertErrorToResponse(error, ctx, new AppController(), log); + await convertErrorToResponse(error, ctx, new AppController(), logger); - strictEqual(message, ''); + strictEqual(actualMessage, undefined); + strictEqual(actualError, undefined); }); }); @@ -52,7 +68,7 @@ describe('convertErrorToResponse', () => { context('given appController.handleError is not defined', () => { it('should return an HttpResponseInternalServerError object created by renderError.', async () => { - const response = await convertErrorToResponse(error, ctx, new AppController(), () => {}); + const response = await convertErrorToResponse(error, ctx, new AppController(), { error: () => {} }); if (!isHttpResponseInternalServerError(response)) { throw new Error('An HttpResponseInternalServerError should have been returned.'); @@ -82,7 +98,7 @@ describe('convertErrorToResponse', () => { } it('should return the response returned by handleError.', async () => { - const response = await convertErrorToResponse(error, ctx, new AppController(), () => {}); + const response = await convertErrorToResponse(error, ctx, new AppController(), { error: () => {} }); if (!isHttpResponseInternalServerError(response)) { throw new Error('An HttpResponseInternalServerError should have been returned.'); @@ -102,7 +118,7 @@ describe('convertErrorToResponse', () => { } it('should return an HttpResponseInternalServerError object created by renderError.', async () => { - const response = await convertErrorToResponse(error, ctx, new AppController(), () => {}); + const response = await convertErrorToResponse(error, ctx, new AppController(), { error: () => {} }); if (!isHttpResponseInternalServerError(response)) { throw new Error('An HttpResponseInternalServerError should have been returned.'); @@ -123,7 +139,7 @@ describe('convertErrorToResponse', () => { } it('should return an HttpResponseInternalServerError object created by renderError.', async () => { - const response = await convertErrorToResponse(error, ctx, new AppController(), () => {}); + const response = await convertErrorToResponse(error, ctx, new AppController(), { error: () => {} }); if (!isHttpResponseInternalServerError(response)) { throw new Error('An HttpResponseInternalServerError should have been returned.'); diff --git a/packages/core/src/core/routes/convert-error-to-response.ts b/packages/core/src/core/routes/convert-error-to-response.ts index 04311fd7d2..27ee838057 100644 --- a/packages/core/src/core/routes/convert-error-to-response.ts +++ b/packages/core/src/core/routes/convert-error-to-response.ts @@ -4,10 +4,15 @@ import { Config } from '../config'; import { Context, HttpResponse } from '../http'; export async function convertErrorToResponse( - error: Error, ctx: Context, appController: IAppController, log = console.error + error: Error, + ctx: Context, + appController: IAppController, + logger: { + error: (message: string, params: { error: Error }) => void + } ): Promise { if (Config.get('settings.logErrors', 'boolean', true)) { - log(error.stack); + logger.error(error.message, { error }); } if (appController.handleError) { diff --git a/packages/core/src/core/routes/get-response.ts b/packages/core/src/core/routes/get-response.ts index cb4da5b6eb..dcbb35aa10 100644 --- a/packages/core/src/core/routes/get-response.ts +++ b/packages/core/src/core/routes/get-response.ts @@ -1,3 +1,4 @@ +import { Logger } from '../../common'; import { IAppController } from '../app.controller.interface'; import { HookPostFunction } from '../hooks'; import { Context, HttpResponse, isHttpResponse } from '../http'; @@ -8,6 +9,8 @@ import { Route } from './route.interface'; export async function getResponse( route: Route, ctx: Context, services: ServiceManager, appController: IAppController ): Promise { + const logger = services.get(Logger); + let response: undefined | HttpResponse; const hookPostFunctions: HookPostFunction[] = []; @@ -17,7 +20,7 @@ export async function getResponse( try { result = await hook(ctx, services); } catch (error: any) { - result = await convertErrorToResponse(error, ctx, appController); + result = await convertErrorToResponse(error, ctx, appController, logger); } if (isHttpResponse(result)) { response = result; @@ -31,20 +34,20 @@ export async function getResponse( try { response = await route.controller[route.propertyKey](ctx, ctx.request.params, ctx.request.body); } catch (error: any) { - response = await convertErrorToResponse(error, ctx, appController); + response = await convertErrorToResponse(error, ctx, appController, logger); } } if (!isHttpResponse(response)) { const error = new Error(`The controller method "${route.propertyKey}" should return an HttpResponse.`); - response = await convertErrorToResponse(error, ctx, appController); + response = await convertErrorToResponse(error, ctx, appController, logger); } for (const postFn of hookPostFunctions) { try { await postFn(response); } catch (error: any) { - response = await convertErrorToResponse(error, ctx, appController); + response = await convertErrorToResponse(error, ctx, appController, logger); } } From 5f5c60941b13f1d6aee949d7c7a4b3916269adc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 18 Oct 2023 13:07:38 +0200 Subject: [PATCH 08/37] Use Foal logger when an error is thrown in WS controller --- ...onvert-error-to-websocket-response.spec.ts | 39 +++++++++++++------ .../convert-error-to-websocket-response.ts | 9 ++++- .../src/routes/get-websocket-response.ts | 12 +++--- 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/packages/socket.io/src/errors/convert-error-to-websocket-response.spec.ts b/packages/socket.io/src/errors/convert-error-to-websocket-response.spec.ts index f3cfb36283..b3ed99804f 100644 --- a/packages/socket.io/src/errors/convert-error-to-websocket-response.spec.ts +++ b/packages/socket.io/src/errors/convert-error-to-websocket-response.spec.ts @@ -23,12 +23,20 @@ describe('convertErrorToWebsocketResponse', () => { context('given the configuration settings.logErrors is true or not defined', () => { it('should log the error stack.', async () => { - let message = ''; - const log = (msg: string) => message = msg; + let actualMessage: string|undefined; + let actualError: Error|undefined; - await convertErrorToWebsocketResponse(error, ctx, new SocketIOController(), log); + const logger = { + error(message: string, { error }: { error: Error }): void { + actualMessage = message; + actualError = error; + } + } + + await convertErrorToWebsocketResponse(error, ctx, new SocketIOController(), logger); - strictEqual(message, error.stack); + strictEqual(actualMessage, error.message); + strictEqual(actualError, error); }); }); @@ -40,12 +48,19 @@ describe('convertErrorToWebsocketResponse', () => { afterEach(() => Config.remove('settings.logErrors')); it('should not log the error stack.', async () => { - let message = ''; - const log = (msg: string) => message = msg; + let actualMessage: string|undefined; + let actualError: Error|undefined; - await convertErrorToWebsocketResponse(error, ctx, new SocketIOController(), log); + const logger = { + error(message: string, { error }: { error: Error }): void { + actualMessage = message; + actualError = error; + } + } + await convertErrorToWebsocketResponse(error, ctx, new SocketIOController(), logger); - strictEqual(message, ''); + strictEqual(actualMessage, undefined); + strictEqual(actualError, undefined); }); }); @@ -53,7 +68,7 @@ describe('convertErrorToWebsocketResponse', () => { context('given socketioController.handleError is not defined', () => { it('should return an WebsocketErrorResponse object created by renderWebsocketError.', async () => { - const response = await convertErrorToWebsocketResponse(error, ctx, new SocketIOController(), () => {}); + const response = await convertErrorToWebsocketResponse(error, ctx, new SocketIOController(), { error: () => {} }); if (!(response instanceof WebsocketErrorResponse)) { throw new Error('An WebsocketErrorResponse should have been returned.'); @@ -83,7 +98,7 @@ describe('convertErrorToWebsocketResponse', () => { } it('should return the response returned by handleError.', async () => { - const response = await convertErrorToWebsocketResponse(error, ctx, new SocketIOController(), () => {}); + const response = await convertErrorToWebsocketResponse(error, ctx, new SocketIOController(), { error: () => {} }); if (!(response instanceof WebsocketErrorResponse)) { throw new Error('An WebsocketErrorResponse should have been returned.'); @@ -103,7 +118,7 @@ describe('convertErrorToWebsocketResponse', () => { } it('should return an WebsocketErrorResponse object created by renderWebsocketError.', async () => { - const response = await convertErrorToWebsocketResponse(error, ctx, new SocketIOController(), () => {}); + const response = await convertErrorToWebsocketResponse(error, ctx, new SocketIOController(), { error: () => {} }); if (!(response instanceof WebsocketErrorResponse)) { throw new Error('An WebsocketErrorResponse should have been returned.'); @@ -124,7 +139,7 @@ describe('convertErrorToWebsocketResponse', () => { } it('should return an WebsocketErrorResponse object created by renderWebsocketError.', async () => { - const response = await convertErrorToWebsocketResponse(error, ctx, new SocketIOController(), () => {}); + const response = await convertErrorToWebsocketResponse(error, ctx, new SocketIOController(), { error: () => {} }); if (!(response instanceof WebsocketErrorResponse)) { throw new Error('An WebsocketErrorResponse should have been returned.'); diff --git a/packages/socket.io/src/errors/convert-error-to-websocket-response.ts b/packages/socket.io/src/errors/convert-error-to-websocket-response.ts index d15c568ad1..06f5e07242 100644 --- a/packages/socket.io/src/errors/convert-error-to-websocket-response.ts +++ b/packages/socket.io/src/errors/convert-error-to-websocket-response.ts @@ -6,10 +6,15 @@ import { ISocketIOController, WebsocketContext, WebsocketErrorResponse, Websocke import { renderWebsocketError } from './render-websocket-error'; export async function convertErrorToWebsocketResponse( - error: Error, ctx: WebsocketContext, socketIOController: ISocketIOController, log = console.error + error: Error, + ctx: WebsocketContext, + socketIOController: ISocketIOController, + logger: { + error: (message: string, params: { error: Error }) => void + } ): Promise { if (Config.get('settings.logErrors', 'boolean', true)) { - log(error.stack); + logger.error(error.message, { error }); } if (socketIOController.handleError) { diff --git a/packages/socket.io/src/routes/get-websocket-response.ts b/packages/socket.io/src/routes/get-websocket-response.ts index 644bf17c03..21b66f9fb0 100644 --- a/packages/socket.io/src/routes/get-websocket-response.ts +++ b/packages/socket.io/src/routes/get-websocket-response.ts @@ -1,5 +1,5 @@ // 3p -import { ServiceManager } from '@foal/core'; +import { Logger, ServiceManager } from '@foal/core'; // FoalTS import { @@ -15,6 +15,8 @@ import { convertErrorToWebsocketResponse } from '../errors'; export async function getWebsocketResponse( route: WebsocketRoute, ctx: WebsocketContext, services: ServiceManager, socketIOController: ISocketIOController ): Promise { + const logger = services.get(Logger); + let response: undefined | WebsocketResponse | WebsocketErrorResponse; const hookPostFunctions: WebsocketHookPostFunction[] = []; @@ -24,7 +26,7 @@ export async function getWebsocketResponse( try { result = await hook(ctx, services); } catch (error: any) { - result = await convertErrorToWebsocketResponse(error, ctx, socketIOController); + result = await convertErrorToWebsocketResponse(error, ctx, socketIOController, logger); } if ((result instanceof WebsocketResponse) || (result instanceof WebsocketErrorResponse)) { response = result; @@ -38,20 +40,20 @@ export async function getWebsocketResponse( try { response = await route.controller[route.propertyKey](ctx, ctx.payload); } catch (error: any) { - response = await convertErrorToWebsocketResponse(error, ctx, socketIOController); + response = await convertErrorToWebsocketResponse(error, ctx, socketIOController, logger); } } if (!((response instanceof WebsocketResponse) || (response instanceof WebsocketErrorResponse))) { const error = new Error(`The controller method "${route.propertyKey}" should return a WebsocketResponse or a WebsocketErrorResponse.`); - response = await convertErrorToWebsocketResponse(error, ctx, socketIOController); + response = await convertErrorToWebsocketResponse(error, ctx, socketIOController, logger); } for (const postFn of hookPostFunctions) { try { await postFn(response); } catch (error: any) { - response = await convertErrorToWebsocketResponse(error, ctx, socketIOController); + response = await convertErrorToWebsocketResponse(error, ctx, socketIOController, logger); } } From 40c7f2357933787ac65ceb295b95bc70835da6a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 18 Oct 2023 13:12:29 +0200 Subject: [PATCH 09/37] Use Foal logger when a stream error is thrown --- packages/core/src/express/create-app.ts | 10 +++++++--- packages/core/src/express/send-response.spec.ts | 2 +- packages/core/src/express/send-response.ts | 14 +++++++++++--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/core/src/express/create-app.ts b/packages/core/src/express/create-app.ts index 5a3c9f6e95..01562235d3 100644 --- a/packages/core/src/express/create-app.ts +++ b/packages/core/src/express/create-app.ts @@ -1,7 +1,7 @@ // 3p import * as cookieParser from 'cookie-parser'; import * as express from 'express'; -import * as logger from 'morgan'; +import * as morgan from 'morgan'; // FoalTS import { @@ -15,6 +15,7 @@ import { ServiceManager, } from '../core'; import { sendResponse } from './send-response'; +import { Logger } from '../common'; export const OPENAPI_SERVICE_ID = 'OPENAPI_SERVICE_ID_a5NWKbBNBxVVZ'; @@ -86,7 +87,7 @@ export async function createApp( '[:date] ":method :url HTTP/:http-version" :status - :response-time ms' ); if (loggerFormat !== 'none') { - app.use(logger(loggerFormat)); + app.use(morgan(loggerFormat)); } app.use(protectionHeaders); @@ -116,6 +117,9 @@ export async function createApp( const services = options.serviceManager || new ServiceManager(); app.foal = { services }; + // Retrieve the logger. + const logger = services.get(Logger); + // Inject the OpenAPI service with an ID string to avoid duplicated singletons // across several npm packages. services.set(OPENAPI_SERVICE_ID, services.get(OpenApi)); @@ -131,7 +135,7 @@ export async function createApp( const ctx = new Context(req, route.controller.constructor.name, route.propertyKey); // TODO: better test this line. const response = await getResponse(route, ctx, services, appController); - sendResponse(response, res); + sendResponse(response, res, logger); } catch (error: any) { // This try/catch will never be called: the `getResponse` function catches any errors // thrown or rejected in the application and converts it into a response. diff --git a/packages/core/src/express/send-response.spec.ts b/packages/core/src/express/send-response.spec.ts index 0ba321c7cb..06cee9f07e 100644 --- a/packages/core/src/express/send-response.spec.ts +++ b/packages/core/src/express/send-response.spec.ts @@ -16,7 +16,7 @@ import { sendResponse } from './send-response'; function execSendResponse(response: HttpResponse): request.Test { const app = express() - .use((req: any, res: any) => sendResponse(response, res)); + .use((req: any, res: any) => sendResponse(response, res, { error: () => {} })); return request(app).get('/'); } diff --git a/packages/core/src/express/send-response.ts b/packages/core/src/express/send-response.ts index 7678bcff9f..7585c7b526 100644 --- a/packages/core/src/express/send-response.ts +++ b/packages/core/src/express/send-response.ts @@ -12,7 +12,13 @@ import { HttpResponse, isHttpResponseMovedPermanently, isHttpResponseRedirect } * @param {any} res - Express response used in middlewares. * @returns {void} */ -export function sendResponse(response: HttpResponse, res: any): void { +export function sendResponse( + response: HttpResponse, + res: any, + logger: { + error: (message: string, params: { error: Error }) => void + } +): void { res.status(response.statusCode); res.set(response.getHeaders()); const cookies = response.getCookies(); @@ -35,8 +41,10 @@ export function sendResponse(response: HttpResponse, res: any): void { } if (response.stream === true) { - pipeline(response.body, res, (err: any) => { - if (err) { console.log(err); } + pipeline(response.body, res, (error: any) => { + if (error) { + logger.error(error.message, { error }); + } }); return; } From fcf298ff379e0618fdc996a86a788ff94238678b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 18 Oct 2023 13:16:59 +0200 Subject: [PATCH 10/37] [CLI] Configure Foal logger for different environments --- .../cli/src/generate/specs/app/config/development.json | 7 +++++-- packages/cli/src/generate/specs/app/config/development.yml | 4 +++- packages/cli/src/generate/specs/app/config/e2e.json | 7 +++++-- .../cli/src/generate/specs/app/config/e2e.mongodb.json | 5 ++++- packages/cli/src/generate/specs/app/config/e2e.mongodb.yml | 2 ++ packages/cli/src/generate/specs/app/config/e2e.yml | 2 ++ .../cli/src/generate/templates/app/config/development.json | 7 +++++-- .../cli/src/generate/templates/app/config/development.yml | 4 +++- packages/cli/src/generate/templates/app/config/e2e.json | 7 +++++-- .../cli/src/generate/templates/app/config/e2e.mongodb.json | 5 ++++- .../cli/src/generate/templates/app/config/e2e.mongodb.yml | 2 ++ packages/cli/src/generate/templates/app/config/e2e.yml | 2 ++ 12 files changed, 42 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/generate/specs/app/config/development.json b/packages/cli/src/generate/specs/app/config/development.json index 11141fbb0f..a9481b4c2e 100644 --- a/packages/cli/src/generate/specs/app/config/development.json +++ b/packages/cli/src/generate/specs/app/config/development.json @@ -1,6 +1,9 @@ { "settings": { "debug": true, - "loggerFormat": "dev" + "loggerFormat": "dev", + "logger": { + "format": "dev" + } } -} +} \ No newline at end of file diff --git a/packages/cli/src/generate/specs/app/config/development.yml b/packages/cli/src/generate/specs/app/config/development.yml index b7f97731c5..fb65913555 100644 --- a/packages/cli/src/generate/specs/app/config/development.yml +++ b/packages/cli/src/generate/specs/app/config/development.yml @@ -1,3 +1,5 @@ settings: debug: true - loggerFormat: dev \ No newline at end of file + loggerFormat: dev + logger: + format: dev \ No newline at end of file diff --git a/packages/cli/src/generate/specs/app/config/e2e.json b/packages/cli/src/generate/specs/app/config/e2e.json index ed22ed8551..052e0449c5 100644 --- a/packages/cli/src/generate/specs/app/config/e2e.json +++ b/packages/cli/src/generate/specs/app/config/e2e.json @@ -1,10 +1,13 @@ { "settings": { - "loggerFormat": "none" + "loggerFormat": "none", + "logger": { + "format": "none" + } }, "database": { "database": "./e2e_db.sqlite3", "dropSchema": true, "synchronize": true } -} +} \ No newline at end of file diff --git a/packages/cli/src/generate/specs/app/config/e2e.mongodb.json b/packages/cli/src/generate/specs/app/config/e2e.mongodb.json index 0557ec9654..7de247fad9 100644 --- a/packages/cli/src/generate/specs/app/config/e2e.mongodb.json +++ b/packages/cli/src/generate/specs/app/config/e2e.mongodb.json @@ -1,6 +1,9 @@ { "settings": { - "loggerFormat": "none" + "loggerFormat": "none", + "logger": { + "format": "none" + } }, "database": { "url": "mongodb://localhost:27017/e2e-test-foo-bar" diff --git a/packages/cli/src/generate/specs/app/config/e2e.mongodb.yml b/packages/cli/src/generate/specs/app/config/e2e.mongodb.yml index 281daa0052..7bc7bb3448 100644 --- a/packages/cli/src/generate/specs/app/config/e2e.mongodb.yml +++ b/packages/cli/src/generate/specs/app/config/e2e.mongodb.yml @@ -1,5 +1,7 @@ settings: loggerFormat: 'none' + logger: + format: 'none' database: url: mongodb://localhost:27017/e2e-test-foo-bar diff --git a/packages/cli/src/generate/specs/app/config/e2e.yml b/packages/cli/src/generate/specs/app/config/e2e.yml index 06a4add77f..60f92f0c56 100644 --- a/packages/cli/src/generate/specs/app/config/e2e.yml +++ b/packages/cli/src/generate/specs/app/config/e2e.yml @@ -1,5 +1,7 @@ settings: loggerFormat: 'none' + logger: + format: 'none' database: database: './e2e_db.sqlite3' diff --git a/packages/cli/src/generate/templates/app/config/development.json b/packages/cli/src/generate/templates/app/config/development.json index 11141fbb0f..a9481b4c2e 100644 --- a/packages/cli/src/generate/templates/app/config/development.json +++ b/packages/cli/src/generate/templates/app/config/development.json @@ -1,6 +1,9 @@ { "settings": { "debug": true, - "loggerFormat": "dev" + "loggerFormat": "dev", + "logger": { + "format": "dev" + } } -} +} \ No newline at end of file diff --git a/packages/cli/src/generate/templates/app/config/development.yml b/packages/cli/src/generate/templates/app/config/development.yml index b7f97731c5..c4ec70c649 100644 --- a/packages/cli/src/generate/templates/app/config/development.yml +++ b/packages/cli/src/generate/templates/app/config/development.yml @@ -1,3 +1,5 @@ settings: debug: true - loggerFormat: dev \ No newline at end of file + loggerFormat: dev + logger: + format: dev \ No newline at end of file diff --git a/packages/cli/src/generate/templates/app/config/e2e.json b/packages/cli/src/generate/templates/app/config/e2e.json index ed22ed8551..052e0449c5 100644 --- a/packages/cli/src/generate/templates/app/config/e2e.json +++ b/packages/cli/src/generate/templates/app/config/e2e.json @@ -1,10 +1,13 @@ { "settings": { - "loggerFormat": "none" + "loggerFormat": "none", + "logger": { + "format": "none" + } }, "database": { "database": "./e2e_db.sqlite3", "dropSchema": true, "synchronize": true } -} +} \ No newline at end of file diff --git a/packages/cli/src/generate/templates/app/config/e2e.mongodb.json b/packages/cli/src/generate/templates/app/config/e2e.mongodb.json index 0526879660..6ad1e7e7e4 100644 --- a/packages/cli/src/generate/templates/app/config/e2e.mongodb.json +++ b/packages/cli/src/generate/templates/app/config/e2e.mongodb.json @@ -1,6 +1,9 @@ { "settings": { - "loggerFormat": "none" + "loggerFormat": "none", + "logger": { + "format": "none" + } }, "database": { "url": "mongodb://localhost:27017/e2e-/* kebabName */" diff --git a/packages/cli/src/generate/templates/app/config/e2e.mongodb.yml b/packages/cli/src/generate/templates/app/config/e2e.mongodb.yml index f3ea49f64c..609d06f307 100644 --- a/packages/cli/src/generate/templates/app/config/e2e.mongodb.yml +++ b/packages/cli/src/generate/templates/app/config/e2e.mongodb.yml @@ -1,5 +1,7 @@ settings: loggerFormat: 'none' + logger: + format: 'none' database: url: mongodb://localhost:27017/e2e-/* kebabName */ diff --git a/packages/cli/src/generate/templates/app/config/e2e.yml b/packages/cli/src/generate/templates/app/config/e2e.yml index 06a4add77f..60f92f0c56 100644 --- a/packages/cli/src/generate/templates/app/config/e2e.yml +++ b/packages/cli/src/generate/templates/app/config/e2e.yml @@ -1,5 +1,7 @@ settings: loggerFormat: 'none' + logger: + format: 'none' database: database: './e2e_db.sqlite3' From 5d797ca19744590401007c061c5dc9077a9f67df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 18 Oct 2023 13:18:50 +0200 Subject: [PATCH 11/37] Use Foal logger in examples --- packages/examples/config/default.yml | 2 -- packages/examples/config/development.yml | 5 +++++ .../examples/src/app/controllers/profile.controller.ts | 7 +++++-- 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 packages/examples/config/development.yml diff --git a/packages/examples/config/default.yml b/packages/examples/config/default.yml index 304432adae..ee139f9d52 100644 --- a/packages/examples/config/default.yml +++ b/packages/examples/config/default.yml @@ -3,8 +3,6 @@ port: 3001 settings: openapi: useHooks: true - debug: true - loggerFormat: dev session: secret: 'my secret' staticPath: 'public/' diff --git a/packages/examples/config/development.yml b/packages/examples/config/development.yml new file mode 100644 index 0000000000..c4ec70c649 --- /dev/null +++ b/packages/examples/config/development.yml @@ -0,0 +1,5 @@ +settings: + debug: true + loggerFormat: dev + logger: + format: dev \ No newline at end of file diff --git a/packages/examples/src/app/controllers/profile.controller.ts b/packages/examples/src/app/controllers/profile.controller.ts index e8e9a34034..21e21eeb98 100644 --- a/packages/examples/src/app/controllers/profile.controller.ts +++ b/packages/examples/src/app/controllers/profile.controller.ts @@ -1,5 +1,5 @@ import { - ApiInfo, ApiServer, Context, dependency, Get, Hook, HttpResponseNotFound, HttpResponseRedirect, Post, render, UserRequired + ApiInfo, ApiServer, Context, dependency, Get, Hook, HttpResponseNotFound, HttpResponseRedirect, Logger, Post, render, UserRequired } from '@foal/core'; import { Disk, ParseAndValidateFiles } from '@foal/storage'; @@ -16,6 +16,9 @@ export class ProfileController { @dependency disk: Disk; + @dependency + logger: Logger; + @Post('/image') @Hook(async ctx => { ctx.user = await User.findOneBy({ email: 'john@foalts.org' }); }) @UserRequired() @@ -28,7 +31,7 @@ export class ProfileController { try { await this.disk.delete(user.profile); } catch (error: any) { - console.log(error.message); + this.logger.error(error.message, { err: error }); } } From d5b40764e6cd925a10d8de4a45cfbe675f786f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Wed, 18 Oct 2023 22:28:29 +0200 Subject: [PATCH 12/37] [CLI] Fix template --- packages/cli/src/generate/specs/app/config/development.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/generate/specs/app/config/development.yml b/packages/cli/src/generate/specs/app/config/development.yml index fb65913555..c4ec70c649 100644 --- a/packages/cli/src/generate/specs/app/config/development.yml +++ b/packages/cli/src/generate/specs/app/config/development.yml @@ -1,5 +1,5 @@ settings: debug: true loggerFormat: dev - logger: + logger: format: dev \ No newline at end of file From 158170db4eeb03997486d40c5bb9a026cd4c6e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 19 Oct 2023 00:34:40 +0200 Subject: [PATCH 13/37] Move formatMessage to logger.utils --- .../core/src/common/logging/logger.spec.ts | 71 +------- packages/core/src/common/logging/logger.ts | 126 +------------ .../src/common/logging/logger.utils.spec.ts | 169 ++++++++++++++++++ .../core/src/common/logging/logger.utils.ts | 108 +++++++++++ 4 files changed, 281 insertions(+), 193 deletions(-) create mode 100644 packages/core/src/common/logging/logger.utils.spec.ts create mode 100644 packages/core/src/common/logging/logger.utils.ts diff --git a/packages/core/src/common/logging/logger.spec.ts b/packages/core/src/common/logging/logger.spec.ts index 7430fbee35..222052d8ab 100644 --- a/packages/core/src/common/logging/logger.spec.ts +++ b/packages/core/src/common/logging/logger.spec.ts @@ -221,78 +221,9 @@ describe('Logger', () => { throws( () => logger.debug(message), - (error: any) => error.message === '[CONFIG] Invalid "settings.logger.format" configuration value: "foobar"' + (error: any) => error.message === 'Invalid logging format: "foobar"' ); }); }); }); - - describe('when info is called', () => { - context('given the configuration "settings.logger.format" is "dev"', () => { - beforeEach(() => { - Config.set('settings.logger.format', 'dev'); - }); - - it('should log a text with the proper color on the log level', () => { - const { logger, mocks, localTimeNow } = createLogger(); - - const message = 'Hello world'; - const params = createTestParams(); - - logger.info(message, params); - - const actual = mocks.logWithConsoleHasBeenCalledWith; - // Cyan color - const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m Hello world`; - - strictEqual(actual.split('\n')[0], expected); - }); - }); - }); - - describe('when warn is called', () => { - context('given the configuration "settings.logger.format" is "dev"', () => { - beforeEach(() => { - Config.set('settings.logger.format', 'dev'); - }); - - it('should log a text with the proper color on the log level', () => { - const { logger, mocks, localTimeNow } = createLogger(); - - const message = 'Hello world'; - const params = createTestParams(); - - logger.warn(message, params); - - const actual = mocks.logWithConsoleHasBeenCalledWith; - // Yellow color - const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[33mWARN\u001b[39m Hello world`; - - strictEqual(actual.split('\n')[0], expected); - }); - }); - }); - - describe('when error is called', () => { - context('given the configuration "settings.logger.format" is "dev"', () => { - beforeEach(() => { - Config.set('settings.logger.format', 'dev'); - }); - - it('should log a text with the proper color on the log level', () => { - const { logger, mocks, localTimeNow } = createLogger(); - - const message = 'Hello world'; - const params = createTestParams(); - - logger.error(message, params); - - const actual = mocks.logWithConsoleHasBeenCalledWith; - // Red color - const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[31mERROR\u001b[39m Hello world`; - - strictEqual(actual.split('\n')[0], expected); - }); - }); - }); }); diff --git a/packages/core/src/common/logging/logger.ts b/packages/core/src/common/logging/logger.ts index 0290b50f90..7517f62cf8 100644 --- a/packages/core/src/common/logging/logger.ts +++ b/packages/core/src/common/logging/logger.ts @@ -1,128 +1,8 @@ import { Config } from '../../core'; - -type Level = 'debug'|'info'|'warn'|'error'; - -function formatObjectToJson(obj: Record): string { - if (obj.error instanceof Error) { - return JSON.stringify({ - ...obj, - error: { - name: obj.error.name, - message: obj.error.message, - stack: obj.error.stack, - }, - }); - }; - return JSON.stringify(obj); -} - -function formatObjectToTextParameters(obj: Record): string { - const tab = ' '; - const space = ' '; - const spaceSize = space.length; - - return Object - .entries(obj) - .reduce((lines, [key, value]) => { - if (value instanceof Error) { - return [ - ...lines, - `${key}: {`, - `${space}name: "${value.name}"`, - `${space}message: "${value.message}"`, - `${space}stack: ${value.stack?.split('\n').join(`\n${space}${space}`)}`, - `}`, - ]; - } - - const formattedValue = JSON.stringify(value, null, spaceSize)?.split('\n').join(`\n${tab}`); - if (formattedValue !== undefined) { - return [ - ...lines, - `${key}: ${formattedValue}`, - ]; - } - - return lines; - }, [] as string[]) - .map(str => `\n${tab}${str}`) - .join(''); -} +import { Level, formatMessage } from './logger.utils'; export class Logger { - private formatMessageToJson( - now: Date, - level: Level, - message: string, - params: { [name: string]: any } - ): string { - const json = { - timestamp: now, - level, - message, - ...params, - }; - - return formatObjectToJson(json); - } - - private formatMessageToRawText( - now: Date, - level: Level, - message: string, - params: { [name: string]: any } - ): string { - const timestamp = `[${now.toISOString()}]`; - const logLevel = `[${level.toUpperCase()}]`; - const parameters = formatObjectToTextParameters(params); - - return `${timestamp} ${logLevel} ${message}${parameters}`; - } - - private formatMessageToDevText( - now: Date, - level: Level, - message: string, - params: { [name: string]: any } - ): string { - const colors = { - debug: '\u001b[35m', // Pink - info: '\u001b[36m', // Cyan - warn: '\u001b[33m', // Yellow - error: '\u001b[31m', // Red - }; - const timestampColor = '\u001b[90m'; // Gray - const endTagColor = '\u001b[39m'; - - const timestamp = `${timestampColor}[${now.toLocaleTimeString()}]${endTagColor}`; - const logLevel = `${colors[level]}${level.toUpperCase()}${endTagColor}`; - const parameters = formatObjectToTextParameters({ error: params.error }); - - return `${timestamp} ${logLevel} ${message}${parameters}`; - } - - private formatMessage( - level: Level, - message: string, - params: { [name: string]: any }, - now: Date, - format: string, - ): string|null { - switch (format) { - case 'raw': - return this.formatMessageToRawText(now, level, message, params); - case 'dev': - return this.formatMessageToDevText(now, level, message, params); - case 'json': - return this.formatMessageToJson(now, level, message, params); - case 'none': - return null; - default: - throw new Error(`[CONFIG] Invalid "settings.logger.format" configuration value: "${format}"`); - } - } - private log( level: Level, message: string, @@ -131,12 +11,12 @@ export class Logger { const format = Config.get('settings.logger.format', 'string', 'raw'); const now = this.getNow(); - const messageFormatted = this.formatMessage( + const messageFormatted = formatMessage( level, message, params, - now, format, + now, ); if (messageFormatted !== null) { diff --git a/packages/core/src/common/logging/logger.utils.spec.ts b/packages/core/src/common/logging/logger.utils.spec.ts new file mode 100644 index 0000000000..9252b9a957 --- /dev/null +++ b/packages/core/src/common/logging/logger.utils.spec.ts @@ -0,0 +1,169 @@ +import { deepStrictEqual, strictEqual, throws } from 'assert'; +import { formatMessage } from './logger.utils'; + +const testStack = `Error: aaa + at createTestParams (/somewhere/logger.spec.ts:6:11) + at Context. (/somewhere/logger.spec.ts:129:13)`; + +function createTestParams() { + const error = new Error('aaa'); + error.stack = testStack; + return { + myBoolean: false, + myNull: null, + myUndefined: undefined, + myNumber: 0, + myString: 'xxx', + mySymbol: Symbol('yyy'), + myObject: { foo: 'bar' }, + error + }; +} + +describe('formatMessage', () => { + const isoNow = '2023-02-03T01:12:03.000Z'; + const localTimeNow = '2:12:03 AM'; + const now = new Date(isoNow); + + context('given format is "raw"', () => { + it('should return a raw text with a full timestamp, detailed params but with no colors.', () => { + const message = 'Hello world'; + const params = createTestParams(); + + const actual = formatMessage('debug', message, params, 'raw', now); + const expected = `[${isoNow}] [DEBUG] Hello world` + + `\n myBoolean: false` + + `\n myNull: null` + + `\n myNumber: 0` + + `\n myString: "xxx"` + + `\n myObject: {` + + `\n "foo": "bar"` + + `\n }` + + `\n error: {` + + `\n name: "Error"` + + `\n message: "aaa"` + + `\n stack: Error: aaa` + + `\n at createTestParams (/somewhere/logger.spec.ts:6:11)` + + `\n at Context. (/somewhere/logger.spec.ts:129:13)` + + `\n }`; + + strictEqual(actual, expected); + }); + }); + + context('given format is "dev"', () => { + context('given level is "debug"', () => { + it('should return a text with gray and pink colors, a short timestamp but with no detailed params (expect for the "error" param).', () => { + const message = 'Hello world'; + const params = createTestParams(); + + const actual = formatMessage('debug', message, params, 'dev', now) + const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[35mDEBUG\u001b[39m Hello world` + + `\n error: {` + + `\n name: "Error"` + + `\n message: "aaa"` + + `\n stack: Error: aaa` + + `\n at createTestParams (/somewhere/logger.spec.ts:6:11)` + + `\n at Context. (/somewhere/logger.spec.ts:129:13)` + + `\n }`; + + strictEqual(actual, expected); + }); + }); + + context('given level is "info"', () => { + it('should return a text with gray and cyan colors, a short timestamp but with no detailed params (expect for the "error" param).', () => { + const message = 'Hello world'; + const params = createTestParams(); + + const actual = formatMessage('info', message, params, 'dev', now) + const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m Hello world` + + `\n error: {` + + `\n name: "Error"` + + `\n message: "aaa"` + + `\n stack: Error: aaa` + + `\n at createTestParams (/somewhere/logger.spec.ts:6:11)` + + `\n at Context. (/somewhere/logger.spec.ts:129:13)` + + `\n }`; + + strictEqual(actual, expected); + }); + }); + + context('given level is "warn"', () => { + it('should return a text with gray and yellow colors, a short timestamp but with no detailed params (expect for the "error" param).', () => { + const message = 'Hello world'; + const params = createTestParams(); + + const actual = formatMessage('warn', message, params, 'dev', now) + const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[33mWARN\u001b[39m Hello world` + + `\n error: {` + + `\n name: "Error"` + + `\n message: "aaa"` + + `\n stack: Error: aaa` + + `\n at createTestParams (/somewhere/logger.spec.ts:6:11)` + + `\n at Context. (/somewhere/logger.spec.ts:129:13)` + + `\n }`; + + strictEqual(actual, expected); + }); + }); + + context('given level is "error"', () => { + it('should return a text with gray and red colors, a short timestamp but with no detailed params (expect for the "error" param).', () => { + const message = 'Hello world'; + const params = createTestParams(); + + const actual = formatMessage('error', message, params, 'dev', now) + const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[31mERROR\u001b[39m Hello world` + + `\n error: {` + + `\n name: "Error"` + + `\n message: "aaa"` + + `\n stack: Error: aaa` + + `\n at createTestParams (/somewhere/logger.spec.ts:6:11)` + + `\n at Context. (/somewhere/logger.spec.ts:129:13)` + + `\n }`; + + strictEqual(actual, expected); + }); + }); + }); + + context('given format is "json"', () => { + it('should return a JSON string.', () => { + const message = 'Hello world'; + const params = createTestParams(); + + const actual = JSON.parse(formatMessage('debug', message, params, 'json', now) || ''); + const expected = { + message, + timestamp: isoNow, + level: 'debug', + myBoolean: false, + myNull: null, + myNumber: 0, + myString: 'xxx', + myObject: { foo: 'bar' }, + error: { + name: 'Error', + message: 'aaa', + stack: testStack, + } + }; + + deepStrictEqual(actual, expected); + }); + }); + + context('given format is invalid', () => { + it('should throw an error.', () => { + const message = 'Hello world'; + const params = createTestParams(); + + throws( + () => formatMessage('debug', message, params, 'invalid', now), + (error: any) => error.message === 'Invalid logging format: "invalid"' + ); + }); + }); +}) \ No newline at end of file diff --git a/packages/core/src/common/logging/logger.utils.ts b/packages/core/src/common/logging/logger.utils.ts new file mode 100644 index 0000000000..4092116947 --- /dev/null +++ b/packages/core/src/common/logging/logger.utils.ts @@ -0,0 +1,108 @@ +export type Level = 'debug'|'info'|'warn'|'error'; + +function formatParamsToText(params: { error?: Error, [name: string]: any }): string { + const tabLength = 2; + const offsetLength = 4; + + const tab = ' '.repeat(tabLength); + const offset = ' '.repeat(offsetLength); + + const lines: string[] = []; + + for (const key in params) { + const value = params[key]; + if (value instanceof Error) { + lines.push( + `${key}: {`, + `${tab}name: "${value.name}"`, + `${tab}message: "${value.message}"`, + `${tab}stack: ${value.stack?.split('\n').join(`\n${tab}${tab}`)}`, + `}` + ); + continue; + } + + const formattedValue = JSON.stringify(value, null, tabLength); + + if (formattedValue !== undefined) { + lines.push(...`${key}: ${formattedValue}`.split('\n')); + } + } + + return lines + .map(line => `\n${offset}${line}`) + .join(''); +} + +function formatMessageToRawText( + level: Level, + message: string, + params: { error?: Error, [name: string]: any }, + now: Date +): string { + const timestamp = `[${now.toISOString()}]`; + const logLevel = `[${level.toUpperCase()}]`; + + return `${timestamp} ${logLevel} ${message}` + formatParamsToText(params); +} + +function formatMessageToDevText( + level: Level, + message: string, + params: { error?: Error, [name: string]: any }, + now: Date +): string { + const levelColorCodes: Record = { + debug: 35, + info: 36, + warn: 33, + error: 31, + }; + const timestamp = `\u001b[90m[${now.toLocaleTimeString()}]\u001b[39m`; + const logLevel = `\u001b[${levelColorCodes[level]}m${level.toUpperCase()}\u001b[39m`; + + return `${timestamp} ${logLevel} ${message}` + formatParamsToText({ error: params.error }); +} + +function formatMessageToJson( + level: Level, + message: string, + params: { error?: Error, [name: string]: any }, + now: Date +): string { + const json = { + timestamp: now.toISOString(), + level, + message, + ...params, + }; + + if (json.error instanceof Error) { + json.error = { + name: json.error.name, + message: json.error.message, + stack: json.error.stack, + }; + } + + return JSON.stringify(json); +} + +export function formatMessage( + level: Level, + message: string, + params: { error?: Error, [name: string]: any }, + format: string, + now: Date, +): string { + switch (format) { + case 'raw': + return formatMessageToRawText(level, message, params, now); + case 'dev': + return formatMessageToDevText(level, message, params, now); + case 'json': + return formatMessageToJson(level, message, params, now); + default: + throw new Error(`Invalid logging format: "${format}"`); + } +} From 28aa931dbc358f423747a5aef5dbb0e014c99a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 19 Oct 2023 10:03:40 +0200 Subject: [PATCH 14/37] Update Node types --- packages/acceptance-tests/package-lock.json | 14 +++++++------- packages/acceptance-tests/package.json | 4 ++-- packages/aws-s3/package-lock.json | 14 +++++++------- packages/aws-s3/package.json | 4 ++-- packages/cli/package-lock.json | 14 +++++++------- packages/cli/package.json | 4 ++-- packages/cli/src/generate/specs/app/package.json | 2 +- .../src/generate/specs/app/package.mongodb.json | 2 +- .../generate/specs/app/package.mongodb.yaml.json | 2 +- .../cli/src/generate/specs/app/package.yaml.json | 2 +- .../cli/src/generate/templates/app/package.json | 2 +- .../generate/templates/app/package.mongodb.json | 2 +- packages/core/package-lock.json | 14 +++++++------- packages/core/package.json | 4 ++-- packages/examples/package-lock.json | 14 +++++++------- packages/examples/package.json | 4 ++-- packages/graphiql/package-lock.json | 14 +++++++------- packages/graphiql/package.json | 4 ++-- packages/graphql/package-lock.json | 14 +++++++------- packages/graphql/package.json | 4 ++-- packages/jwks-rsa/package-lock.json | 14 +++++++------- packages/jwks-rsa/package.json | 4 ++-- packages/jwt/package-lock.json | 14 +++++++------- packages/jwt/package.json | 4 ++-- packages/password/package-lock.json | 14 +++++++------- packages/password/package.json | 4 ++-- packages/redis/package-lock.json | 14 +++++++------- packages/redis/package.json | 4 ++-- packages/social/package-lock.json | 14 +++++++------- packages/social/package.json | 4 ++-- packages/socket.io/package-lock.json | 14 +++++++------- packages/socket.io/package.json | 4 ++-- packages/storage/package-lock.json | 14 +++++++------- packages/storage/package.json | 4 ++-- packages/swagger/package-lock.json | 14 +++++++------- packages/swagger/package.json | 4 ++-- packages/typestack/package-lock.json | 14 +++++++------- packages/typestack/package.json | 4 ++-- 38 files changed, 150 insertions(+), 150 deletions(-) diff --git a/packages/acceptance-tests/package-lock.json b/packages/acceptance-tests/package-lock.json index edd5dcd273..6988816512 100644 --- a/packages/acceptance-tests/package-lock.json +++ b/packages/acceptance-tests/package-lock.json @@ -31,7 +31,7 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "@types/react": "18.2.21", "@types/react-dom": "18.2.7", "@types/supertest": "2.0.12", @@ -387,9 +387,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==" }, "node_modules/@types/prop-types": { "version": "15.7.5", @@ -4577,9 +4577,9 @@ "dev": true }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==" }, "@types/prop-types": { "version": "15.7.5", diff --git a/packages/acceptance-tests/package.json b/packages/acceptance-tests/package.json index 9e9e2a657d..8144344cda 100644 --- a/packages/acceptance-tests/package.json +++ b/packages/acceptance-tests/package.json @@ -48,7 +48,7 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "@types/react": "18.2.21", "@types/react-dom": "18.2.7", "@types/supertest": "2.0.12", @@ -56,4 +56,4 @@ "ts-node": "~10.9.1", "typescript": "~4.9.5" } -} +} \ No newline at end of file diff --git a/packages/aws-s3/package-lock.json b/packages/aws-s3/package-lock.json index f0d5913838..4db9d6b482 100644 --- a/packages/aws-s3/package-lock.json +++ b/packages/aws-s3/package-lock.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "copy": "~0.3.2", "mocha": "~10.2.0", "rimraf": "~5.0.1", @@ -1535,9 +1535,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "node_modules/acorn": { @@ -5018,9 +5018,9 @@ "dev": true }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "acorn": { diff --git a/packages/aws-s3/package.json b/packages/aws-s3/package.json index 0810431a7e..bd7b015b38 100644 --- a/packages/aws-s3/package.json +++ b/packages/aws-s3/package.json @@ -50,11 +50,11 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "copy": "~0.3.2", "mocha": "~10.2.0", "rimraf": "~5.0.1", "ts-node": "~10.9.1", "typescript": "~4.9.5" } -} +} \ No newline at end of file diff --git a/packages/cli/package-lock.json b/packages/cli/package-lock.json index 15b7c9808b..843dad19eb 100644 --- a/packages/cli/package-lock.json +++ b/packages/cli/package-lock.json @@ -21,7 +21,7 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "copyfiles": "~2.4.1", "mocha": "~10.2.0", "rimraf": "~5.0.1", @@ -209,9 +209,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "node_modules/acorn": { @@ -1936,9 +1936,9 @@ "dev": true }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "acorn": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 46317d5533..7a907792e1 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -66,11 +66,11 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "copyfiles": "~2.4.1", "mocha": "~10.2.0", "rimraf": "~5.0.1", "ts-node": "~10.9.1", "typescript": "~4.9.5" } -} +} \ No newline at end of file diff --git a/packages/cli/src/generate/specs/app/package.json b/packages/cli/src/generate/specs/app/package.json index 8be1b7d30d..6de2dfece4 100644 --- a/packages/cli/src/generate/specs/app/package.json +++ b/packages/cli/src/generate/specs/app/package.json @@ -33,7 +33,7 @@ "devDependencies": { "@foal/cli": "^4.0.0", "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "concurrently": "~8.2.1", "mocha": "~10.2.0", "supertest": "~6.3.3", diff --git a/packages/cli/src/generate/specs/app/package.mongodb.json b/packages/cli/src/generate/specs/app/package.mongodb.json index 1eb5a87c29..56396ffb66 100644 --- a/packages/cli/src/generate/specs/app/package.mongodb.json +++ b/packages/cli/src/generate/specs/app/package.mongodb.json @@ -29,7 +29,7 @@ "devDependencies": { "@foal/cli": "^4.0.0", "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "concurrently": "~8.2.1", "mocha": "~10.2.0", "supertest": "~6.3.3", diff --git a/packages/cli/src/generate/specs/app/package.mongodb.yaml.json b/packages/cli/src/generate/specs/app/package.mongodb.yaml.json index d865bf872a..05b62efa16 100644 --- a/packages/cli/src/generate/specs/app/package.mongodb.yaml.json +++ b/packages/cli/src/generate/specs/app/package.mongodb.yaml.json @@ -30,7 +30,7 @@ "devDependencies": { "@foal/cli": "^4.0.0", "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "concurrently": "~8.2.1", "mocha": "~10.2.0", "supertest": "~6.3.3", diff --git a/packages/cli/src/generate/specs/app/package.yaml.json b/packages/cli/src/generate/specs/app/package.yaml.json index a8b15c7523..a82e4f1488 100644 --- a/packages/cli/src/generate/specs/app/package.yaml.json +++ b/packages/cli/src/generate/specs/app/package.yaml.json @@ -34,7 +34,7 @@ "devDependencies": { "@foal/cli": "^4.0.0", "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "concurrently": "~8.2.1", "mocha": "~10.2.0", "supertest": "~6.3.3", diff --git a/packages/cli/src/generate/templates/app/package.json b/packages/cli/src/generate/templates/app/package.json index 6129f3d0fd..7b4fea82d9 100644 --- a/packages/cli/src/generate/templates/app/package.json +++ b/packages/cli/src/generate/templates/app/package.json @@ -33,7 +33,7 @@ "devDependencies": { "@foal/cli": "^4.0.0", "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "concurrently": "~8.2.1", "mocha": "~10.2.0", "supertest": "~6.3.3", diff --git a/packages/cli/src/generate/templates/app/package.mongodb.json b/packages/cli/src/generate/templates/app/package.mongodb.json index ab49726db1..c27bf56cc2 100644 --- a/packages/cli/src/generate/templates/app/package.mongodb.json +++ b/packages/cli/src/generate/templates/app/package.mongodb.json @@ -29,7 +29,7 @@ "devDependencies": { "@foal/cli": "^4.0.0", "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "concurrently": "~8.2.1", "mocha": "~10.2.0", "supertest": "~6.3.3", diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json index 81eb506e35..5e9360ed64 100644 --- a/packages/core/package-lock.json +++ b/packages/core/package-lock.json @@ -18,7 +18,7 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "@types/supertest": "2.0.12", "ajv-errors": "~3.0.0", "ejs": "~3.1.9", @@ -230,9 +230,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "node_modules/@types/superagent": { @@ -2770,9 +2770,9 @@ "dev": true }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "@types/superagent": { diff --git a/packages/core/package.json b/packages/core/package.json index 468a8561e5..1ce7355c4a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -69,7 +69,7 @@ "devDependencies": { "@foal/internal-test": "^4.0.0", "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "@types/supertest": "2.0.12", "ajv-errors": "~3.0.0", "ejs": "~3.1.9", @@ -82,4 +82,4 @@ "typescript": "~4.9.5", "yamljs": "~0.3.0" } -} +} \ No newline at end of file diff --git a/packages/examples/package-lock.json b/packages/examples/package-lock.json index ca160dc4f8..0a1b7e1741 100644 --- a/packages/examples/package-lock.json +++ b/packages/examples/package-lock.json @@ -17,7 +17,7 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "concurrently": "~8.2.1", "copy": "~0.3.2", "mocha": "~10.2.0", @@ -124,9 +124,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "node_modules/abbrev": { @@ -3661,9 +3661,9 @@ "dev": true }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "abbrev": { diff --git a/packages/examples/package.json b/packages/examples/package.json index e4e329f285..93f999bd32 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -61,11 +61,11 @@ "devDependencies": { "@foal/cli": "^4.0.0", "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "concurrently": "~8.2.1", "copy": "~0.3.2", "mocha": "~10.2.0", "supervisor": "~0.12.0", "typescript": "~4.9.5" } -} +} \ No newline at end of file diff --git a/packages/graphiql/package-lock.json b/packages/graphiql/package-lock.json index ee6d68704c..b918379760 100644 --- a/packages/graphiql/package-lock.json +++ b/packages/graphiql/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "graphiql": "~3.0.5", "mocha": "~10.2.0", "react": "~18.1.0", @@ -1129,9 +1129,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "node_modules/@types/tern": { @@ -3645,9 +3645,9 @@ "dev": true }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "@types/tern": { diff --git a/packages/graphiql/package.json b/packages/graphiql/package.json index 245df5c6b4..ce81265a5c 100644 --- a/packages/graphiql/package.json +++ b/packages/graphiql/package.json @@ -44,7 +44,7 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "graphiql": "~3.0.5", "mocha": "~10.2.0", "react": "~18.1.0", @@ -53,4 +53,4 @@ "ts-node": "~10.9.1", "typescript": "~4.9.5" } -} +} \ No newline at end of file diff --git a/packages/graphql/package-lock.json b/packages/graphql/package-lock.json index 5c847240df..b8a0a76585 100644 --- a/packages/graphql/package-lock.json +++ b/packages/graphql/package-lock.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "graphql": "~16.8.0", "graphql-request": "~6.1.0", "mocha": "~10.2.0", @@ -208,9 +208,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "node_modules/@types/semver": { @@ -1863,9 +1863,9 @@ "dev": true }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "@types/semver": { diff --git a/packages/graphql/package.json b/packages/graphql/package.json index 530072f37d..94d87234e6 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -46,7 +46,7 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "graphql": "~16.8.0", "graphql-request": "~6.1.0", "mocha": "~10.2.0", @@ -58,4 +58,4 @@ "peerDependencies": { "graphql": "^16.8.0" } -} +} \ No newline at end of file diff --git a/packages/jwks-rsa/package-lock.json b/packages/jwks-rsa/package-lock.json index 9baed060d6..4d43aecffd 100644 --- a/packages/jwks-rsa/package-lock.json +++ b/packages/jwks-rsa/package-lock.json @@ -13,7 +13,7 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "mocha": "~10.2.0", "rimraf": "~5.0.1", "ts-node": "~10.9.1", @@ -289,9 +289,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==" }, "node_modules/@types/qs": { "version": "6.9.7", @@ -2731,9 +2731,9 @@ "dev": true }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==" }, "@types/qs": { "version": "6.9.7", diff --git a/packages/jwks-rsa/package.json b/packages/jwks-rsa/package.json index 7a26d471c2..51f361c77a 100644 --- a/packages/jwks-rsa/package.json +++ b/packages/jwks-rsa/package.json @@ -53,10 +53,10 @@ "@foal/core": "^4.0.0", "@foal/jwt": "^4.0.0", "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "mocha": "~10.2.0", "rimraf": "~5.0.1", "ts-node": "~10.9.1", "typescript": "~4.9.5" } -} +} \ No newline at end of file diff --git a/packages/jwt/package-lock.json b/packages/jwt/package-lock.json index 9a1cb95ce6..d5a3326259 100644 --- a/packages/jwt/package-lock.json +++ b/packages/jwt/package-lock.json @@ -13,7 +13,7 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "mocha": "~10.2.0", "rimraf": "~5.0.1", "ts-node": "~10.9.1", @@ -200,9 +200,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "node_modules/acorn": { @@ -1728,9 +1728,9 @@ "dev": true }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "acorn": { diff --git a/packages/jwt/package.json b/packages/jwt/package.json index 57f34b52a7..bd1b2b2439 100644 --- a/packages/jwt/package.json +++ b/packages/jwt/package.json @@ -47,10 +47,10 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "mocha": "~10.2.0", "rimraf": "~5.0.1", "ts-node": "~10.9.1", "typescript": "~4.9.5" } -} +} \ No newline at end of file diff --git a/packages/password/package-lock.json b/packages/password/package-lock.json index f37e65176e..71cbee1338 100644 --- a/packages/password/package-lock.json +++ b/packages/password/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "copy": "~0.3.2", "mocha": "~10.2.0", "rimraf": "~5.0.1", @@ -148,9 +148,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "node_modules/acorn": { @@ -2485,9 +2485,9 @@ "dev": true }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "acorn": { diff --git a/packages/password/package.json b/packages/password/package.json index 718364f7c5..07a19b9418 100644 --- a/packages/password/package.json +++ b/packages/password/package.json @@ -41,11 +41,11 @@ ], "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "copy": "~0.3.2", "mocha": "~10.2.0", "rimraf": "~5.0.1", "ts-node": "~10.9.1", "typescript": "~4.9.5" } -} +} \ No newline at end of file diff --git a/packages/redis/package-lock.json b/packages/redis/package-lock.json index d815b63251..dc4dc27759 100644 --- a/packages/redis/package-lock.json +++ b/packages/redis/package-lock.json @@ -13,7 +13,7 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "mocha": "~10.2.0", "rimraf": "~5.0.1", "ts-node": "~10.9.1", @@ -253,9 +253,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "node_modules/acorn": { @@ -1739,9 +1739,9 @@ "dev": true }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "acorn": { diff --git a/packages/redis/package.json b/packages/redis/package.json index ccf80c046d..8d3d6d3a2e 100644 --- a/packages/redis/package.json +++ b/packages/redis/package.json @@ -46,10 +46,10 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "mocha": "~10.2.0", "rimraf": "~5.0.1", "ts-node": "~10.9.1", "typescript": "~4.9.5" } -} +} \ No newline at end of file diff --git a/packages/social/package-lock.json b/packages/social/package-lock.json index f2b1f59306..3b277df1ca 100644 --- a/packages/social/package-lock.json +++ b/packages/social/package-lock.json @@ -13,7 +13,7 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "copy": "~0.3.2", "jsonwebtoken": "~9.0.2", "mocha": "~10.2.0", @@ -202,9 +202,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "node_modules/acorn": { @@ -2613,9 +2613,9 @@ "dev": true }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "acorn": { diff --git a/packages/social/package.json b/packages/social/package.json index 233bca229c..831aaea9d2 100644 --- a/packages/social/package.json +++ b/packages/social/package.json @@ -57,7 +57,7 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "copy": "~0.3.2", "jsonwebtoken": "~9.0.2", "mocha": "~10.2.0", @@ -65,4 +65,4 @@ "ts-node": "~10.9.1", "typescript": "~4.9.5" } -} +} \ No newline at end of file diff --git a/packages/socket.io/package-lock.json b/packages/socket.io/package-lock.json index 418c03ca3e..a1ee7c0f8d 100644 --- a/packages/socket.io/package-lock.json +++ b/packages/socket.io/package-lock.json @@ -15,7 +15,7 @@ "devDependencies": { "@socket.io/redis-adapter": "~8.2.1", "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "mocha": "~10.2.0", "redis": "~4.6.8", "rimraf": "~5.0.1", @@ -298,9 +298,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==" }, "node_modules/accepts": { "version": "1.3.8", @@ -2045,9 +2045,9 @@ "dev": true }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==" }, "accepts": { "version": "1.3.8", diff --git a/packages/socket.io/package.json b/packages/socket.io/package.json index 338be9fcd4..6b0b54b1ca 100644 --- a/packages/socket.io/package.json +++ b/packages/socket.io/package.json @@ -46,7 +46,7 @@ "devDependencies": { "@socket.io/redis-adapter": "~8.2.1", "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "mocha": "~10.2.0", "redis": "~4.6.8", "rimraf": "~5.0.1", @@ -59,4 +59,4 @@ "reflect-metadata": "~0.1.13", "socket.io": "~4.7.2" } -} +} \ No newline at end of file diff --git a/packages/storage/package-lock.json b/packages/storage/package-lock.json index 772859b06a..b69013860a 100644 --- a/packages/storage/package-lock.json +++ b/packages/storage/package-lock.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "@types/supertest": "2.0.12", "copy": "~0.3.2", "mocha": "~10.2.0", @@ -210,9 +210,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "node_modules/@types/superagent": { @@ -2821,9 +2821,9 @@ "dev": true }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "@types/superagent": { diff --git a/packages/storage/package.json b/packages/storage/package.json index f3ad808c3b..82e520e6b1 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -51,7 +51,7 @@ "devDependencies": { "@foal/internal-test": "^4.0.0", "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "@types/supertest": "2.0.12", "copy": "~0.3.2", "mocha": "~10.2.0", @@ -60,4 +60,4 @@ "ts-node": "~10.9.1", "typescript": "~4.9.5" } -} +} \ No newline at end of file diff --git a/packages/swagger/package-lock.json b/packages/swagger/package-lock.json index 5a31312314..63994020d5 100644 --- a/packages/swagger/package-lock.json +++ b/packages/swagger/package-lock.json @@ -13,7 +13,7 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "copy": "~0.3.2", "mocha": "~10.2.0", "rimraf": "~5.0.1", @@ -201,9 +201,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "node_modules/acorn": { @@ -2446,9 +2446,9 @@ "dev": true }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "acorn": { diff --git a/packages/swagger/package.json b/packages/swagger/package.json index d507d23fb6..c8c2dc3c65 100644 --- a/packages/swagger/package.json +++ b/packages/swagger/package.json @@ -48,11 +48,11 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "copy": "~0.3.2", "mocha": "~10.2.0", "rimraf": "~5.0.1", "ts-node": "~10.9.1", "typescript": "~4.9.5" } -} +} \ No newline at end of file diff --git a/packages/typestack/package-lock.json b/packages/typestack/package-lock.json index 5ff62dfff6..52214ef965 100644 --- a/packages/typestack/package-lock.json +++ b/packages/typestack/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "@types/validator": "13.11.1", "class-transformer": "~0.5.1", "class-validator": "~0.14.0", @@ -205,9 +205,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "node_modules/@types/validator": { @@ -2483,9 +2483,9 @@ "dev": true }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "@types/validator": { diff --git a/packages/typestack/package.json b/packages/typestack/package.json index 5aff6b98b3..ff13d7c232 100644 --- a/packages/typestack/package.json +++ b/packages/typestack/package.json @@ -52,7 +52,7 @@ }, "devDependencies": { "@types/mocha": "10.0.1", - "@types/node": "18.11.9", + "@types/node": "18.18.6", "@types/validator": "13.11.1", "class-transformer": "~0.5.1", "class-validator": "~0.14.0", @@ -62,4 +62,4 @@ "ts-node": "~10.9.1", "typescript": "~4.9.5" } -} +} \ No newline at end of file From 164ced0d5ca4ddef658c8e3d8c271922b843aff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 19 Oct 2023 10:15:37 +0200 Subject: [PATCH 15/37] Add shouldLog util --- .../src/common/logging/logger.utils.spec.ts | 51 ++++++++++++++++++- .../core/src/common/logging/logger.utils.ts | 13 +++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/packages/core/src/common/logging/logger.utils.spec.ts b/packages/core/src/common/logging/logger.utils.spec.ts index 9252b9a957..3075bf3cf3 100644 --- a/packages/core/src/common/logging/logger.utils.spec.ts +++ b/packages/core/src/common/logging/logger.utils.spec.ts @@ -1,5 +1,5 @@ import { deepStrictEqual, strictEqual, throws } from 'assert'; -import { formatMessage } from './logger.utils'; +import { formatMessage, shouldLog } from './logger.utils'; const testStack = `Error: aaa at createTestParams (/somewhere/logger.spec.ts:6:11) @@ -166,4 +166,51 @@ describe('formatMessage', () => { ); }); }); -}) \ No newline at end of file +}); + +describe('shouldLog', () => { + context('given the configLogLevel is "debug"', () => { + it('should return true for all levels.', () => { + strictEqual(shouldLog('debug', 'debug'), true); + strictEqual(shouldLog('info', 'debug'), true); + strictEqual(shouldLog('warn', 'debug'), true); + strictEqual(shouldLog('error', 'debug'), true); + }); + }); + + context('given the configLogLevel is "info"', () => { + it('should return false for "debug" and true for "info", "warn" and "error".', () => { + strictEqual(shouldLog('debug', 'info'), false); + strictEqual(shouldLog('info', 'info'), true); + strictEqual(shouldLog('warn', 'info'), true); + strictEqual(shouldLog('error', 'info'), true); + }); + }); + + context('given the configLogLevel is "warn"', () => { + it('should return false for "debug" and "info" and true for "warn" and "error".', () => { + strictEqual(shouldLog('debug', 'warn'), false); + strictEqual(shouldLog('info', 'warn'), false); + strictEqual(shouldLog('warn', 'warn'), true); + strictEqual(shouldLog('error', 'warn'), true); + }); + }); + + context('given the configLogLevel is "error"', () => { + it('should return false for "debug", "info" and "warn" and true for "error".', () => { + strictEqual(shouldLog('debug', 'error'), false); + strictEqual(shouldLog('info', 'error'), false); + strictEqual(shouldLog('warn', 'error'), false); + strictEqual(shouldLog('error', 'error'), true); + }); + }); + + context('given the configLogLevel is invalid', () => { + it('should throw an error.', () => { + throws( + () => shouldLog('debug', 'invalid'), + (error: any) => error.message === 'Invalid log level: "invalid"' + ); + }); + }); +}); \ No newline at end of file diff --git a/packages/core/src/common/logging/logger.utils.ts b/packages/core/src/common/logging/logger.utils.ts index 4092116947..ff0f0c445b 100644 --- a/packages/core/src/common/logging/logger.utils.ts +++ b/packages/core/src/common/logging/logger.utils.ts @@ -106,3 +106,16 @@ export function formatMessage( throw new Error(`Invalid logging format: "${format}"`); } } + +export function shouldLog(level: Level, configLogLevel: string): boolean { + const levels: string[] = ['debug', 'info', 'warn', 'error']; + + const levelIndex = levels.indexOf(level); + const configLogLevelIndex = levels.indexOf(configLogLevel); + + if (configLogLevelIndex === -1) { + throw new Error(`Invalid log level: "${configLogLevel}"`); + } + + return levelIndex >= configLogLevelIndex; +} \ No newline at end of file From 48c6a2a0d5a0d0068b2142b244755eb0b89bc005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 19 Oct 2023 10:46:07 +0200 Subject: [PATCH 16/37] Add log level config in Logger --- .../core/src/common/logging/logger.spec.ts | 327 +++++++----------- packages/core/src/common/logging/logger.ts | 63 ++-- 2 files changed, 157 insertions(+), 233 deletions(-) diff --git a/packages/core/src/common/logging/logger.spec.ts b/packages/core/src/common/logging/logger.spec.ts index 222052d8ab..49cfc6b119 100644 --- a/packages/core/src/common/logging/logger.spec.ts +++ b/packages/core/src/common/logging/logger.spec.ts @@ -1,229 +1,156 @@ -import { deepStrictEqual, strictEqual, throws } from 'assert'; -import { Logger } from './logger'; -import { Config } from '../../core'; - -const testStack = `Error: aaa - at createTestParams (/somewhere/logger.spec.ts:6:11) - at Context. (/somewhere/logger.spec.ts:129:13)`; - -function createTestParams() { - const error = new Error('aaa'); - error.stack = testStack; - return { - myBoolean: false, - myNull: null, - myUndefined: undefined, - myNumber: 0, - myString: 'xxx', - mySymbol: Symbol('yyy'), - myObject: { foo: 'bar' }, - error - }; -} - -function createLogger(): { - logger: Logger, - mocks: { logWithConsoleHasBeenCalledWith: any }, - now: Date, - isoNow: string, - localTimeNow: string -} { - const mocks: { logWithConsoleHasBeenCalledWith: any } = { logWithConsoleHasBeenCalledWith: undefined }; - - const isoNow = '2023-02-03T01:12:03.000Z'; - const localTimeNow = '2:12:03 AM'; - const now = new Date(isoNow); - - class Logger2 extends Logger { - logWithConsole(message: string): void { - mocks.logWithConsoleHasBeenCalledWith = message; - } - - getNow(): Date { - return now; - } - } - - const logger = new Logger2(); - - return { - logger, - mocks, - now, - isoNow, - localTimeNow, - }; -} +// std +import { strictEqual } from 'assert'; +import { mock } from 'node:test'; -describe('Logger', () => { +// FoalTS +import { Logger, log } from './logger'; +import { Config } from '../../core'; +describe('log', () => { afterEach(() => { + mock.reset(); Config.remove('settings.logger.format'); - }) + Config.remove('settings.logger.logLevel'); + }); - describe('when debug is called', () => { - context('given the configuration "settings.logger.format" is NOT defined', () => { - it('should log a text with an exhaustive timestamp, with params but with no colors', () => { - const { logger, mocks, isoNow } = createLogger(); - - const message = 'Hello world'; - const params = createTestParams(); - - logger.debug(message, params); - - const actual = mocks.logWithConsoleHasBeenCalledWith; - const expected = `[${isoNow}] [DEBUG] Hello world` - + `\n myBoolean: false` - + `\n myNull: null` - + `\n myNumber: 0` - + `\n myString: "xxx"` - + `\n myObject: {` - + `\n "foo": "bar"` - + `\n }` - + `\n error: {` - + `\n name: "Error"` - + `\n message: "aaa"` - + `\n stack: Error: aaa` - + `\n at createTestParams (/somewhere/logger.spec.ts:6:11)` - + `\n at Context. (/somewhere/logger.spec.ts:129:13)` - + `\n }`; - - strictEqual(actual, expected); - }); - }); + context('given the configuration "settings.logger.format" is NOT defined', () => { + it('should log the message to a raw text.', () => { + const consoleMock = mock.method(console, 'log', () => {}); - context('given the configuration "settings.logger.format" is "raw"', () => { - beforeEach(() => { - Config.set('settings.logger.format', 'raw'); - }); - - it('should log a text with an exhaustive timestamp, with params but with no colors', () => { - const { logger, mocks, isoNow } = createLogger(); - - const message = 'Hello world'; - const params = createTestParams(); - - logger.debug(message, params); - - const actual = mocks.logWithConsoleHasBeenCalledWith; - const expected = `[${isoNow}] [DEBUG] Hello world` - + `\n myBoolean: false` - + `\n myNull: null` - + `\n myNumber: 0` - + `\n myString: "xxx"` - + `\n myObject: {` - + `\n "foo": "bar"` - + `\n }` - + `\n error: {` - + `\n name: "Error"` - + `\n message: "aaa"` - + `\n stack: Error: aaa` - + `\n at createTestParams (/somewhere/logger.spec.ts:6:11)` - + `\n at Context. (/somewhere/logger.spec.ts:129:13)` - + `\n }`; - - strictEqual(actual, expected); - }); - }); + log('error', 'Hello world', {}); + + strictEqual(consoleMock.mock.callCount(), 1); + const loggedMessage = consoleMock.mock.calls[0].arguments[0]; - context('given the configuration "settings.logger.format" is "dev"', () => { - beforeEach(() => { - Config.set('settings.logger.format', 'dev'); - }); + strictEqual(loggedMessage.includes('[ERROR]'), true); + }) + }); - it('should log a text with a short timestamp, with colors but with no params (except the "error" param)', () => { - const { logger, mocks, localTimeNow } = createLogger(); + context('given the configuration "settings.logger.format" is "json"', () => { + it('should log the message to a JSON.', () => { + Config.set('settings.logger.format', 'json'); - const message = 'Hello world'; - const params = createTestParams(); + const consoleMock = mock.method(console, 'log', () => {}); - logger.debug(message, params); + log('error', 'Hello world', {}); - const actual = mocks.logWithConsoleHasBeenCalledWith; - // Pink color - const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[35mDEBUG\u001b[39m Hello world` - + `\n error: {` - + `\n name: "Error"` - + `\n message: "aaa"` - + `\n stack: Error: aaa` - + `\n at createTestParams (/somewhere/logger.spec.ts:6:11)` - + `\n at Context. (/somewhere/logger.spec.ts:129:13)` - + `\n }`; + strictEqual(consoleMock.mock.callCount(), 1); - strictEqual(actual, expected); - }); - }); + const loggedMessage = consoleMock.mock.calls[0].arguments[0]; + const json = JSON.parse(loggedMessage); - context('given the configuration "settings.logger.format" is "json"', () => { - beforeEach(() => { - Config.set('settings.logger.format', 'json'); - }); - - it('should log a JSON', () => { - const { logger, mocks, isoNow } = createLogger(); - - const message = 'Hello world'; - const params = createTestParams(); - - logger.debug(message, params); - - const actual = JSON.parse(mocks.logWithConsoleHasBeenCalledWith); - const expected = { - message, - timestamp: isoNow, - level: 'debug', - myBoolean: false, - myNull: null, - myNumber: 0, - myString: 'xxx', - myObject: { foo: 'bar' }, - error: { - name: 'Error', - message: 'aaa', - stack: testStack, - } - }; - - deepStrictEqual(actual, expected); - }); + strictEqual(json.level, 'error'); }); + }); - context('given the configuration "settings.logger.format" is "none"', () => { - beforeEach(() => { - Config.set('settings.logger.format', 'none'); - }); + context('given the configuration "settings.logger.format" is "none"', () => { + it('should log nothing.', () => { + Config.set('settings.logger.format', 'none'); - it('should log nothing', () => { - const { logger, mocks } = createLogger(); + const consoleMock = mock.method(console, 'log', () => {}); - const message = 'Hello world'; - const params = createTestParams(); + log('error', 'Hello world', {}); - logger.debug(message, params); + strictEqual(consoleMock.mock.callCount(), 0); + }); + }); + + context('given the configuration "settings.logger.logLevel" is NOT defined', () => { + it('should log messages based on an "INFO" log level', () => { + const consoleMock = mock.method(console, 'log', () => {}); - const actual = mocks.logWithConsoleHasBeenCalledWith; - const expected = undefined; + log('info', 'Hello world', {}); + strictEqual(consoleMock.mock.callCount(), 1); - strictEqual(actual, expected); - }); + consoleMock.mock.resetCalls(); + + log('debug', 'Hello world', {}); + strictEqual(consoleMock.mock.callCount(), 0); }); + }); + + context('given the configuration "settings.logger.logLevel" is "warn"', () => { + it('should log messages based on a "WARN" log level', () => { + Config.set('settings.logger.logLevel', 'warn'); - context('given the configuration "settings.logger.format" has an incorrect value', () => { - beforeEach(() => { - Config.set('settings.logger.format', 'foobar'); - }); + const consoleMock = mock.method(console, 'log', () => {}); - it('should throw an error', () => { - const { logger } = createLogger(); + log('warn', 'Hello world', {}); + strictEqual(consoleMock.mock.callCount(), 1); - const message = 'Hello world'; + consoleMock.mock.resetCalls(); - throws( - () => logger.debug(message), - (error: any) => error.message === 'Invalid logging format: "foobar"' - ); - }); + log('info', 'Hello world', {}); + strictEqual(consoleMock.mock.callCount(), 0); }); }); }); + +describe('Logger', () => { + + beforeEach(() => { + Config.set('settings.logger.logLevel', 'debug'); + }); + + afterEach(() => { + mock.reset(); + Config.remove('settings.logger.logLevel'); + }) + + it('has a debug(...args) method which is an alias for log("debug", ...args)', () => { + const logger = new Logger(); + + const consoleMock = mock.method(console, 'log', () => {}); + + logger.debug('Hello world', {}); + + strictEqual(consoleMock.mock.callCount(), 1); + strictEqual( + consoleMock.mock.calls[0].arguments[0].includes('[DEBUG]'), + true, + ); + }); + + it('has an info(...args) method which is an alias for log("info", ...args)', () => { + const logger = new Logger(); + + const consoleMock = mock.method(console, 'log', () => {}); + + logger.info('Hello world', {}); + + strictEqual(consoleMock.mock.callCount(), 1); + strictEqual( + consoleMock.mock.calls[0].arguments[0].includes('[INFO]'), + true, + ); + }); + + it('has a warn(...args) method which is an alias for log("warn", ...args)', () => { + const logger = new Logger(); + + const consoleMock = mock.method(console, 'log', () => {}); + + logger.warn('Hello world', {}); + + strictEqual(consoleMock.mock.callCount(), 1); + strictEqual( + consoleMock.mock.calls[0].arguments[0].includes('[WARN]'), + true, + ); + }); + + it('has an error(...args) method which is an alias for log("error", ...args)', () => { + const logger = new Logger(); + + const consoleMock = mock.method(console, 'log', () => {}); + + logger.error('Hello world', {}); + + strictEqual(consoleMock.mock.callCount(), 1); + strictEqual( + consoleMock.mock.calls[0].arguments[0].includes('[ERROR]'), + true, + ); + }); +}); diff --git a/packages/core/src/common/logging/logger.ts b/packages/core/src/common/logging/logger.ts index 7517f62cf8..1dadb0d066 100644 --- a/packages/core/src/common/logging/logger.ts +++ b/packages/core/src/common/logging/logger.ts @@ -1,50 +1,47 @@ import { Config } from '../../core'; -import { Level, formatMessage } from './logger.utils'; - -export class Logger { - - private log( - level: Level, - message: string, - params: { [name: string]: any } - ): void { - const format = Config.get('settings.logger.format', 'string', 'raw'); - const now = this.getNow(); - - const messageFormatted = formatMessage( - level, - message, - params, - format, - now, - ); - - if (messageFormatted !== null) { - this.logWithConsole(messageFormatted); - } +import { Level, formatMessage, shouldLog } from './logger.utils'; + +export function log( + level: Level, + message: string, + params: { [name: string]: any } +): void { + const format = Config.get('settings.logger.format', 'string', 'raw'); + if (format === 'none') { + return; } - protected logWithConsole(message: string): void { - console.log(message); - } + const configLogLevel = Config.get('settings.logger.logLevel', 'string', 'info'); + if (!shouldLog(level, configLogLevel)) { + return; + }; - protected getNow(): Date { - return new Date(); - } + const now = new Date(); + const formattedMessage = formatMessage( + level, + message, + params, + format, + now, + ); + + console.log(formattedMessage); +} +export class Logger { debug(message: string, params: { error?: Error, [name: string]: any } = {}): void { - this.log('debug', message, params); + log('debug', message, params); } info(message: string, params: { error?: Error, [name: string]: any } = {}): void { - this.log('info', message, params); + log('info', message, params); } warn(message: string, params: { error?: Error, [name: string]: any } = {}): void { - this.log('warn', message, params); + log('warn', message, params); } error(message: string, params: { error?: Error, [name: string]: any } = {}): void { - this.log('error', message, params); + log('error', message, params); } } \ No newline at end of file From 9c3ceb9a0cbadf545fdfd0dc27ae12ea35d87ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 19 Oct 2023 15:41:03 +0200 Subject: [PATCH 17/37] Support http pretty logging in Foal logger --- packages/core/src/common/logging/index.ts | 3 +- .../src/common/logging/logger.utils.spec.ts | 67 +++++++++++++++++++ .../core/src/common/logging/logger.utils.ts | 22 ++++++ 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/packages/core/src/common/logging/index.ts b/packages/core/src/common/logging/index.ts index 0a9e39b0e0..fc055d66fc 100644 --- a/packages/core/src/common/logging/index.ts +++ b/packages/core/src/common/logging/index.ts @@ -1 +1,2 @@ -export { Logger } from './logger'; \ No newline at end of file +export { Logger } from './logger'; +export { httpRequestMessagePrefix } from './logger.utils'; \ No newline at end of file diff --git a/packages/core/src/common/logging/logger.utils.spec.ts b/packages/core/src/common/logging/logger.utils.spec.ts index 3075bf3cf3..c9c2b7c64d 100644 --- a/packages/core/src/common/logging/logger.utils.spec.ts +++ b/packages/core/src/common/logging/logger.utils.spec.ts @@ -127,6 +127,73 @@ describe('formatMessage', () => { strictEqual(actual, expected); }); }); + + context('given the message is a HTTP log and is prefixed by "HTTP request -"', () => { + it('should return a text with a well-formatted message (1xx status).', () => { + const message = 'HTTP request - GET /foo/bar'; + const params = { + statusCode: 100, + responseTime: 123, + }; + + const actual = formatMessage('info', message, params, 'dev', now); + const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m GET /foo/bar 100 - 123 ms` + + strictEqual(actual, expected); + }); + + it('should return a text with a well-formatted message (2xx status).', () => { + const message = 'HTTP request - GET /foo/bar'; + const params = { + statusCode: 200, + responseTime: 123, + }; + + const actual = formatMessage('info', message, params, 'dev', now); + const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m GET /foo/bar \u001b[32m200\u001b[39m - 123 ms` + + strictEqual(actual, expected); + }); + + it('should return a text with a well-formatted message (3xx status).', () => { + const message = 'HTTP request - GET /foo/bar'; + const params = { + statusCode: 300, + responseTime: 123, + }; + + const actual = formatMessage('info', message, params, 'dev', now); + const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m GET /foo/bar \u001b[36m300\u001b[39m - 123 ms` + + strictEqual(actual, expected); + }); + + it('should return a text with a well-formatted message (4xx status).', () => { + const message = 'HTTP request - GET /foo/bar'; + const params = { + statusCode: 400, + responseTime: 123, + }; + + const actual = formatMessage('info', message, params, 'dev', now); + const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m GET /foo/bar \u001b[33m400\u001b[39m - 123 ms` + + strictEqual(actual, expected); + }); + + it('should return a text with a well-formatted message (5xx status).', () => { + const message = 'HTTP request - GET /foo/bar'; + const params = { + statusCode: 500, + responseTime: 123, + }; + + const actual = formatMessage('info', message, params, 'dev', now); + const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m GET /foo/bar \u001b[31m500\u001b[39m - 123 ms` + + strictEqual(actual, expected); + }); + }); }); context('given format is "json"', () => { diff --git a/packages/core/src/common/logging/logger.utils.ts b/packages/core/src/common/logging/logger.utils.ts index ff0f0c445b..31446c3e53 100644 --- a/packages/core/src/common/logging/logger.utils.ts +++ b/packages/core/src/common/logging/logger.utils.ts @@ -1,4 +1,5 @@ export type Level = 'debug'|'info'|'warn'|'error'; +export const httpRequestMessagePrefix = 'HTTP request - '; function formatParamsToText(params: { error?: Error, [name: string]: any }): string { const tabLength = 2; @@ -46,6 +47,22 @@ function formatMessageToRawText( return `${timestamp} ${logLevel} ${message}` + formatParamsToText(params); } +function getColoredStatusCode(statusCode: number): string { + if (statusCode >= 500) { + return `\u001b[31m${statusCode}\u001b[39m`; + } + if (statusCode >= 400) { + return `\u001b[33m${statusCode}\u001b[39m`; + } + if (statusCode >= 300) { + return `\u001b[36m${statusCode}\u001b[39m`; + } + if (statusCode >= 200) { + return `\u001b[32m${statusCode}\u001b[39m`; + } + return statusCode.toString(); +} + function formatMessageToDevText( level: Level, message: string, @@ -61,6 +78,11 @@ function formatMessageToDevText( const timestamp = `\u001b[90m[${now.toLocaleTimeString()}]\u001b[39m`; const logLevel = `\u001b[${levelColorCodes[level]}m${level.toUpperCase()}\u001b[39m`; + if (message.startsWith(httpRequestMessagePrefix)) { + message = message.slice(httpRequestMessagePrefix.length); + message += ` ${getColoredStatusCode(params.statusCode)} - ${params.responseTime} ms`; + } + return `${timestamp} ${logLevel} ${message}` + formatParamsToText({ error: params.error }); } From c32b437f18bf13d356f443d355c6c5eea2e58e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Thu, 19 Oct 2023 21:37:22 +0200 Subject: [PATCH 18/37] Allow to log HTTP requests with Foal logger --- packages/core/src/express/create-app.spec.ts | 74 +++++++++++++++++++- packages/core/src/express/create-app.ts | 39 ++++++++--- packages/examples/config/default.yml | 1 + packages/examples/config/development.yml | 1 - 4 files changed, 104 insertions(+), 11 deletions(-) diff --git a/packages/core/src/express/create-app.spec.ts b/packages/core/src/express/create-app.spec.ts index ebb789c248..9cedda2e69 100644 --- a/packages/core/src/express/create-app.spec.ts +++ b/packages/core/src/express/create-app.spec.ts @@ -1,5 +1,5 @@ // std -import { strictEqual } from 'assert'; +import { deepStrictEqual, strictEqual } from 'assert'; import { Buffer } from 'buffer'; // 3p @@ -25,6 +25,8 @@ import { ServiceManager } from '../core'; import { createApp, OPENAPI_SERVICE_ID } from './create-app'; +import { mock } from 'node:test'; +import { Logger } from '../common'; describe('createApp', () => { @@ -48,10 +50,12 @@ describe('createApp', () => { }); afterEach(() => { + mock.reset(); Config.remove('settings.staticPathPrefix'); Config.remove('settings.debug'); Config.remove('settings.bodyParser.limit'); Config.remove('settings.cookieParser.secret'); + Config.remove('settings.loggerFormat'); }); const cookieSecret = 'strong-secret'; @@ -663,4 +667,72 @@ describe('createApp', () => { } }); + context('given the configuration "settings.loggerFormat" is set to "foal"', () => { + it('should log the request with a detailed message and detail parameters.', async () => { + Config.set('settings.loggerFormat', 'foal'); + + class AppController { + @Get('/a') + getA(ctx: Context) { + return new HttpResponseOK('a'); + } + } + + const serviceManager = new ServiceManager(); + + const logger = serviceManager.get(Logger); + const loggerMock = mock.method(logger, 'info', () => {}); + + const app = await createApp(AppController, { + serviceManager + }); + + await request(app) + .get('/a') + .expect(200); + + strictEqual(loggerMock.mock.callCount(), 1); + + const message = loggerMock.mock.calls[0].arguments[0]; + const params = loggerMock.mock.calls[0].arguments[1]; + + strictEqual(message, 'HTTP request - GET /a'); + strictEqual(typeof params?.responseTime, 'number') + + delete params?.responseTime; + + deepStrictEqual(params, { + method: 'GET', + url: '/a', + statusCode: 200, + contentLength: '1', + }); + }) + }); + + context('given the configuration "settings.loggerFormat" is set to a value different from "none" or "foal"', () => { + it('should log a warning message.', async () => { + Config.set('settings.loggerFormat', 'dev'); + + class AppController { + @Get('/a') + getA(ctx: Context) { + return new HttpResponseOK('a'); + } + } + + const serviceManager = new ServiceManager(); + + const logger = serviceManager.get(Logger); + const loggerMock = mock.method(logger, 'warn', () => {}); + + const app = await createApp(AppController, { + serviceManager + }); + + strictEqual(loggerMock.mock.callCount(), 1); + strictEqual(loggerMock.mock.calls[0].arguments[0], '[CONFIG] Using another format than "foal" for "settings.loggerFormat" is deprecated.'); + }); + }); + }); diff --git a/packages/core/src/express/create-app.ts b/packages/core/src/express/create-app.ts index 01562235d3..5236b859bb 100644 --- a/packages/core/src/express/create-app.ts +++ b/packages/core/src/express/create-app.ts @@ -15,7 +15,7 @@ import { ServiceManager, } from '../core'; import { sendResponse } from './send-response'; -import { Logger } from '../common'; +import { httpRequestMessagePrefix, Logger } from '../common'; export const OPENAPI_SERVICE_ID = 'OPENAPI_SERVICE_ID_a5NWKbBNBxVVZ'; @@ -80,13 +80,41 @@ export async function createApp( app.use(middleware); } + // Create the service and controller manager. + const services = options.serviceManager || new ServiceManager(); + app.foal = { services }; + + // Retrieve the logger. + const logger = services.get(Logger); + // Log requests. const loggerFormat = Config.get( 'settings.loggerFormat', 'string', '[:date] ":method :url HTTP/:http-version" :status - :response-time ms' ); - if (loggerFormat !== 'none') { + if (loggerFormat === 'foal') { + app.use(morgan( + (tokens: any, req: any, res: any) => { + return JSON.stringify({ + method: tokens.method(req, res), + url: tokens.url(req, res), + statusCode: parseInt(tokens.status(req, res), 10), + contentLength: tokens.res(req, res, 'content-length'), + responseTime: parseFloat(tokens['response-time'](req, res)), + }); + }, + { + stream: { + write: (message: string) => { + const data = JSON.parse(message); + logger.info(`${httpRequestMessagePrefix}${data.method} ${data.url}`, data); + }, + }, + } + )) + } else if (loggerFormat !== 'none') { + logger.warn('[CONFIG] Using another format than "foal" for "settings.loggerFormat" is deprecated.'); app.use(morgan(loggerFormat)); } @@ -113,13 +141,6 @@ export async function createApp( app.use(middleware); } - // Create the service and controller manager. - const services = options.serviceManager || new ServiceManager(); - app.foal = { services }; - - // Retrieve the logger. - const logger = services.get(Logger); - // Inject the OpenAPI service with an ID string to avoid duplicated singletons // across several npm packages. services.set(OPENAPI_SERVICE_ID, services.get(OpenApi)); diff --git a/packages/examples/config/default.yml b/packages/examples/config/default.yml index ee139f9d52..2b7a2a1c0f 100644 --- a/packages/examples/config/default.yml +++ b/packages/examples/config/default.yml @@ -3,6 +3,7 @@ port: 3001 settings: openapi: useHooks: true + loggerFormat: foal session: secret: 'my secret' staticPath: 'public/' diff --git a/packages/examples/config/development.yml b/packages/examples/config/development.yml index c4ec70c649..259f5fb0fa 100644 --- a/packages/examples/config/development.yml +++ b/packages/examples/config/development.yml @@ -1,5 +1,4 @@ settings: debug: true - loggerFormat: dev logger: format: dev \ No newline at end of file From a3e31f24d6043a5ab74797dbbd62222311517873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Fri, 20 Oct 2023 14:50:58 +0200 Subject: [PATCH 19/37] Do not log query params --- packages/core/src/express/create-app.spec.ts | 2 +- packages/core/src/express/create-app.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/express/create-app.spec.ts b/packages/core/src/express/create-app.spec.ts index 9cedda2e69..4cf8e376a7 100644 --- a/packages/core/src/express/create-app.spec.ts +++ b/packages/core/src/express/create-app.spec.ts @@ -688,7 +688,7 @@ describe('createApp', () => { }); await request(app) - .get('/a') + .get('/a?apiKey=a_secret_api_key') .expect(200); strictEqual(loggerMock.mock.callCount(), 1); diff --git a/packages/core/src/express/create-app.ts b/packages/core/src/express/create-app.ts index 5236b859bb..2c1df633e0 100644 --- a/packages/core/src/express/create-app.ts +++ b/packages/core/src/express/create-app.ts @@ -98,7 +98,7 @@ export async function createApp( (tokens: any, req: any, res: any) => { return JSON.stringify({ method: tokens.method(req, res), - url: tokens.url(req, res), + url: tokens.url(req, res).split('?')[0], statusCode: parseInt(tokens.status(req, res), 10), contentLength: tokens.res(req, res, 'content-length'), responseTime: parseFloat(tokens['response-time'](req, res)), From dbba1b8943df2892d55abf93e4a2010e6904761b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Fri, 20 Oct 2023 21:43:00 +0200 Subject: [PATCH 20/37] Fix linting --- packages/core/src/express/create-app.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/express/create-app.spec.ts b/packages/core/src/express/create-app.spec.ts index 4cf8e376a7..300edcc165 100644 --- a/packages/core/src/express/create-app.spec.ts +++ b/packages/core/src/express/create-app.spec.ts @@ -726,7 +726,7 @@ describe('createApp', () => { const logger = serviceManager.get(Logger); const loggerMock = mock.method(logger, 'warn', () => {}); - const app = await createApp(AppController, { + await createApp(AppController, { serviceManager }); From 10490b83c08d760290a8a61f6895d50f76fb8c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Fri, 20 Oct 2023 21:48:58 +0200 Subject: [PATCH 21/37] [CLI] Use Foal logger for HTTP requests --- packages/cli/src/generate/specs/app/config/default.json | 2 +- .../cli/src/generate/specs/app/config/default.mongodb.json | 4 ++-- .../cli/src/generate/specs/app/config/default.mongodb.yml | 2 +- packages/cli/src/generate/specs/app/config/default.yml | 2 +- packages/cli/src/generate/specs/app/config/development.json | 1 - packages/cli/src/generate/specs/app/config/development.yml | 1 - packages/cli/src/generate/templates/app/config/default.json | 2 +- .../src/generate/templates/app/config/default.mongodb.json | 4 ++-- .../cli/src/generate/templates/app/config/default.mongodb.yml | 2 +- packages/cli/src/generate/templates/app/config/default.yml | 2 +- .../cli/src/generate/templates/app/config/development.json | 1 - .../cli/src/generate/templates/app/config/development.yml | 1 - 12 files changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/cli/src/generate/specs/app/config/default.json b/packages/cli/src/generate/specs/app/config/default.json index 23b9f6e127..89e6cd04c8 100644 --- a/packages/cli/src/generate/specs/app/config/default.json +++ b/packages/cli/src/generate/specs/app/config/default.json @@ -1,7 +1,7 @@ { "port": "env(PORT)", "settings": { - "loggerFormat": "tiny", + "loggerFormat": "foal", "session": { "store": "@foal/typeorm" } diff --git a/packages/cli/src/generate/specs/app/config/default.mongodb.json b/packages/cli/src/generate/specs/app/config/default.mongodb.json index cbe36e70aa..1e975a3cd8 100644 --- a/packages/cli/src/generate/specs/app/config/default.mongodb.json +++ b/packages/cli/src/generate/specs/app/config/default.mongodb.json @@ -1,10 +1,10 @@ { "port": "env(PORT)", "settings": { - "loggerFormat": "tiny" + "loggerFormat": "foal" }, "database": { "type": "mongodb", "url": "mongodb://localhost:27017/test-foo-bar" } -} +} \ No newline at end of file diff --git a/packages/cli/src/generate/specs/app/config/default.mongodb.yml b/packages/cli/src/generate/specs/app/config/default.mongodb.yml index ae86ee05bc..33bd271229 100644 --- a/packages/cli/src/generate/specs/app/config/default.mongodb.yml +++ b/packages/cli/src/generate/specs/app/config/default.mongodb.yml @@ -1,7 +1,7 @@ port: env(PORT) settings: - loggerFormat: tiny + loggerFormat: foal database: type: mongodb diff --git a/packages/cli/src/generate/specs/app/config/default.yml b/packages/cli/src/generate/specs/app/config/default.yml index 664a2b41f7..b8d452a4a6 100644 --- a/packages/cli/src/generate/specs/app/config/default.yml +++ b/packages/cli/src/generate/specs/app/config/default.yml @@ -1,7 +1,7 @@ port: env(PORT) settings: - loggerFormat: tiny + loggerFormat: foal session: store: '@foal/typeorm' diff --git a/packages/cli/src/generate/specs/app/config/development.json b/packages/cli/src/generate/specs/app/config/development.json index a9481b4c2e..dbf4a581d8 100644 --- a/packages/cli/src/generate/specs/app/config/development.json +++ b/packages/cli/src/generate/specs/app/config/development.json @@ -1,7 +1,6 @@ { "settings": { "debug": true, - "loggerFormat": "dev", "logger": { "format": "dev" } diff --git a/packages/cli/src/generate/specs/app/config/development.yml b/packages/cli/src/generate/specs/app/config/development.yml index c4ec70c649..259f5fb0fa 100644 --- a/packages/cli/src/generate/specs/app/config/development.yml +++ b/packages/cli/src/generate/specs/app/config/development.yml @@ -1,5 +1,4 @@ settings: debug: true - loggerFormat: dev logger: format: dev \ No newline at end of file diff --git a/packages/cli/src/generate/templates/app/config/default.json b/packages/cli/src/generate/templates/app/config/default.json index 23b9f6e127..89e6cd04c8 100644 --- a/packages/cli/src/generate/templates/app/config/default.json +++ b/packages/cli/src/generate/templates/app/config/default.json @@ -1,7 +1,7 @@ { "port": "env(PORT)", "settings": { - "loggerFormat": "tiny", + "loggerFormat": "foal", "session": { "store": "@foal/typeorm" } diff --git a/packages/cli/src/generate/templates/app/config/default.mongodb.json b/packages/cli/src/generate/templates/app/config/default.mongodb.json index 1f1a4f7734..f0e91cf5e5 100644 --- a/packages/cli/src/generate/templates/app/config/default.mongodb.json +++ b/packages/cli/src/generate/templates/app/config/default.mongodb.json @@ -1,10 +1,10 @@ { "port": "env(PORT)", "settings": { - "loggerFormat": "tiny" + "loggerFormat": "foal" }, "database": { "type": "mongodb", "url": "mongodb://localhost:27017//* kebabName */" } -} +} \ No newline at end of file diff --git a/packages/cli/src/generate/templates/app/config/default.mongodb.yml b/packages/cli/src/generate/templates/app/config/default.mongodb.yml index 88d0e5b673..2afcb89e21 100644 --- a/packages/cli/src/generate/templates/app/config/default.mongodb.yml +++ b/packages/cli/src/generate/templates/app/config/default.mongodb.yml @@ -1,7 +1,7 @@ port: env(PORT) settings: - loggerFormat: tiny + loggerFormat: foal database: type: mongodb diff --git a/packages/cli/src/generate/templates/app/config/default.yml b/packages/cli/src/generate/templates/app/config/default.yml index 664a2b41f7..b8d452a4a6 100644 --- a/packages/cli/src/generate/templates/app/config/default.yml +++ b/packages/cli/src/generate/templates/app/config/default.yml @@ -1,7 +1,7 @@ port: env(PORT) settings: - loggerFormat: tiny + loggerFormat: foal session: store: '@foal/typeorm' diff --git a/packages/cli/src/generate/templates/app/config/development.json b/packages/cli/src/generate/templates/app/config/development.json index a9481b4c2e..dbf4a581d8 100644 --- a/packages/cli/src/generate/templates/app/config/development.json +++ b/packages/cli/src/generate/templates/app/config/development.json @@ -1,7 +1,6 @@ { "settings": { "debug": true, - "loggerFormat": "dev", "logger": { "format": "dev" } diff --git a/packages/cli/src/generate/templates/app/config/development.yml b/packages/cli/src/generate/templates/app/config/development.yml index c4ec70c649..259f5fb0fa 100644 --- a/packages/cli/src/generate/templates/app/config/development.yml +++ b/packages/cli/src/generate/templates/app/config/development.yml @@ -1,5 +1,4 @@ settings: debug: true - loggerFormat: dev logger: format: dev \ No newline at end of file From b7cc76728131613c374a9887cb26d9a4f0d68876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Fri, 20 Oct 2023 23:19:43 +0200 Subject: [PATCH 22/37] [CLI] Rever config file changes --- packages/cli/src/generate/specs/app/config/e2e.json | 5 +---- packages/cli/src/generate/specs/app/config/e2e.mongodb.json | 5 +---- packages/cli/src/generate/specs/app/config/e2e.mongodb.yml | 2 -- packages/cli/src/generate/specs/app/config/e2e.yml | 2 -- packages/cli/src/generate/templates/app/config/e2e.json | 5 +---- .../cli/src/generate/templates/app/config/e2e.mongodb.json | 5 +---- .../cli/src/generate/templates/app/config/e2e.mongodb.yml | 2 -- packages/cli/src/generate/templates/app/config/e2e.yml | 2 -- 8 files changed, 4 insertions(+), 24 deletions(-) diff --git a/packages/cli/src/generate/specs/app/config/e2e.json b/packages/cli/src/generate/specs/app/config/e2e.json index 052e0449c5..3aa0c9d436 100644 --- a/packages/cli/src/generate/specs/app/config/e2e.json +++ b/packages/cli/src/generate/specs/app/config/e2e.json @@ -1,9 +1,6 @@ { "settings": { - "loggerFormat": "none", - "logger": { - "format": "none" - } + "loggerFormat": "none" }, "database": { "database": "./e2e_db.sqlite3", diff --git a/packages/cli/src/generate/specs/app/config/e2e.mongodb.json b/packages/cli/src/generate/specs/app/config/e2e.mongodb.json index 7de247fad9..0557ec9654 100644 --- a/packages/cli/src/generate/specs/app/config/e2e.mongodb.json +++ b/packages/cli/src/generate/specs/app/config/e2e.mongodb.json @@ -1,9 +1,6 @@ { "settings": { - "loggerFormat": "none", - "logger": { - "format": "none" - } + "loggerFormat": "none" }, "database": { "url": "mongodb://localhost:27017/e2e-test-foo-bar" diff --git a/packages/cli/src/generate/specs/app/config/e2e.mongodb.yml b/packages/cli/src/generate/specs/app/config/e2e.mongodb.yml index 7bc7bb3448..281daa0052 100644 --- a/packages/cli/src/generate/specs/app/config/e2e.mongodb.yml +++ b/packages/cli/src/generate/specs/app/config/e2e.mongodb.yml @@ -1,7 +1,5 @@ settings: loggerFormat: 'none' - logger: - format: 'none' database: url: mongodb://localhost:27017/e2e-test-foo-bar diff --git a/packages/cli/src/generate/specs/app/config/e2e.yml b/packages/cli/src/generate/specs/app/config/e2e.yml index 60f92f0c56..06a4add77f 100644 --- a/packages/cli/src/generate/specs/app/config/e2e.yml +++ b/packages/cli/src/generate/specs/app/config/e2e.yml @@ -1,7 +1,5 @@ settings: loggerFormat: 'none' - logger: - format: 'none' database: database: './e2e_db.sqlite3' diff --git a/packages/cli/src/generate/templates/app/config/e2e.json b/packages/cli/src/generate/templates/app/config/e2e.json index 052e0449c5..3aa0c9d436 100644 --- a/packages/cli/src/generate/templates/app/config/e2e.json +++ b/packages/cli/src/generate/templates/app/config/e2e.json @@ -1,9 +1,6 @@ { "settings": { - "loggerFormat": "none", - "logger": { - "format": "none" - } + "loggerFormat": "none" }, "database": { "database": "./e2e_db.sqlite3", diff --git a/packages/cli/src/generate/templates/app/config/e2e.mongodb.json b/packages/cli/src/generate/templates/app/config/e2e.mongodb.json index 6ad1e7e7e4..0526879660 100644 --- a/packages/cli/src/generate/templates/app/config/e2e.mongodb.json +++ b/packages/cli/src/generate/templates/app/config/e2e.mongodb.json @@ -1,9 +1,6 @@ { "settings": { - "loggerFormat": "none", - "logger": { - "format": "none" - } + "loggerFormat": "none" }, "database": { "url": "mongodb://localhost:27017/e2e-/* kebabName */" diff --git a/packages/cli/src/generate/templates/app/config/e2e.mongodb.yml b/packages/cli/src/generate/templates/app/config/e2e.mongodb.yml index 609d06f307..f3ea49f64c 100644 --- a/packages/cli/src/generate/templates/app/config/e2e.mongodb.yml +++ b/packages/cli/src/generate/templates/app/config/e2e.mongodb.yml @@ -1,7 +1,5 @@ settings: loggerFormat: 'none' - logger: - format: 'none' database: url: mongodb://localhost:27017/e2e-/* kebabName */ diff --git a/packages/cli/src/generate/templates/app/config/e2e.yml b/packages/cli/src/generate/templates/app/config/e2e.yml index 60f92f0c56..06a4add77f 100644 --- a/packages/cli/src/generate/templates/app/config/e2e.yml +++ b/packages/cli/src/generate/templates/app/config/e2e.yml @@ -1,7 +1,5 @@ settings: loggerFormat: 'none' - logger: - format: 'none' database: database: './e2e_db.sqlite3' From f096bd4e5fb2a915ee7480ccdea07da39c1cda82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Fri, 20 Oct 2023 23:19:57 +0200 Subject: [PATCH 23/37] Pretty display error in @foal/examples --- packages/examples/src/app/controllers/profile.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/examples/src/app/controllers/profile.controller.ts b/packages/examples/src/app/controllers/profile.controller.ts index 21e21eeb98..7a49bb1881 100644 --- a/packages/examples/src/app/controllers/profile.controller.ts +++ b/packages/examples/src/app/controllers/profile.controller.ts @@ -31,7 +31,7 @@ export class ProfileController { try { await this.disk.delete(user.profile); } catch (error: any) { - this.logger.error(error.message, { err: error }); + this.logger.error(error.message, { error }); } } From 1599a9b020bd55ea9746d3f9b22b89a9389c8a66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 21 Oct 2023 12:17:41 +0200 Subject: [PATCH 24/37] Mark @Log as deprecated --- .../core/src/common/utils/log.hook.spec.ts | 24 +++++++++++++++++++ packages/core/src/common/utils/log.hook.ts | 7 +++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/core/src/common/utils/log.hook.spec.ts b/packages/core/src/common/utils/log.hook.spec.ts index 1715f39907..c5f2920b09 100644 --- a/packages/core/src/common/utils/log.hook.spec.ts +++ b/packages/core/src/common/utils/log.hook.spec.ts @@ -4,6 +4,8 @@ import { strictEqual } from 'assert'; // FoalTS import { Context, getHookFunction, ServiceManager } from '../../core'; import { Log } from './log.hook'; +import { mock } from 'node:test'; +import { Logger } from '../logging'; describe('Log', () => { @@ -15,6 +17,28 @@ describe('Log', () => { logFn = (...args) => msgs.push(args); }); + afterEach(() => { + mock.reset(); + }) + + it('should log a deprecation message.', () => { + const hook = getHookFunction(Log('foo', { logFn })); + + const ctx = new Context({}); + const services = new ServiceManager(); + + const logger = services.get(Logger); + const loggerMock = mock.method(logger, 'warn'); + + hook(ctx, services); + + strictEqual(loggerMock.mock.callCount(), 1); + strictEqual( + loggerMock.mock.calls[0].arguments[0], + 'Using the @Log hook is deprecated. Use the Logger service in a custom hook instead.' + ); + }); + it('should log the message.', () => { const hook = getHookFunction(Log('foo', { logFn })); diff --git a/packages/core/src/common/utils/log.hook.ts b/packages/core/src/common/utils/log.hook.ts index 067d349ccf..871d2c3cc5 100644 --- a/packages/core/src/common/utils/log.hook.ts +++ b/packages/core/src/common/utils/log.hook.ts @@ -1,4 +1,5 @@ import { Context, Hook, HookDecorator } from '../../core'; +import { Logger } from '../logging'; /** * Options of the `Log` hook. @@ -25,13 +26,17 @@ export interface LogOptions { * Hook factory logging a message with optional information on the HTTP request. * * @export + * @deprecated Use the Logger service in a custom hook instead. * @param {string} message - The message to print on each request. * @param {LogOptions} [options={}] - Options to specify which information on the HTTP request should be printed. * @returns {HookDecorator} The hook. */ export function Log(message: string, options: LogOptions = {}): HookDecorator { const logFn = options.logFn || console.log; - return Hook((ctx: Context) => { + return Hook((ctx: Context, services) => { + const logger = services.get(Logger); + logger.warn('Using the @Log hook is deprecated. Use the Logger service in a custom hook instead.'); + logFn(message); if (options.body) { logFn('Body: ', ctx.request.body); From 00baeca6328d2b6deb54de773a9af3ebad6547b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 21 Oct 2023 16:38:19 +0200 Subject: [PATCH 25/37] Add random ID on each request --- packages/core/src/core/http/context.ts | 3 ++ packages/core/src/express/create-app.spec.ts | 55 ++++++++++++++++++++ packages/core/src/express/create-app.ts | 12 +++++ 3 files changed, 70 insertions(+) diff --git a/packages/core/src/core/http/context.ts b/packages/core/src/core/http/context.ts index 622bdb6903..f0db4e3c1f 100644 --- a/packages/core/src/core/http/context.ts +++ b/packages/core/src/core/http/context.ts @@ -58,6 +58,9 @@ interface Request extends IncomingMessage { // This line has been added based on @types/express in order not to make the url possibly undefined. url: string; + // Extra attribute added for Foal + id: string; + // This line has been added based on @types/express but it is not present in Express official documentation. accepts(): string[]; accepts(types: string|string[]): string|false; diff --git a/packages/core/src/express/create-app.spec.ts b/packages/core/src/express/create-app.spec.ts index 300edcc165..f8893f308d 100644 --- a/packages/core/src/express/create-app.spec.ts +++ b/packages/core/src/express/create-app.spec.ts @@ -735,4 +735,59 @@ describe('createApp', () => { }); }); + context('given a "X-Request-ID" header is present in the request', () => { + it('should add the request ID to the request object.', async () => { + const requestId = 'a_request_id'; + + class AppController { + @Get('/') + get(ctx: Context) { + return new HttpResponseOK({ + requestId: ctx.request.id + }); + } + } + + const serviceManager = new ServiceManager(); + const app = await createApp(AppController, { + serviceManager + }); + + await request(app) + .get('/') + .set('X-Request-ID', requestId) + .expect(200) + .expect({ + requestId, + }); + }); + }); + + context('given a "X-Request-ID" header is NOT present in the request', () => { + it('should add a request ID to the request object.', async () => { + class AppController { + @Get('/') + get(ctx: Context) { + return new HttpResponseOK({ + requestId: ctx.request.id + }); + } + } + + const serviceManager = new ServiceManager(); + const app = await createApp(AppController, { + serviceManager + }); + + await request(app) + .get('/') + .expect(200) + .expect(response => { + if (!response.body.requestId) { + throw new Error('The request ID should exist.'); + } + }); + + }); + }); }); diff --git a/packages/core/src/express/create-app.ts b/packages/core/src/express/create-app.ts index 2c1df633e0..6ad60b9e63 100644 --- a/packages/core/src/express/create-app.ts +++ b/packages/core/src/express/create-app.ts @@ -1,3 +1,6 @@ +// std +import { randomUUID } from 'node:crypto'; + // 3p import * as cookieParser from 'cookie-parser'; import * as express from 'express'; @@ -87,6 +90,15 @@ export async function createApp( // Retrieve the logger. const logger = services.get(Logger); + // Generate a unique ID for each request. + app.use((req: any, res: any, next: (err?: any) => any) => { + const requestId = req.get('x-request-id') || randomUUID(); + + req.id = requestId; + + next(); + }); + // Log requests. const loggerFormat = Config.get( 'settings.loggerFormat', From 0a82607483ce18482cee11b4439d3c8d8f436e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 21 Oct 2023 17:01:51 +0200 Subject: [PATCH 26/37] Add Logger.log --- .../core/src/common/logging/logger.spec.ts | 115 +++++++++--------- packages/core/src/common/logging/logger.ts | 60 ++++----- 2 files changed, 87 insertions(+), 88 deletions(-) diff --git a/packages/core/src/common/logging/logger.spec.ts b/packages/core/src/common/logging/logger.spec.ts index 49cfc6b119..af26bf542f 100644 --- a/packages/core/src/common/logging/logger.spec.ts +++ b/packages/core/src/common/logging/logger.spec.ts @@ -3,102 +3,101 @@ import { strictEqual } from 'assert'; import { mock } from 'node:test'; // FoalTS -import { Logger, log } from './logger'; +import { Logger } from './logger'; import { Config } from '../../core'; -describe('log', () => { +describe('Logger', () => { + afterEach(() => { mock.reset(); Config.remove('settings.logger.format'); Config.remove('settings.logger.logLevel'); - }); + }) - context('given the configuration "settings.logger.format" is NOT defined', () => { - it('should log the message to a raw text.', () => { - const consoleMock = mock.method(console, 'log', () => {}); + describe('has a "log" method that', () => { - log('error', 'Hello world', {}); + context('given the configuration "settings.logger.format" is NOT defined', () => { + it('should log the message to a raw text.', () => { + const consoleMock = mock.method(console, 'log', () => {}); - strictEqual(consoleMock.mock.callCount(), 1); + const logger = new Logger(); + logger.log('error', 'Hello world', {}); - const loggedMessage = consoleMock.mock.calls[0].arguments[0]; + strictEqual(consoleMock.mock.callCount(), 1); - strictEqual(loggedMessage.includes('[ERROR]'), true); - }) - }); + const loggedMessage = consoleMock.mock.calls[0].arguments[0]; - context('given the configuration "settings.logger.format" is "json"', () => { - it('should log the message to a JSON.', () => { - Config.set('settings.logger.format', 'json'); + strictEqual(loggedMessage.includes('[ERROR]'), true); + }) + }); - const consoleMock = mock.method(console, 'log', () => {}); + context('given the configuration "settings.logger.format" is "json"', () => { + it('should log the message to a JSON.', () => { + Config.set('settings.logger.format', 'json'); - log('error', 'Hello world', {}); + const consoleMock = mock.method(console, 'log', () => {}); - strictEqual(consoleMock.mock.callCount(), 1); + const logger = new Logger(); + logger.log('error', 'Hello world', {}); - const loggedMessage = consoleMock.mock.calls[0].arguments[0]; - const json = JSON.parse(loggedMessage); + strictEqual(consoleMock.mock.callCount(), 1); - strictEqual(json.level, 'error'); + const loggedMessage = consoleMock.mock.calls[0].arguments[0]; + const json = JSON.parse(loggedMessage); + + strictEqual(json.level, 'error'); + }); }); - }); - context('given the configuration "settings.logger.format" is "none"', () => { - it('should log nothing.', () => { - Config.set('settings.logger.format', 'none'); + context('given the configuration "settings.logger.format" is "none"', () => { + it('should log nothing.', () => { + Config.set('settings.logger.format', 'none'); - const consoleMock = mock.method(console, 'log', () => {}); + const consoleMock = mock.method(console, 'log', () => {}); - log('error', 'Hello world', {}); + const logger = new Logger(); + logger.log('error', 'Hello world', {}); - strictEqual(consoleMock.mock.callCount(), 0); + strictEqual(consoleMock.mock.callCount(), 0); + }); }); - }); - context('given the configuration "settings.logger.logLevel" is NOT defined', () => { - it('should log messages based on an "INFO" log level', () => { - const consoleMock = mock.method(console, 'log', () => {}); + context('given the configuration "settings.logger.logLevel" is NOT defined', () => { + it('should log messages based on an "INFO" log level', () => { + const consoleMock = mock.method(console, 'log', () => {}); - log('info', 'Hello world', {}); - strictEqual(consoleMock.mock.callCount(), 1); + const logger = new Logger(); + logger.log('info', 'Hello world', {}); + strictEqual(consoleMock.mock.callCount(), 1); - consoleMock.mock.resetCalls(); + consoleMock.mock.resetCalls(); - log('debug', 'Hello world', {}); - strictEqual(consoleMock.mock.callCount(), 0); + logger.log('debug', 'Hello world', {}); + strictEqual(consoleMock.mock.callCount(), 0); + }); }); - }); - context('given the configuration "settings.logger.logLevel" is "warn"', () => { - it('should log messages based on a "WARN" log level', () => { - Config.set('settings.logger.logLevel', 'warn'); + context('given the configuration "settings.logger.logLevel" is "warn"', () => { + it('should log messages based on a "WARN" log level', () => { + Config.set('settings.logger.logLevel', 'warn'); - const consoleMock = mock.method(console, 'log', () => {}); + const consoleMock = mock.method(console, 'log', () => {}); - log('warn', 'Hello world', {}); - strictEqual(consoleMock.mock.callCount(), 1); + const logger = new Logger(); + logger.log('warn', 'Hello world', {}); + strictEqual(consoleMock.mock.callCount(), 1); - consoleMock.mock.resetCalls(); + consoleMock.mock.resetCalls(); - log('info', 'Hello world', {}); - strictEqual(consoleMock.mock.callCount(), 0); + logger.log('info', 'Hello world', {}); + strictEqual(consoleMock.mock.callCount(), 0); + }); }); }); -}); -describe('Logger', () => { - - beforeEach(() => { + it('has a debug(...args) method which is an alias for log("debug", ...args)', () => { Config.set('settings.logger.logLevel', 'debug'); - }); - - afterEach(() => { - mock.reset(); - Config.remove('settings.logger.logLevel'); - }) - it('has a debug(...args) method which is an alias for log("debug", ...args)', () => { const logger = new Logger(); const consoleMock = mock.method(console, 'log', () => {}); diff --git a/packages/core/src/common/logging/logger.ts b/packages/core/src/common/logging/logger.ts index 1dadb0d066..9a65d801b9 100644 --- a/packages/core/src/common/logging/logger.ts +++ b/packages/core/src/common/logging/logger.ts @@ -1,47 +1,47 @@ import { Config } from '../../core'; import { Level, formatMessage, shouldLog } from './logger.utils'; -export function log( - level: Level, - message: string, - params: { [name: string]: any } -): void { - const format = Config.get('settings.logger.format', 'string', 'raw'); - if (format === 'none') { - return; +export class Logger { + log( + level: Level, + message: string, + params: { [name: string]: any } = {} + ): void { + const format = Config.get('settings.logger.format', 'string', 'raw'); + if (format === 'none') { + return; + } + + const configLogLevel = Config.get('settings.logger.logLevel', 'string', 'info'); + if (!shouldLog(level, configLogLevel)) { + return; + }; + + const now = new Date(); + const formattedMessage = formatMessage( + level, + message, + params, + format, + now, + ); + + console.log(formattedMessage); } - const configLogLevel = Config.get('settings.logger.logLevel', 'string', 'info'); - if (!shouldLog(level, configLogLevel)) { - return; - }; - - const now = new Date(); - const formattedMessage = formatMessage( - level, - message, - params, - format, - now, - ); - - console.log(formattedMessage); -} - -export class Logger { debug(message: string, params: { error?: Error, [name: string]: any } = {}): void { - log('debug', message, params); + this.log('debug', message, params); } info(message: string, params: { error?: Error, [name: string]: any } = {}): void { - log('info', message, params); + this.log('info', message, params); } warn(message: string, params: { error?: Error, [name: string]: any } = {}): void { - log('warn', message, params); + this.log('warn', message, params); } error(message: string, params: { error?: Error, [name: string]: any } = {}): void { - log('error', message, params); + this.log('error', message, params); } } \ No newline at end of file From 0ca1b30db836838ed51f7dd6d7b3b7d4fa52ad24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 21 Oct 2023 19:16:17 +0200 Subject: [PATCH 27/37] Allow to add context to Logger --- .../core/src/common/logging/logger.spec.ts | 74 ++++++++++++++++++- packages/core/src/common/logging/logger.ts | 25 ++++++- packages/core/src/express/create-app.spec.ts | 37 ++++++++++ packages/core/src/express/create-app.ts | 5 ++ 4 files changed, 137 insertions(+), 4 deletions(-) diff --git a/packages/core/src/common/logging/logger.spec.ts b/packages/core/src/common/logging/logger.spec.ts index af26bf542f..0c2e378c6f 100644 --- a/packages/core/src/common/logging/logger.spec.ts +++ b/packages/core/src/common/logging/logger.spec.ts @@ -1,5 +1,5 @@ // std -import { strictEqual } from 'assert'; +import { notStrictEqual, strictEqual } from 'assert'; import { mock } from 'node:test'; // FoalTS @@ -16,18 +16,85 @@ describe('Logger', () => { describe('has a "log" method that', () => { + context('given the log context has been initialized', () => { + it('should log the message with the context.', () => { + const consoleMock = mock.method(console, 'log', () => {}); + + const logger = new Logger(); + logger.initLogContext(() => { + logger.addLogContext('foo', 'bar'); + logger.log('error', 'Hello world', {}); + }); + logger.initLogContext(() => { + logger.addLogContext('foo2', 'bar2'); + logger.log('error', 'Hello world 2', {}); + }); + + strictEqual(consoleMock.mock.callCount(), 2); + + const loggedMessage = consoleMock.mock.calls[0].arguments[0]; + + strictEqual(loggedMessage.includes('[ERROR]'), true); + strictEqual(loggedMessage.includes('foo: "bar"'), true); + notStrictEqual(loggedMessage.includes('foo2: "bar2"'), true); + + const loggedMessage2 = consoleMock.mock.calls[1].arguments[0]; + + strictEqual(loggedMessage2.includes('[ERROR]'), true); + strictEqual(loggedMessage2.includes('foo2: "bar2"'), true); + notStrictEqual(loggedMessage2.includes('foo: "bar"'), true); + }); + + it('should let given params override the context.', () => { + const consoleMock = mock.method(console, 'log', () => {}); + + const logger = new Logger(); + logger.initLogContext(() => { + logger.addLogContext('foo', 'bar'); + logger.log('error', 'Hello world', { foo: 'bar2' }); + }); + + strictEqual(consoleMock.mock.callCount(), 1); + + const loggedMessage = consoleMock.mock.calls[0].arguments[0]; + + strictEqual(loggedMessage.includes('[ERROR]'), true); + strictEqual(loggedMessage.includes('foo: "bar2"'), true); + }); + }); + + context('given the log context has NOT been initialized', () => { + it('should log a warning message when adding log context.', () => { + const consoleMock = mock.method(console, 'log', () => {}); + + const logger = new Logger(); + logger.addLogContext('foo', 'bar'); + + strictEqual(consoleMock.mock.callCount(), 1); + + const loggedMessage = consoleMock.mock.calls[0].arguments[0]; + + strictEqual(loggedMessage.includes('[WARN]'), true); + strictEqual( + loggedMessage.includes('Impossible to add log context information. The logger context has not been initialized.'), + true + ); + }); + }); + context('given the configuration "settings.logger.format" is NOT defined', () => { it('should log the message to a raw text.', () => { const consoleMock = mock.method(console, 'log', () => {}); const logger = new Logger(); - logger.log('error', 'Hello world', {}); + logger.log('error', 'Hello world', { foobar: 'bar' }); strictEqual(consoleMock.mock.callCount(), 1); const loggedMessage = consoleMock.mock.calls[0].arguments[0]; strictEqual(loggedMessage.includes('[ERROR]'), true); + strictEqual(loggedMessage.includes('foobar: "bar"'), true); }) }); @@ -38,7 +105,7 @@ describe('Logger', () => { const consoleMock = mock.method(console, 'log', () => {}); const logger = new Logger(); - logger.log('error', 'Hello world', {}); + logger.log('error', 'Hello world', { foobar: 'bar' }); strictEqual(consoleMock.mock.callCount(), 1); @@ -46,6 +113,7 @@ describe('Logger', () => { const json = JSON.parse(loggedMessage); strictEqual(json.level, 'error'); + strictEqual(json.foobar, 'bar'); }); }); diff --git a/packages/core/src/common/logging/logger.ts b/packages/core/src/common/logging/logger.ts index 9a65d801b9..f0da7a3b1c 100644 --- a/packages/core/src/common/logging/logger.ts +++ b/packages/core/src/common/logging/logger.ts @@ -1,7 +1,26 @@ +// std +import { AsyncLocalStorage } from 'node:async_hooks'; + +// FoalTS import { Config } from '../../core'; import { Level, formatMessage, shouldLog } from './logger.utils'; export class Logger { + private asyncLocalStorage = new AsyncLocalStorage>(); + + initLogContext(callback: () => void): void { + this.asyncLocalStorage.run({}, callback); + } + + addLogContext(name: string, value: any): void { + const store = this.asyncLocalStorage.getStore(); + if (!store) { + this.log('warn', 'Impossible to add log context information. The logger context has not been initialized.'); + return; + } + store[name] = value; + } + log( level: Level, message: string, @@ -18,10 +37,14 @@ export class Logger { }; const now = new Date(); + const contextParams = this.asyncLocalStorage.getStore(); const formattedMessage = formatMessage( level, message, - params, + { + ...contextParams, + ...params, + }, format, now, ); diff --git a/packages/core/src/express/create-app.spec.ts b/packages/core/src/express/create-app.spec.ts index f8893f308d..048b42784d 100644 --- a/packages/core/src/express/create-app.spec.ts +++ b/packages/core/src/express/create-app.spec.ts @@ -16,6 +16,7 @@ import { dependency, Get, Head, + Hook, HttpResponseOK, OpenApi, Options, @@ -56,6 +57,7 @@ describe('createApp', () => { Config.remove('settings.bodyParser.limit'); Config.remove('settings.cookieParser.secret'); Config.remove('settings.loggerFormat'); + Config.remove('settings.logger.format'); }); const cookieSecret = 'strong-secret'; @@ -735,6 +737,41 @@ describe('createApp', () => { }); }); + it('should allow to add log context information.', async () => { + Config.set('settings.logger.format', 'json'); + + class AppController { + @dependency + logger: Logger; + + @Get('/') + @Hook((ctx, services) => { + const logger = services.get(Logger); + logger.addLogContext('foo', 'bar'); + }) + getA(ctx: Context) { + this.logger.info('Hello world'); + return new HttpResponseOK(); + } + } + + const serviceManager = new ServiceManager(); + + const consoleMock = mock.method(console, 'log', () => {}); + + const app = await createApp(AppController, { + serviceManager + }); + + await request(app) + .get('/') + .expect(200); + + const messages = consoleMock.mock.calls.map(call => JSON.parse(call.arguments[0])); + + strictEqual(messages.some(message => message.foo === 'bar'), true); + }); + context('given a "X-Request-ID" header is present in the request', () => { it('should add the request ID to the request object.', async () => { const requestId = 'a_request_id'; diff --git a/packages/core/src/express/create-app.ts b/packages/core/src/express/create-app.ts index 6ad60b9e63..08d10e037d 100644 --- a/packages/core/src/express/create-app.ts +++ b/packages/core/src/express/create-app.ts @@ -90,6 +90,11 @@ export async function createApp( // Retrieve the logger. const logger = services.get(Logger); + // Allow to add log context. + app.use((req: any, res: any, next: (err?: any) => any) => { + logger.initLogContext(next); + }); + // Generate a unique ID for each request. app.use((req: any, res: any, next: (err?: any) => any) => { const requestId = req.get('x-request-id') || randomUUID(); From 39e63e61e3a1754ce0320502da436c06a5daaa29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 21 Oct 2023 19:22:42 +0200 Subject: [PATCH 28/37] Add request ID to log context --- packages/core/src/express/create-app.spec.ts | 33 ++++++++++++++++++++ packages/core/src/express/create-app.ts | 1 + 2 files changed, 34 insertions(+) diff --git a/packages/core/src/express/create-app.spec.ts b/packages/core/src/express/create-app.spec.ts index 048b42784d..afaa3b1ebe 100644 --- a/packages/core/src/express/create-app.spec.ts +++ b/packages/core/src/express/create-app.spec.ts @@ -824,7 +824,40 @@ describe('createApp', () => { throw new Error('The request ID should exist.'); } }); + }); + }); + + it('should add the request ID to the log context.', async () => { + class AppController { + @Get('/') + get(ctx: Context) { + return new HttpResponseOK({ + requestId: ctx.request.id + }); + } + } + + const serviceManager = new ServiceManager(); + const logger = serviceManager.get(Logger); + const loggerMock = mock.method(logger, 'addLogContext', () => {}); + const app = await createApp(AppController, { + serviceManager }); + + let requestId: string|undefined; + await request(app) + .get('/') + .expect(200) + .then(response => { + requestId = response.body.requestId; + }) + + strictEqual(loggerMock.mock.callCount(), 1); + + const [key, value] = loggerMock.mock.calls[0].arguments; + + strictEqual(key, 'requestId'); + strictEqual(value, requestId); }); }); diff --git a/packages/core/src/express/create-app.ts b/packages/core/src/express/create-app.ts index 08d10e037d..38ff29faf5 100644 --- a/packages/core/src/express/create-app.ts +++ b/packages/core/src/express/create-app.ts @@ -100,6 +100,7 @@ export async function createApp( const requestId = req.get('x-request-id') || randomUUID(); req.id = requestId; + logger.addLogContext('requestId', requestId); next(); }); From 7b8bf3c43a3eff5ad9ecdaf4707cc5f7386331e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 21 Oct 2023 20:31:27 +0200 Subject: [PATCH 29/37] Add user ID to log context for sessions --- .../sessions/http/use-sessions.hook.spec.ts | 26 ++++++++++++++++++- .../src/sessions/http/use-sessions.hook.ts | 4 +++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/core/src/sessions/http/use-sessions.hook.spec.ts b/packages/core/src/sessions/http/use-sessions.hook.spec.ts index 6f011b5c11..224bb68267 100644 --- a/packages/core/src/sessions/http/use-sessions.hook.spec.ts +++ b/packages/core/src/sessions/http/use-sessions.hook.spec.ts @@ -1,5 +1,6 @@ // std import { deepStrictEqual, doesNotReject, rejects, strictEqual } from 'assert'; +import { mock } from 'node:test'; // FoalTS import { @@ -36,6 +37,7 @@ import { SessionStore, } from '../core'; import { UseSessions } from './use-sessions.hook'; +import { Logger } from '../../common'; describe('UseSessions', () => { @@ -513,7 +515,7 @@ describe('UseSessions', () => { it('should throw an error if the session state has no CSRF token.', async () => { ctx = createContextWithPostMethod({}, { [SESSION_DEFAULT_COOKIE_NAME]: anonymousSessionID }); return rejects( - () => hook(ctx, services), + async () => { await hook(ctx, services) }, { message: 'Unexpected error: the session content does not have a "csrfToken" field. ' + 'Are you sure you created the session with "createSession"?' @@ -605,12 +607,34 @@ describe('UseSessions', () => { strictEqual(ctx.user, null); }); + it('and add null as user ID to the log context.', async () => { + const logger = services.get(Logger); + const loggerMock = mock.method(logger, 'addLogContext', () => {}); + + await hook(ctx, services); + + strictEqual(loggerMock.mock.callCount(), 1); + + deepStrictEqual(loggerMock.mock.calls[0].arguments, ['userId', null]); + }); + }); context('given the session has a user ID', () => { beforeEach(() => ctx = createContext({ Authorization: `Bearer ${authenticatedSessionID}`})); + it('and add null as user ID to the log context.', async () => { + const logger = services.get(Logger); + const loggerMock = mock.method(logger, 'addLogContext', () => {}); + + await hook(ctx, services); + + strictEqual(loggerMock.mock.callCount(), 1); + + deepStrictEqual(loggerMock.mock.calls[0].arguments, ['userId', userId]); + }); + context('given options.user is not defined', () => { it('with the null value.', async () => { diff --git a/packages/core/src/sessions/http/use-sessions.hook.ts b/packages/core/src/sessions/http/use-sessions.hook.ts index a9e2225d68..9d69af00f2 100644 --- a/packages/core/src/sessions/http/use-sessions.hook.ts +++ b/packages/core/src/sessions/http/use-sessions.hook.ts @@ -22,6 +22,7 @@ import { checkUserIdType } from './check-user-id-type'; import { getSessionIDFromRequest, RequestValidationError } from './get-session-id-from-request'; import { createSession, readSession, SessionStore } from '../core'; import { getCsrfTokenFromRequest, removeSessionCookie, setSessionCookie, shouldVerifyCsrfToken } from './utils'; +import { Logger } from '../../common'; export type UseSessionOptions = { store?: Class; @@ -142,6 +143,9 @@ export function UseSessions(options: UseSessionOptions = {}): HookDecorator { /* Set ctx.user */ + const logger = services.get(Logger); + logger.addLogContext('userId', session.userId); + if (session.userId !== null && options.user) { const userId = checkUserIdType(session.userId, options.userIdType); ctx.user = await options.user(userId as never, services); From 0b809dd934cef1f4006563eb2d622c0b368137b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 21 Oct 2023 20:43:13 +0200 Subject: [PATCH 30/37] Add user ID to log context for JWT --- packages/jwt/src/http/jwt.hook.spec.ts | 23 +++++++++++++++++++++++ packages/jwt/src/http/jwt.hook.ts | 5 +++++ 2 files changed, 28 insertions(+) diff --git a/packages/jwt/src/http/jwt.hook.spec.ts b/packages/jwt/src/http/jwt.hook.spec.ts index 54162d8e28..9fe53f6301 100644 --- a/packages/jwt/src/http/jwt.hook.spec.ts +++ b/packages/jwt/src/http/jwt.hook.spec.ts @@ -1,5 +1,6 @@ // std import { deepStrictEqual, notStrictEqual, rejects, strictEqual } from 'assert'; +import { mock } from 'node:test'; // 3p import { @@ -20,6 +21,7 @@ import { isHttpResponseBadRequest, isHttpResponseForbidden, isHttpResponseUnauthorized, + Logger, ServiceManager } from '@foal/core'; import { sign } from 'jsonwebtoken'; @@ -631,6 +633,27 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: describe('should set Context.user', () => { + it('and shoud add the user ID to the log context.', async () => { + const jwt = sign({}, secret, { subject: '123' }); + ctx = createContext({ Authorization: `Bearer ${jwt}` }); + + hook = getHookFunction(JWT({ + user: async () => null, + userIdType: 'number' + })); + + const logger = services.get(Logger); + const loggerMock = mock.method(logger, 'addLogContext', () => {}); + + await hook(ctx, services); + + strictEqual(loggerMock.mock.callCount(), 1); + deepStrictEqual( + loggerMock.mock.calls[0].arguments, + ['userId', 123], + ); + }) + context('given options.user is not defined', () => { it('with the decoded payload (header & secret).', async () => { diff --git a/packages/jwt/src/http/jwt.hook.ts b/packages/jwt/src/http/jwt.hook.ts index 76716985e4..44d18db238 100644 --- a/packages/jwt/src/http/jwt.hook.ts +++ b/packages/jwt/src/http/jwt.hook.ts @@ -12,6 +12,7 @@ import { HttpResponseForbidden, HttpResponseUnauthorized, IApiSecurityScheme, + Logger, ServiceManager } from '@foal/core'; import { decode, verify } from 'jsonwebtoken'; @@ -198,6 +199,10 @@ export function JWT(required: boolean, options: JWTOptions, verifyOptions: Verif } const userId = checkAndConvertUserIdType(payload.sub, options.userIdType); + + const logger = services.get(Logger); + logger.addLogContext('userId', userId); + const user = await options.user(userId as never, services); if (!user) { return new InvalidTokenResponse('The token subject does not match any user.'); From 37808375adc1c42937746cec44c348b6334f1dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sat, 21 Oct 2023 20:49:01 +0200 Subject: [PATCH 31/37] Make use of mocks consistent --- .../core/src/common/logging/logger.spec.ts | 76 +++++++++---------- .../core/src/common/utils/log.hook.spec.ts | 6 +- packages/core/src/express/create-app.spec.ts | 24 +++--- .../sessions/http/use-sessions.hook.spec.ts | 12 +-- packages/jwt/src/http/jwt.hook.spec.ts | 6 +- 5 files changed, 62 insertions(+), 62 deletions(-) diff --git a/packages/core/src/common/logging/logger.spec.ts b/packages/core/src/common/logging/logger.spec.ts index 0c2e378c6f..725c613ae0 100644 --- a/packages/core/src/common/logging/logger.spec.ts +++ b/packages/core/src/common/logging/logger.spec.ts @@ -18,7 +18,7 @@ describe('Logger', () => { context('given the log context has been initialized', () => { it('should log the message with the context.', () => { - const consoleMock = mock.method(console, 'log', () => {}); + const consoleMock = mock.method(console, 'log', () => {}).mock; const logger = new Logger(); logger.initLogContext(() => { @@ -30,15 +30,15 @@ describe('Logger', () => { logger.log('error', 'Hello world 2', {}); }); - strictEqual(consoleMock.mock.callCount(), 2); + strictEqual(consoleMock.callCount(), 2); - const loggedMessage = consoleMock.mock.calls[0].arguments[0]; + const loggedMessage = consoleMock.calls[0].arguments[0]; strictEqual(loggedMessage.includes('[ERROR]'), true); strictEqual(loggedMessage.includes('foo: "bar"'), true); notStrictEqual(loggedMessage.includes('foo2: "bar2"'), true); - const loggedMessage2 = consoleMock.mock.calls[1].arguments[0]; + const loggedMessage2 = consoleMock.calls[1].arguments[0]; strictEqual(loggedMessage2.includes('[ERROR]'), true); strictEqual(loggedMessage2.includes('foo2: "bar2"'), true); @@ -46,7 +46,7 @@ describe('Logger', () => { }); it('should let given params override the context.', () => { - const consoleMock = mock.method(console, 'log', () => {}); + const consoleMock = mock.method(console, 'log', () => {}).mock; const logger = new Logger(); logger.initLogContext(() => { @@ -54,9 +54,9 @@ describe('Logger', () => { logger.log('error', 'Hello world', { foo: 'bar2' }); }); - strictEqual(consoleMock.mock.callCount(), 1); + strictEqual(consoleMock.callCount(), 1); - const loggedMessage = consoleMock.mock.calls[0].arguments[0]; + const loggedMessage = consoleMock.calls[0].arguments[0]; strictEqual(loggedMessage.includes('[ERROR]'), true); strictEqual(loggedMessage.includes('foo: "bar2"'), true); @@ -65,14 +65,14 @@ describe('Logger', () => { context('given the log context has NOT been initialized', () => { it('should log a warning message when adding log context.', () => { - const consoleMock = mock.method(console, 'log', () => {}); + const consoleMock = mock.method(console, 'log', () => {}).mock; const logger = new Logger(); logger.addLogContext('foo', 'bar'); - strictEqual(consoleMock.mock.callCount(), 1); + strictEqual(consoleMock.callCount(), 1); - const loggedMessage = consoleMock.mock.calls[0].arguments[0]; + const loggedMessage = consoleMock.calls[0].arguments[0]; strictEqual(loggedMessage.includes('[WARN]'), true); strictEqual( @@ -84,14 +84,14 @@ describe('Logger', () => { context('given the configuration "settings.logger.format" is NOT defined', () => { it('should log the message to a raw text.', () => { - const consoleMock = mock.method(console, 'log', () => {}); + const consoleMock = mock.method(console, 'log', () => {}).mock; const logger = new Logger(); logger.log('error', 'Hello world', { foobar: 'bar' }); - strictEqual(consoleMock.mock.callCount(), 1); + strictEqual(consoleMock.callCount(), 1); - const loggedMessage = consoleMock.mock.calls[0].arguments[0]; + const loggedMessage = consoleMock.calls[0].arguments[0]; strictEqual(loggedMessage.includes('[ERROR]'), true); strictEqual(loggedMessage.includes('foobar: "bar"'), true); @@ -102,14 +102,14 @@ describe('Logger', () => { it('should log the message to a JSON.', () => { Config.set('settings.logger.format', 'json'); - const consoleMock = mock.method(console, 'log', () => {}); + const consoleMock = mock.method(console, 'log', () => {}).mock; const logger = new Logger(); logger.log('error', 'Hello world', { foobar: 'bar' }); - strictEqual(consoleMock.mock.callCount(), 1); + strictEqual(consoleMock.callCount(), 1); - const loggedMessage = consoleMock.mock.calls[0].arguments[0]; + const loggedMessage = consoleMock.calls[0].arguments[0]; const json = JSON.parse(loggedMessage); strictEqual(json.level, 'error'); @@ -121,27 +121,27 @@ describe('Logger', () => { it('should log nothing.', () => { Config.set('settings.logger.format', 'none'); - const consoleMock = mock.method(console, 'log', () => {}); + const consoleMock = mock.method(console, 'log', () => {}).mock; const logger = new Logger(); logger.log('error', 'Hello world', {}); - strictEqual(consoleMock.mock.callCount(), 0); + strictEqual(consoleMock.callCount(), 0); }); }); context('given the configuration "settings.logger.logLevel" is NOT defined', () => { it('should log messages based on an "INFO" log level', () => { - const consoleMock = mock.method(console, 'log', () => {}); + const consoleMock = mock.method(console, 'log', () => {}).mock; const logger = new Logger(); logger.log('info', 'Hello world', {}); - strictEqual(consoleMock.mock.callCount(), 1); + strictEqual(consoleMock.callCount(), 1); - consoleMock.mock.resetCalls(); + consoleMock.resetCalls(); logger.log('debug', 'Hello world', {}); - strictEqual(consoleMock.mock.callCount(), 0); + strictEqual(consoleMock.callCount(), 0); }); }); @@ -149,16 +149,16 @@ describe('Logger', () => { it('should log messages based on a "WARN" log level', () => { Config.set('settings.logger.logLevel', 'warn'); - const consoleMock = mock.method(console, 'log', () => {}); + const consoleMock = mock.method(console, 'log', () => {}).mock; const logger = new Logger(); logger.log('warn', 'Hello world', {}); - strictEqual(consoleMock.mock.callCount(), 1); + strictEqual(consoleMock.callCount(), 1); - consoleMock.mock.resetCalls(); + consoleMock.resetCalls(); logger.log('info', 'Hello world', {}); - strictEqual(consoleMock.mock.callCount(), 0); + strictEqual(consoleMock.callCount(), 0); }); }); }); @@ -168,13 +168,13 @@ describe('Logger', () => { const logger = new Logger(); - const consoleMock = mock.method(console, 'log', () => {}); + const consoleMock = mock.method(console, 'log', () => {}).mock; logger.debug('Hello world', {}); - strictEqual(consoleMock.mock.callCount(), 1); + strictEqual(consoleMock.callCount(), 1); strictEqual( - consoleMock.mock.calls[0].arguments[0].includes('[DEBUG]'), + consoleMock.calls[0].arguments[0].includes('[DEBUG]'), true, ); }); @@ -182,13 +182,13 @@ describe('Logger', () => { it('has an info(...args) method which is an alias for log("info", ...args)', () => { const logger = new Logger(); - const consoleMock = mock.method(console, 'log', () => {}); + const consoleMock = mock.method(console, 'log', () => {}).mock; logger.info('Hello world', {}); - strictEqual(consoleMock.mock.callCount(), 1); + strictEqual(consoleMock.callCount(), 1); strictEqual( - consoleMock.mock.calls[0].arguments[0].includes('[INFO]'), + consoleMock.calls[0].arguments[0].includes('[INFO]'), true, ); }); @@ -196,13 +196,13 @@ describe('Logger', () => { it('has a warn(...args) method which is an alias for log("warn", ...args)', () => { const logger = new Logger(); - const consoleMock = mock.method(console, 'log', () => {}); + const consoleMock = mock.method(console, 'log', () => {}).mock; logger.warn('Hello world', {}); - strictEqual(consoleMock.mock.callCount(), 1); + strictEqual(consoleMock.callCount(), 1); strictEqual( - consoleMock.mock.calls[0].arguments[0].includes('[WARN]'), + consoleMock.calls[0].arguments[0].includes('[WARN]'), true, ); }); @@ -210,13 +210,13 @@ describe('Logger', () => { it('has an error(...args) method which is an alias for log("error", ...args)', () => { const logger = new Logger(); - const consoleMock = mock.method(console, 'log', () => {}); + const consoleMock = mock.method(console, 'log', () => {}).mock; logger.error('Hello world', {}); - strictEqual(consoleMock.mock.callCount(), 1); + strictEqual(consoleMock.callCount(), 1); strictEqual( - consoleMock.mock.calls[0].arguments[0].includes('[ERROR]'), + consoleMock.calls[0].arguments[0].includes('[ERROR]'), true, ); }); diff --git a/packages/core/src/common/utils/log.hook.spec.ts b/packages/core/src/common/utils/log.hook.spec.ts index c5f2920b09..5bdcdbd22c 100644 --- a/packages/core/src/common/utils/log.hook.spec.ts +++ b/packages/core/src/common/utils/log.hook.spec.ts @@ -28,13 +28,13 @@ describe('Log', () => { const services = new ServiceManager(); const logger = services.get(Logger); - const loggerMock = mock.method(logger, 'warn'); + const loggerMock = mock.method(logger, 'warn').mock; hook(ctx, services); - strictEqual(loggerMock.mock.callCount(), 1); + strictEqual(loggerMock.callCount(), 1); strictEqual( - loggerMock.mock.calls[0].arguments[0], + loggerMock.calls[0].arguments[0], 'Using the @Log hook is deprecated. Use the Logger service in a custom hook instead.' ); }); diff --git a/packages/core/src/express/create-app.spec.ts b/packages/core/src/express/create-app.spec.ts index afaa3b1ebe..6ae40ba15e 100644 --- a/packages/core/src/express/create-app.spec.ts +++ b/packages/core/src/express/create-app.spec.ts @@ -683,7 +683,7 @@ describe('createApp', () => { const serviceManager = new ServiceManager(); const logger = serviceManager.get(Logger); - const loggerMock = mock.method(logger, 'info', () => {}); + const loggerMock = mock.method(logger, 'info', () => {}).mock; const app = await createApp(AppController, { serviceManager @@ -693,10 +693,10 @@ describe('createApp', () => { .get('/a?apiKey=a_secret_api_key') .expect(200); - strictEqual(loggerMock.mock.callCount(), 1); + strictEqual(loggerMock.callCount(), 1); - const message = loggerMock.mock.calls[0].arguments[0]; - const params = loggerMock.mock.calls[0].arguments[1]; + const message = loggerMock.calls[0].arguments[0]; + const params = loggerMock.calls[0].arguments[1]; strictEqual(message, 'HTTP request - GET /a'); strictEqual(typeof params?.responseTime, 'number') @@ -726,14 +726,14 @@ describe('createApp', () => { const serviceManager = new ServiceManager(); const logger = serviceManager.get(Logger); - const loggerMock = mock.method(logger, 'warn', () => {}); + const loggerMock = mock.method(logger, 'warn', () => {}).mock; await createApp(AppController, { serviceManager }); - strictEqual(loggerMock.mock.callCount(), 1); - strictEqual(loggerMock.mock.calls[0].arguments[0], '[CONFIG] Using another format than "foal" for "settings.loggerFormat" is deprecated.'); + strictEqual(loggerMock.callCount(), 1); + strictEqual(loggerMock.calls[0].arguments[0], '[CONFIG] Using another format than "foal" for "settings.loggerFormat" is deprecated.'); }); }); @@ -757,7 +757,7 @@ describe('createApp', () => { const serviceManager = new ServiceManager(); - const consoleMock = mock.method(console, 'log', () => {}); + const consoleMock = mock.method(console, 'log', () => {}).mock; const app = await createApp(AppController, { serviceManager @@ -767,7 +767,7 @@ describe('createApp', () => { .get('/') .expect(200); - const messages = consoleMock.mock.calls.map(call => JSON.parse(call.arguments[0])); + const messages = consoleMock.calls.map(call => JSON.parse(call.arguments[0])); strictEqual(messages.some(message => message.foo === 'bar'), true); }); @@ -839,7 +839,7 @@ describe('createApp', () => { const serviceManager = new ServiceManager(); const logger = serviceManager.get(Logger); - const loggerMock = mock.method(logger, 'addLogContext', () => {}); + const loggerMock = mock.method(logger, 'addLogContext', () => {}).mock; const app = await createApp(AppController, { serviceManager @@ -853,9 +853,9 @@ describe('createApp', () => { requestId = response.body.requestId; }) - strictEqual(loggerMock.mock.callCount(), 1); + strictEqual(loggerMock.callCount(), 1); - const [key, value] = loggerMock.mock.calls[0].arguments; + const [key, value] = loggerMock.calls[0].arguments; strictEqual(key, 'requestId'); strictEqual(value, requestId); diff --git a/packages/core/src/sessions/http/use-sessions.hook.spec.ts b/packages/core/src/sessions/http/use-sessions.hook.spec.ts index 224bb68267..e31ebafb0f 100644 --- a/packages/core/src/sessions/http/use-sessions.hook.spec.ts +++ b/packages/core/src/sessions/http/use-sessions.hook.spec.ts @@ -609,13 +609,13 @@ describe('UseSessions', () => { it('and add null as user ID to the log context.', async () => { const logger = services.get(Logger); - const loggerMock = mock.method(logger, 'addLogContext', () => {}); + const loggerMock = mock.method(logger, 'addLogContext', () => {}).mock; await hook(ctx, services); - strictEqual(loggerMock.mock.callCount(), 1); + strictEqual(loggerMock.callCount(), 1); - deepStrictEqual(loggerMock.mock.calls[0].arguments, ['userId', null]); + deepStrictEqual(loggerMock.calls[0].arguments, ['userId', null]); }); }); @@ -626,13 +626,13 @@ describe('UseSessions', () => { it('and add null as user ID to the log context.', async () => { const logger = services.get(Logger); - const loggerMock = mock.method(logger, 'addLogContext', () => {}); + const loggerMock = mock.method(logger, 'addLogContext', () => {}).mock; await hook(ctx, services); - strictEqual(loggerMock.mock.callCount(), 1); + strictEqual(loggerMock.callCount(), 1); - deepStrictEqual(loggerMock.mock.calls[0].arguments, ['userId', userId]); + deepStrictEqual(loggerMock.calls[0].arguments, ['userId', userId]); }); context('given options.user is not defined', () => { diff --git a/packages/jwt/src/http/jwt.hook.spec.ts b/packages/jwt/src/http/jwt.hook.spec.ts index 9fe53f6301..cdfa648f78 100644 --- a/packages/jwt/src/http/jwt.hook.spec.ts +++ b/packages/jwt/src/http/jwt.hook.spec.ts @@ -643,13 +643,13 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: })); const logger = services.get(Logger); - const loggerMock = mock.method(logger, 'addLogContext', () => {}); + const loggerMock = mock.method(logger, 'addLogContext', () => {}).mock; await hook(ctx, services); - strictEqual(loggerMock.mock.callCount(), 1); + strictEqual(loggerMock.callCount(), 1); deepStrictEqual( - loggerMock.mock.calls[0].arguments, + loggerMock.calls[0].arguments, ['userId', 123], ); }) From 6a9f0681f84a8bdb14ae84216a6719837f588ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sun, 22 Oct 2023 11:46:05 +0200 Subject: [PATCH 32/37] Allow to customize logged morgan params --- packages/core/src/express/create-app.spec.ts | 45 +++++++++++++++++++- packages/core/src/express/create-app.ts | 22 ++++++---- packages/core/src/express/index.ts | 2 +- packages/core/src/index.ts | 1 + 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/packages/core/src/express/create-app.spec.ts b/packages/core/src/express/create-app.spec.ts index 6ae40ba15e..10de1e3e36 100644 --- a/packages/core/src/express/create-app.spec.ts +++ b/packages/core/src/express/create-app.spec.ts @@ -709,7 +709,50 @@ describe('createApp', () => { statusCode: 200, contentLength: '1', }); - }) + }); + + it('should use the options.getHttpLogParams if provided', async () => { + Config.set('settings.loggerFormat', 'foal'); + + class AppController { + @Get('/a') + getA(ctx: Context) { + return new HttpResponseOK('a'); + } + } + + const serviceManager = new ServiceManager(); + + const logger = serviceManager.get(Logger); + const loggerMock = mock.method(logger, 'info', () => {}).mock; + + const app = await createApp(AppController, { + serviceManager, + getHttpLogParams: (tokens: any, req: any, res: any) => ({ + method: tokens.method(req, res), + url: tokens.url(req, res).split('?')[0], + myCustomHeader: req.get('my-custom-header') + }), + }); + + await request(app) + .get('/a') + .set('my-custom-header', 'my-custom-value') + .expect(200); + + strictEqual(loggerMock.callCount(), 1); + + const message = loggerMock.calls[0].arguments[0]; + const params = loggerMock.calls[0].arguments[1]; + + strictEqual(message, 'HTTP request - GET /a'); + + deepStrictEqual(params, { + method: 'GET', + url: '/a', + myCustomHeader: 'my-custom-value', + }); + }); }); context('given the configuration "settings.loggerFormat" is set to a value different from "none" or "foal"', () => { diff --git a/packages/core/src/express/create-app.ts b/packages/core/src/express/create-app.ts index 38ff29faf5..d63765a6ee 100644 --- a/packages/core/src/express/create-app.ts +++ b/packages/core/src/express/create-app.ts @@ -28,6 +28,7 @@ type ErrorMiddleware = (err: any, req: any, res: any, next: (err?: any) => any) export interface CreateAppOptions { expressInstance?: any; serviceManager?: ServiceManager; + getHttpLogParams?: (tokens: any, req: any, res: any) => Record; preMiddlewares?: (Middleware|ErrorMiddleware)[]; afterPreMiddlewares?: (Middleware|ErrorMiddleware)[]; postMiddlewares?: (Middleware|ErrorMiddleware)[]; @@ -53,6 +54,16 @@ function protectionHeaders(req: any, res: any, next: (err?: any) => any) { next(); } +export function getHttpLogParamsDefault(tokens: any, req: any, res: any): Record { + return { + method: tokens.method(req, res), + url: tokens.url(req, res).split('?')[0], + statusCode: parseInt(tokens.status(req, res), 10), + contentLength: tokens.res(req, res, 'content-length'), + responseTime: parseFloat(tokens['response-time'](req, res)), + }; +} + /** * Create an Express application from the root controller. * @@ -112,16 +123,9 @@ export async function createApp( '[:date] ":method :url HTTP/:http-version" :status - :response-time ms' ); if (loggerFormat === 'foal') { + const getHttpLogParams = options.getHttpLogParams || getHttpLogParamsDefault; app.use(morgan( - (tokens: any, req: any, res: any) => { - return JSON.stringify({ - method: tokens.method(req, res), - url: tokens.url(req, res).split('?')[0], - statusCode: parseInt(tokens.status(req, res), 10), - contentLength: tokens.res(req, res, 'content-length'), - responseTime: parseFloat(tokens['response-time'](req, res)), - }); - }, + (tokens: any, req: any, res: any) => JSON.stringify(getHttpLogParams(tokens, req, res)), { stream: { write: (message: string) => { diff --git a/packages/core/src/express/index.ts b/packages/core/src/express/index.ts index d481ac92dc..2acd84f439 100644 --- a/packages/core/src/express/index.ts +++ b/packages/core/src/express/index.ts @@ -1 +1 @@ -export { createApp, OPENAPI_SERVICE_ID } from './create-app'; +export { createApp, OPENAPI_SERVICE_ID, getHttpLogParamsDefault } from './create-app'; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e341aa9eb8..acd0200cce 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -218,6 +218,7 @@ export { export { OPENAPI_SERVICE_ID, createApp, + getHttpLogParamsDefault, } from './express'; export { Session, From da08345f5b28661dc6c4bcaac44c4ffc8ee68b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sun, 22 Oct 2023 11:50:30 +0200 Subject: [PATCH 33/37] Fix test on locale time --- .../src/common/logging/logger.utils.spec.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/core/src/common/logging/logger.utils.spec.ts b/packages/core/src/common/logging/logger.utils.spec.ts index c9c2b7c64d..ff5e6bc722 100644 --- a/packages/core/src/common/logging/logger.utils.spec.ts +++ b/packages/core/src/common/logging/logger.utils.spec.ts @@ -22,8 +22,8 @@ function createTestParams() { describe('formatMessage', () => { const isoNow = '2023-02-03T01:12:03.000Z'; - const localTimeNow = '2:12:03 AM'; const now = new Date(isoNow); + const localeTimeNow = now.toLocaleTimeString(); context('given format is "raw"', () => { it('should return a raw text with a full timestamp, detailed params but with no colors.', () => { @@ -58,7 +58,7 @@ describe('formatMessage', () => { const params = createTestParams(); const actual = formatMessage('debug', message, params, 'dev', now) - const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[35mDEBUG\u001b[39m Hello world` + const expected = `\u001b[90m[${localeTimeNow}]\u001b[39m \u001b[35mDEBUG\u001b[39m Hello world` + `\n error: {` + `\n name: "Error"` + `\n message: "aaa"` @@ -77,7 +77,7 @@ describe('formatMessage', () => { const params = createTestParams(); const actual = formatMessage('info', message, params, 'dev', now) - const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m Hello world` + const expected = `\u001b[90m[${localeTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m Hello world` + `\n error: {` + `\n name: "Error"` + `\n message: "aaa"` @@ -96,7 +96,7 @@ describe('formatMessage', () => { const params = createTestParams(); const actual = formatMessage('warn', message, params, 'dev', now) - const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[33mWARN\u001b[39m Hello world` + const expected = `\u001b[90m[${localeTimeNow}]\u001b[39m \u001b[33mWARN\u001b[39m Hello world` + `\n error: {` + `\n name: "Error"` + `\n message: "aaa"` @@ -115,7 +115,7 @@ describe('formatMessage', () => { const params = createTestParams(); const actual = formatMessage('error', message, params, 'dev', now) - const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[31mERROR\u001b[39m Hello world` + const expected = `\u001b[90m[${localeTimeNow}]\u001b[39m \u001b[31mERROR\u001b[39m Hello world` + `\n error: {` + `\n name: "Error"` + `\n message: "aaa"` @@ -137,7 +137,7 @@ describe('formatMessage', () => { }; const actual = formatMessage('info', message, params, 'dev', now); - const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m GET /foo/bar 100 - 123 ms` + const expected = `\u001b[90m[${localeTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m GET /foo/bar 100 - 123 ms` strictEqual(actual, expected); }); @@ -150,7 +150,7 @@ describe('formatMessage', () => { }; const actual = formatMessage('info', message, params, 'dev', now); - const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m GET /foo/bar \u001b[32m200\u001b[39m - 123 ms` + const expected = `\u001b[90m[${localeTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m GET /foo/bar \u001b[32m200\u001b[39m - 123 ms` strictEqual(actual, expected); }); @@ -163,7 +163,7 @@ describe('formatMessage', () => { }; const actual = formatMessage('info', message, params, 'dev', now); - const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m GET /foo/bar \u001b[36m300\u001b[39m - 123 ms` + const expected = `\u001b[90m[${localeTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m GET /foo/bar \u001b[36m300\u001b[39m - 123 ms` strictEqual(actual, expected); }); @@ -176,7 +176,7 @@ describe('formatMessage', () => { }; const actual = formatMessage('info', message, params, 'dev', now); - const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m GET /foo/bar \u001b[33m400\u001b[39m - 123 ms` + const expected = `\u001b[90m[${localeTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m GET /foo/bar \u001b[33m400\u001b[39m - 123 ms` strictEqual(actual, expected); }); @@ -189,7 +189,7 @@ describe('formatMessage', () => { }; const actual = formatMessage('info', message, params, 'dev', now); - const expected = `\u001b[90m[${localTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m GET /foo/bar \u001b[31m500\u001b[39m - 123 ms` + const expected = `\u001b[90m[${localeTimeNow}]\u001b[39m \u001b[36mINFO\u001b[39m GET /foo/bar \u001b[31m500\u001b[39m - 123 ms` strictEqual(actual, expected); }); From 1fe6358a6a78eda0391882cfee49cd75e43623ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sun, 22 Oct 2023 12:37:29 +0200 Subject: [PATCH 34/37] Fix test to support latest version of Node 20 --- packages/core/src/express/create-app.spec.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/core/src/express/create-app.spec.ts b/packages/core/src/express/create-app.spec.ts index 10de1e3e36..dad7648616 100644 --- a/packages/core/src/express/create-app.spec.ts +++ b/packages/core/src/express/create-app.spec.ts @@ -493,9 +493,18 @@ describe('createApp', () => { .type('application/json') .send('{ "foo": "bar", }') .expect(400) - .expect({ - body: '{ \"foo\": \"bar\", }', - message: 'Unexpected token } in JSON at position 16' + .then(response => { + try { + deepStrictEqual(response.body, { + body: '{ \"foo\": \"bar\", }', + message: 'Unexpected token } in JSON at position 16' + }); + } catch (error) { + deepStrictEqual(response.body, { + body: '{ \"foo\": \"bar\", }', + message: 'Expected double-quoted property name in JSON at position 16' + }) + } }); }); From 931aa978f8b7f6768d4efd3a79c76ed19a6d49a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Sun, 22 Oct 2023 12:50:12 +0200 Subject: [PATCH 35/37] Allow to add transport --- .../core/src/common/logging/logger.spec.ts | 20 +++++++++++++++++++ packages/core/src/common/logging/logger.ts | 11 +++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/core/src/common/logging/logger.spec.ts b/packages/core/src/common/logging/logger.spec.ts index 725c613ae0..645551eeaf 100644 --- a/packages/core/src/common/logging/logger.spec.ts +++ b/packages/core/src/common/logging/logger.spec.ts @@ -161,6 +161,26 @@ describe('Logger', () => { strictEqual(consoleMock.callCount(), 0); }); }); + + context('given transports have been registered', () => { + it('should send them the logs and their levels.', () => { + const transport1 = mock.fn((level, log) => {}); + const transport2 = mock.fn((level, log) => {}); + + const logger = new Logger(); + logger.addTransport(transport1); + logger.addTransport(transport2); + logger.log('error', 'Hello world', {}); + + strictEqual(transport1.mock.callCount(), 1); + strictEqual(transport1.mock.calls[0].arguments[0], 'error'); + strictEqual(transport1.mock.calls[0].arguments[1].includes('Hello world'), true); + + strictEqual(transport2.mock.callCount(), 1); + strictEqual(transport2.mock.calls[0].arguments[0], 'error'); + strictEqual(transport2.mock.calls[0].arguments[1].includes('Hello world'), true); + }); + }) }); it('has a debug(...args) method which is an alias for log("debug", ...args)', () => { diff --git a/packages/core/src/common/logging/logger.ts b/packages/core/src/common/logging/logger.ts index f0da7a3b1c..0ee9c253ce 100644 --- a/packages/core/src/common/logging/logger.ts +++ b/packages/core/src/common/logging/logger.ts @@ -7,6 +7,13 @@ import { Level, formatMessage, shouldLog } from './logger.utils'; export class Logger { private asyncLocalStorage = new AsyncLocalStorage>(); + private transports: ((level: Level, log: string) => void)[] = [ + (level, log) => console.log(log), + ]; + + addTransport(transport: (level: Level, log: string) => void): void { + this.transports.push(transport); + } initLogContext(callback: () => void): void { this.asyncLocalStorage.run({}, callback); @@ -49,7 +56,9 @@ export class Logger { now, ); - console.log(formattedMessage); + for (const transport of this.transports) { + transport(level, formattedMessage); + } } debug(message: string, params: { error?: Error, [name: string]: any } = {}): void { From 44d32191a2cb43185557bd82720995d732713d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Mon, 23 Oct 2023 23:44:39 +0200 Subject: [PATCH 36/37] [Docs] Add docs on logging --- docs/docs/common/images/dev-format.png | Bin 0 -> 75820 bytes docs/docs/common/images/json-format.png | Bin 0 -> 96046 bytes docs/docs/common/images/raw-format.png | Bin 0 -> 112165 bytes docs/docs/common/logging.md | 359 ++++++++++-------- .../current/architecture/controllers.md | 5 + 5 files changed, 196 insertions(+), 168 deletions(-) create mode 100644 docs/docs/common/images/dev-format.png create mode 100644 docs/docs/common/images/json-format.png create mode 100644 docs/docs/common/images/raw-format.png diff --git a/docs/docs/common/images/dev-format.png b/docs/docs/common/images/dev-format.png new file mode 100644 index 0000000000000000000000000000000000000000..78a18bf843a3d8e81f7e8c97f2bd7a312be9cece GIT binary patch literal 75820 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 literal 0 HcmV?d00001 diff --git a/docs/docs/common/images/json-format.png b/docs/docs/common/images/json-format.png new file mode 100644 index 0000000000000000000000000000000000000000..872f0393cde0dd20b76c2b4e4d0caac420a09ee0 GIT binary patch literal 96046 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

GjMsP3 z)e3KKsvmnwU=?QAl%&n$3>spE9{K`X(L=?{wz%MapFniXj7RrZ{4xy9$GlaqQ^mfY znrGe>^iiW8_yP>3>x5jwm!Lu-*+cL7yg8L?K~hGTO`C}6uV)KVen-Q=b??>p8YKx} z39C)BxdA=dMlE+fN}tfOLy@Ucn^F3V0&`M5As}kYS3CH ztT^OdGr#LEI^tWw5d@Z&XbU@3is_qoJz1SM@Ky+0`j`LkWZuvHB*_06i=&-E(d==4 z>tCEiI2mM7%~=F+bY=mT^Jhv)V&3o2eF#I7*@H~RwW=xr`Nsq-@)2ei4bQC~me!2u z3?yQbuWWhZ?hMzHM>U@mjlFMbs@Vgxg;Ob)@Q)dJ&vdb5F{^l!h~J+v-Sev4%&ec- z-jSv4XSpdJ9rKqFY7uLsqb$JR*7BGOzfNn1-E(g1>`6N{Uit-G6E4V< zF_d}V&-vMo0W#(y$wWI11#7*tBt7q2Cz*|$(^q08J%WtKg<)=WOcD`VQd)zPB*O^* z8OAn4e**FR8*ITX)s<%tChmS_VJ#%EiI! zVQ*Xv&Pw`Hyst{$5ilHXB8Bf`aap~t!iST;O&11ntQRV9`iyv3hv=urfDC0D^uKS0 zW0k}f8`KG`w^6(Jn_T}R17PLvz?oE|&kcRbN9-XA7jYzP ztqC*D3Y%i>n?3|jeSZnhK`Fs+;v}z+X@aFRw2#tB9Jb5(XuBTJKmGL29paA#ENONU zEX>6@-`8$_7{e7eRCST2aZ{3g)h!8ehdu1ezPgUn=oP<&{lWrQ6 zo6iprYIs&`Cd5s5A_W#sZN)NZ$bwGE?3t-(av-6l+p3*w;_0YrRU5gf6EJbCtL;-X zZ}}~r-IF+!Li~Zw#UHpiX{@BS8T9m%=HOOJb5Hrg*Fzh@x%&5^1F8voO~wHul4@$rdG=0$t4k>sdyB~ENGZ$ zlEPY182QqCI0` zjD%FZLjfM*)}pQyQA2-PeEOb01e2a>)g?|e4ApjLPU@hdTq6_^V%$N^@4X;V9N@mr z=43297+Rd7D0jx9N`r<XrOuO=@TE>h zAC*fa1V-_F3VpfoQ*Fd*1vjJ*drft3%z-*;Qr99HuXN%_l+bTck;{af`_w2ov~m{X zcGLbOk}Rs^OuF;#ZZKs*6{7!(80@|!%i|VBzOe@kZ8=4ncNB#+a>j;rpV_}F#jpVq9^JG9+y2zs&Oqu+Ud4fbk)K34lpKKspRd5M4WZn{&u_gdO5Br~W@7|qfo zDTIK72{rBg$5>~nBToz9_yUPS30FqLfuQ<(3=glSBhlFN(*}FGV9BAPA$`DzfIkk; zCgxn%*kF_hA{*18X(ee#Y#tfoc{u#|dqWjO&OeR;t0ti$!Y~*~@}3Du+mibtzj+y+ zelf%D)tbPnr4%f+-LMw3*Te@-A$o3`(9ZoKVB_!Hv}1_G-GAR%%Fczims5+|k6Op~ z#-MK+CR*H$NLM)&93O@`EJ^5%^mg}N(W?@;K8m1DVT~~D^p$hq?U=>js3s@c=C}M9 zsFHs1LHfne?Tt^Zpi8$nk`z=`eysTT>1anN%8Wl|d(V(LMR>YH?x0hP*PK8>>5iTY zeW^}u&j3pl!(I7lb%Sx9JD=F$we%uLuTdqICIJ4FFKOcvO!bQctA4R6reO#R;@o_g z+%fa3q|0~#?8=V+(LttQgnDK~>x+cP9mi;7_TJ}#*72A&ce4^Q<3FJ&m=gC=Ml6#l z7*%iWpmrIsVi|U29F9*NkLO6B{Z8>P!TZtyF`2wI%UTUM2YroiiDLI7zCFrk%7^|L z5V~6Sgjh)D>Y;5r33~J~b7!_E)|`5(pB9G==hqWaca6JM;P6{nqQdvNQ9?ROCyO z`CRMBOmCVJ?KEAMD&wG|%knGTS<%?;5qvp=QaH{oObDjmhZLMBYI5b(!w)pGLkirO zCmya7Kh5XL$Cvrq;NMUqxIqu|(}ZL-9mBc`%llrZ7&gdV%RFQqn)=Z#Z?!B6=5$#N z?_{z<%`Kvt3@Nv@4iYYECw#R$B>-f3Z;VQd3YVW>> z{w}w(u018WtXT}oBl!}OkrIFTnEt7cv}I4I*Rzq{7-fiEDC0es&u{Nt>CKrv4(WdN zsrB@rh|G5Zk7o4eiIrI;?`?&24D=9oL|sm7cptMz=8faJEj%M`X;fXc$b2F}+KBb# z0eLH*S(-B9GTes-#Pc*fBoQIYp*qXX5M$6=lf(3CPPl;S3H9)!W5&a8J?sEFqpg> zBx#tDf$MV}e=*Cnc$~f%^Vs5)UEwX*Ru7#9n+JPDmCV9^wsbKfZDaUQ_Y4vpSn+|s z0BPjeuha}bb9}hIrsyhxS7U5rRH})?a(_3`9vHkNGJ!7G$WO2o$QAl&gP7E9xg`_2 zX&RKiW0pQI@G>De@SO3nR%#>@lWXg_?Mmko3whyK<)95P`n}orp8Gw~6b&hkAtlN~ zj$A^m`b$JVZ>2r&RH_I%Qn&ImvG^Zan2fS|jjVGeqUL8@nJg*G#~*wBo# z(gB(Z#XKI%^tev-$;%GC$7z)b10AANwIHt?7}r?%BH)q$zNEf=w|V!UH2L$#j`O`- z$m1s9OHtwdTuauR+ehcdcEi1-tg>!1`e^fq?~c1ZFaD%eZM8<6F^x>AD2rkA>aMtW zWL)h0_-mQ_eKk{yXp}@K;XdqKArR9ZiCH5oH8zv+LIp{a1Rav{?`mV-sc|;=^@d1IP2Cx4CMcW6((6 zM29(uC&-A3_jFE4=X0tEF79krUGis!mUtZTXH|3W-nNp^0-^7)#7f!q50ba3jIq~Mb`!+( z@lkt9bb;JL0b)ZvH5IE6DCRr;83iTp2!%;v@daKH0}3YS%!BOLRxlbdQ zbMEpd`xsqJic~^^HtYLZCq-|sTGv}nkT-kyHE)H1fV{t!T%RVw$2X3>l0sdg>OsW5khWRHAll zxv?8^LByE)2wTd~%SprXB9~{ycB%8~_w(hYY8$w<+0`d9 z`$~g(w{dj`LOv>?pPQX;-mrQd^0zlkGdwtNl14ui*d{+bWV2`@;Tss;?;aHcOy+6p zO3Jq#SA(qe!=!V`*~M#6%bz;Qj&uv*Kg@!WSg$;r0=jF*7EIX_7SX~%XVP{U%8!B{ zt-2Emq2n&ZmvdW9o{80PH4QumAZ#a@g`+BAlDKBWsPSf|TW#J}HH(1Kg zGH|zVud)8|9^|;Qux}0P9woUX4u~%^IjNs4MFNJl5<>+`$zcTv#lZfA24nwsyuzAv zAm`$gscHgAolTEcKUp&7#WPc!buE5f%iQ4QKAc6m5FtGv%|1ep^LAbk=DR9}`Mn?}-iV7PT(sThmut+mW3U#*=JNV$1ZEc>wHKi& zsJPDJCeh8}USQI{$X;Ly10#71`i@p?Xye=A)BPW3J!ieHpGY14K0%rp*}!Yk6Ve^P zO@?zkF8GRLnp*b7=PUNYW~9V)ny)RNKa^j;d%)AX@#u(mbB~bkEjg=KjsDb+5;-kb za}3&ZAgajNhjgh<4OyZnW$On57pGlXRz*<)%<3=g^smoU`LBC!-=1&)&~608JI-CZ zF`$yQ(7h5y>l3=9UNr`#U9z0ld=TQ#2=06p+e4mz+g$OoTz@{G3F0ul_k{ma?rk04 z8#(P`VjAaP!Y@vAmoU@KIel?7J`4We_#5VFTvhnFd|nqc>fTUqD6gcxsiWb$h3DAk@D+=Wu4gHBv5!5fHT!6>Oqah_*%erEbCAW|2((eG zQ!q`KKe{`8pII+it`R8aKG7j$KldDgn@3?UzDB>tLe6T}tdq|SF)#P1e1>0g7rI&w z7FXgzd-uuWOP@{BB-tacvPFqkB-WxXROPzIj{$Foz)x_YgSpf4h-O&b8;Hr(k)kpt zxQ`Fk%Ji$;A^=5*ZdnfT8h~d_VN-qu2J7 zK2M&13-t=c@g@W7DHo41)h$WKUod&e=%xP=O70}$RqxTcwoV{H%g%9l4JDhCYC=T+ zzUh?SbTt&Z(D~K!z>wy-+x$U&iR#=%kHPQXUqhwko)yxVS$(sNzb+XG$_|<#BP_dq za@yyq&^f#AG&du(?C|N}1PbT{lnz3YFcEO()Iy#zkY*A>nje<8p>yNu&394TS;V|( z`X1kfsV+c#@@R3=!FANb|AZW>D#tadG3VH^C&V|F?vq^a%m``Vz6y>H=URg;e!IS`oddOTpm>Ye*y<1ImIK?JTTXsDhkBrG^Ya}%$ zqKQRO^%<~}I{4L6fibgV(5Qpm$_n%lT=pWj?}5HPC~oXhx2iDs``obHGrdIPerB{O zhUWlvARXEq3qgwyKnut%f#*j}{`s!x^f7hsC6Pj`VH2nyDBqB1{5eZ7AaNOy-l)cf z1O_)cyEw*>{HymwXBxG1`E^HRUD`Yu0Bn_c9IcOg?3Amp!14}@jkiNxiu{_p_| zENcO4Q96CQ_D27r_aVcwvrUZdKPDd&_qal&QvPOw5V7+~iblTdn=Pc5VI-|mrg1?!dVG4gY5lS9d7 z=F9Z~w;ag#c!a_Pl{<)cVKj%*40`WqhE_jFhH}gHVO%^jp!j{!zpO9!CA$PmAop*^ zrQs#*uxG{B^6^jQCvxkR^}dc;!L_h?8+~I$#6A+S%_trPwTEKDs+QGDjAZ!m`L?r` z)&1aWIWCG+p6q$?^t<>m9FKNQ!k>$Vyp>F3q932>s**D=Zi!C+e5DBa!c8v@Fo)ws z6Y66Iv-b5yke)NBI&C*&YhWmB5u-bJ82VgS? z?&?K4H?6PoR(>5kRLqy(lq7O$ExvHGp_DQS*g)FvPXT|D<`wz8WCUU6v8&78e)b)H zySjYSYsQ87y8O8ge$ZxUfme%ntYJxpO6BRg|CxdQ7yAW=!PcnxdpnCckE$e<5yQ)} z5;2NG;M*;|liA_go+CT_>Gmj#8*Ib6(If<85|9(Gy8pFnSoPA9Dc(dt#x@;aRDu5? z9%h%5WsmK4A$`oJP=FwSSX7P}(Q-ApiHyYNlc@Dhrr;*a4ImR-^0pWgj zFA%KnxME+6UTYf1 zM*7LoZuWeda)KB_;Q*HkZ0`%zxrr9lCMIa7IEt~zrw|r7WtaJ|{ zWpWNPX2!&EKkAQ1=6d3ph_3YACXgex+3zFgU*?E8ZPFz?>Q?*q=tF?-;!b zt;HLtCmg~iz)$!rl0+z4G5KE z$_4LS8skSmLQexx-#G^H_q|sw3oO@*>IV(*SXa8?>MZesx*l)}=B1LU2FY0yZy*3!Yy+Tv25AIo-&as>k zW_-`N17Zz3-iFr^%a{FeSKomCzawj>3(%;ymrN0yu5v*GW`X$Cd2Bw8c$(^DNk_P?qi zYga^jlz2|-%23O9(H;;SgvzJN=h0l88ac_2beki}eEgA#!raNu%Gomtys;IiPXg;# z_}&X1lxR1kjVPHpZhP@$TjI;;@jJTGZsrT6mvc9&hjo-AOlAK2JGC7H-&;U!PM@ zx+qW;&2&!cFk)t~>DmC(>4gs_drfbYKg0`8?!|Q=3cQn@~kh8H*GTt$^ z86#^=J^SWu%EuiFu`y)ZxUqw$MjXf#UqU*T(|_IkW;eSFIeUf4Vnn^SJ2MU|-|uns zu}{m&kJA-BvG?9S)3bi2v7Z0y?MYw|{tCmu%t>osE^@!ueEzLnV?*(g@yvY&{?PH~ z-_L{h2W&DwMeB_`H)(-t+!cr-D`HYv*@BJS;?s!{qrmL4Jcmlqi;Wh200E`$$RR2F z(8q>&e{fTps@RR~n>v@uT=MRh)jCU&9V)Vo5^PP>5319J*H}SLP8KWy<^%A#4tI6i zGvDp$a9|0JB}*Qi1SJ1P8L) zd^2Y^p9ltM6m;L?07NBKM&gDLT6V?T7a752=PU0FWJH$|(sf75dx3uPIf-S>sG&vv zgc7ZEJXxNqgu{Qv3c78VDSoaZ>ZmjsT=9HXqHk|$_86a&uipY+?&hauN7<7N+f_3} zqcHi1QA`>4URQPusOc*6NVg1$MR;Uph>GZSzAxP$V2K)Q^~|-5=e1%%YYjGSZG`e4 z)KWbe0CHje-K>m~yAQ(slVBD;n1T(4p(l0l0o;kJ4?s^K6J-uP$0jgNDY1#{L6*~K z^Dqg`*4HoD_;ca??PScrPxmBhU6p^``ft+z>;Pct@haBs*7}mJ%vqW;=_~F8+?FPW z`zz|OVt#R4;?tC@UddV)-7xj!hPy)7_2EYP$tt6uP5JnygP#5VdDM_l}GJzps9F)f*xF@6EJ5 z-|EO7(%5KdKB2hArVo2Ioz+$lkURxbKh>(tY7TIPnOJd@;Q?ye=tK)amY27KkL$4@Wey>@SUYj+y9@46p{w=XhjY@Y;y!}J`+V_fn z-(R(V9<-7*uhKP_HB$|+d+W|u!0NwKdv=+dnC;jT)*9c6cOcBx+6-#lUmO6JtjxJx z(+Fax#8)>5f8MR2zjFxMv=>vZlZQwN8vQo3y3-wS?}1jR^NC4ZF-M%tSJ$h4xMp?2 z^6p9T;#PJRmzZqiO1)LBt6lcU5l(`E%;N6^&n6-u*cm_Y{`gr-X=|YcYmd0zM+L{P zvi(u-*%s|>D{MH3#gRAAitBXnsTgr4{Hq3Cg@MEOK&H(agD~|sA0G^#W(NJ5<9uKZ z3X+sr0&>3HpT!xHrr1(Q@V}|WuMy>u^T?&-QHJ~c*ff>DTJ^A!q*!ii_!!w#_uoZ# z03Of~!=qeR=u^Eg&<$tWZ}{!eseGyMiV;zFl!$c(R+}5oPalO@x0x=1ymH$R_^^B> z{p)h+n})(6%EFIYcOSpmJxzlz<@eglkElCxN6d3m_%c)*`J#Otb6vkrp1|aWjTSOUCWpn1qoaKQ^wXQxu_lbP8-E8qJ zL>ZvG(o{%9Q@+NgV3LgTI_m0Wf1m5@Uk0(WKmYXi$T5B*j=GzRWGDV{K3W7y2j?4G<+fZSOffm~jGNH;C$7;7cii*` zGtU4Rz-5tXOgP1%2pPjhDe4cS84)l9-8U1uiru`y7gfVZU8*R*tLFm+cT!DtR(A{B zc{JHWwi4E(X8om^UE*+xzWxg(Fb&*m+GFH;)kqoc=wPDn-^~5BKYG1ZUs@>U^MdYK z7>ZQ<7Z0hx5!DqGskdLpq=w%soyO{g(lhCIH*Ime;cp+j zV|-#<0J2Zm;)rK?XT`OAa%aUk%XuPeZo%THe%`*k{yY;BS8Ma2G*fK{;EL}=8?PHh zk~i8vc)v*m0Tw3YeQvW8E>ZGsE>)l2jgukRCaCysleW?i9H13&A4q-saQC|0;?Px3 zv*%9gmCaWRPWku!dyjlgYBS8Wy2R*nnse)0{aJUaRU-|Zb(g(&EeUMB+@_tODg3t0 z5AF6^WBCtHGFQP{e`h3rCXBS`7WPe4dPB04-ZYv)4eg%z`Hw?jL;Si_9wRk2^Hxoq zVDM1}rQRUB){#Lf5c6hVpi)?EuGsd>*mfy7Z{GNqUt?aT{p_Jo``+ub+Jws-NPa`* z_^o;>x%#{r#CGXa=pNI9;L5W5tsdnBWJoKwf}KBe{AH2v;u+IU@lj^mQd#MX-uc<; z;*~n~82ZZ^e+Uw!wVB-yYlK<|DSoI&LHo;b_cEBQp$LwTT%S=4q7O}Z96&6MoQrb1 zwHknoIU@27y#EuX+}i<7?h|-;TTO|`*?tf|v}oNNKAUkeh%WB{#F)x>&u_mG-1_JA zb*DwZr~3?C3KjDb7&$G8EnTH9O)P(GKH}gvuoDq6$Ur=^NAkXieLJ*p70=^L7)v>6 zgco3weOfp&EV4a>+-x3aw+mbRF*~EY(?zJI|0aal)Lw6Uzr)hxD%uHpknz!AWd01k zjXd|K=wO4&)umnY`p%|6?2Hkbnc^1yKpp@i*0M@^4)pm!X4gTxirEghmAJjyEBHjV zfAH+I+UjOEACn&;ZfZs4{b@j{Ew`PEM5J~DD1!Os|9 z{UuPbc1>)D#m%lXA)t~~eDH()3?levblSQLe{Ky(h4ln?LQt+Wgfqi^l5Qx!c`Dzw zxcO>$rSBb(Xd=eavfznQdZQe4Iwqt~Y^TqN+1@0aW?Zg7TbK(f3`Ekg>fXS!u2zfI zQN9OTlAj48gDoZBc6eF^a1j^ZYhkuEOu+B>Id=(ihLdmO5pBT1iLl8_OT6tuND$bJ z6`o3ZSZ)tNW!{qob_EuqS|VO#%sn+oR~^eM=^``Vv$Qf$j3eLWcgL@uV{e3`5TSmqZmX@yT!V0VzbK_g@)FOipOpV zd_xpX?+r7YjfGKpeb%O5xxEB;t4iJkY(XOqE{YuOXKR#_`KGvwD2C=kSiH0GQ$gyk zZvO^(ua|~Jq5_;-ZGu*Q>4>)8XN;3G|6O348I{~Jz103w$Wp<$C-6kFk#%fYjz}aot2&0W=1QwdMI13sk^w0*d?Pz7tqmI81I4~^y|K67W@TOlNNa0fM zxPx*K1C)_zGe2;cu6CP#26&-L#qFVdE_)UER5UpT1BpfRB~KhUz7i{ou67M!A=4rjMgiPbA4 zAOahrjjZ&7vZy;PnntokfKt$RgzxYAzvqyFXhm-Mq|U&3oUb2PkibuELa^T+I5gfc z#klp+D&(xoaCl7bF5)sE@UNU#xDu*>y{#t)cR?F6CJ_PE-5RRmdQgP-RWH?HnGZwe zN41kj>Sz=2EJb#Lw~*(hrDL9G3MgS5k7StX`jYrb??dN)phi@K!w8=P060^C_hLDn z;)}K&Z3PgHh$FD9^*`=0YdV1Q$gq1As1htAt-K7+DV_GO1F7zE1VDfKSz`OZGi!oM z&tC_xCPfc)CYgbTz&D|DSoUKVJSu_M-8#-_cJ{+rQKYOWEUsmCU*MyAIkTsVX$PWZ zFrjACj>7pm$oh6a2^G7`bz8?Y(taE%vpCovi~hZ~7RDn0=JXPLuMb*9=GhpTW`*l?F{GE+Dza#Y$@ z0T0>M>WBex3}si~PV^zJKuf{`0mM;L@L1Tv0Ib(1;#Ix`-miMEi_9Z03kDcNP4ZUw z5pY}Od9+=&AF(~+BNIEt&WlwDhiD9@fK4=5Eu?G|Gu0>WGbBP3_Sh^t=LV!myg?{hlO*=4NW3Xt_L#bv=~28MyQWyZNg zl+(;Y8+bWlgl8|?aRzF@syie4R76ESc{JIw8;c!0xj<&Nx)@&g*hTKaiovube!Hl| zveRhSw!EJ{qo0ZubXh;z6!27KqpLD7rq{!+v9l7Tyt zOJg$cB-nd$5wN3=e5|tCkgh^({%rj>`rXMbk?MfMuanvEc5!|#smu5-T*d3F+i@V3 z{;j?ie<{Jb0hDSxUX2oA7+4i%-Vhmqzl%T>*_Fs!wN-u_QzA@x?1xJ5*bk`Qh+t9n z43ejWvaLMjS_IhVNRTaHIZ_UR!jDmcU`2Je zYW4sg_&`8^6B_h0U;fnjahN}{rURodAjn@GU;JD}C|atsx1otc1@nbk@T=qpP>?9U z3G&^drD7r+#6+ALcy13MHf+~tfFtXRK_Yy1OV%Rn68+;uea|VzJ7J!$>(!Dd_+1fe zYir>dlhsJ9zJRI|i<5ah)1|?Pf#cq{tZctv&N_**cS zX)yOXRjrXd8`_He(SPMM>ykjlYXiS^tfRI~?clmP#=Golo4->h$=-iwf|-|wv;bz# zj%W*Dk}lN%XfWGBdWJa$uQ?f;u8d4`Fuo)JIc+kN-BF}%;#QV`*qYZX2 zBdCh{Ldmyf0mR<`zw3Ouh)q@xNlV0wgC|&Z#iCrI<3ajad18-=DvRkME0`phQeCm?;KQnO20l|A5eREMb^-{J4Lz)M z4)Z9_ziq?=5=Mg1F(OXVqumjDUL^9zs-PYwMG1>VC^!TTD}!aP({boV4}JbGK`4#! z5aJVDB4knHO@K|z+<3ggH*5eph+(t)?LO-{RLp=7d}CK;eD#P>NoriVnQK%=M;5Hh z;s5B)<&&?ksIe?GMYLmg=n1aoYm}3Kck<_y;uH&^Yn?EF3oZf+{;M!c4wkVlhA`A5 zW|@JPIjh9$AnACFdi!NiEG43jd1}UzsGi}-^%S(=NBN;nS*gx0h#y}{UpXsuV((SF z1;UWDo}}E>)hTFkb@67=cPORv=mFyE(hrCzWUxcY0STi1Ud-m;X7U5Q9_&z)>jz*cGV@OqQ%ULUp&ZjAd~IOr#VUoyoOi*)6_=d< zoR+0BNNNJ6_#YL6rn5b_6g}y-AQ_Nt(&C2dUKbDU9XPDKiC~mo5xn6U;371r!B@gB=veEi0~l21TMK_#8x+pesfTGn~N5=NHM3p!dshEarf$batAM67Gy4$ z3dDAdZnahJ2=55G=Ptgpgt|8#qJTD^tqG?Y1+s$Vs-a1G(T96WWe5kd zICB|r+dSBkn24Njdm|?7*jUBMMtJ=oyTIFXYuQFj>)eT2pdx~8khPyIW25VQ%F@L}z9~qZ1&=V+k5>3(D3okAbPbKAv(Y}U&3n&`GzF3(&4~P2%EF?*n zQuk`^)tkO9H>`RDA(z}i6eDrnN&ovQ*&TE6rXUsFZcNn-q-VZ=FMTry90w#(D{0#} zs|vfVv2jb$*8=#Tu2TS-*rHQ5<3^jJgxYK&1d6UNS^T+cVnuFBAcw6e%Y2|)OcK7^ z7tOzyy`|t=|Ff!6kdWO(MMK;mw2=uRy-Fqb!eB`?ZI?hC>alRa5$CdVEf=1!g5 z14>=Ru}&lS$9-Nsmt6v6r!_sNzd;REc1Ou-q>A*Y-b(b!U2%(M`ms(7s6$Mzq)l2) zJ4?4iOGJheP(5RVVGM>XUuX}67sI%5N3t<`^Y2{B)Wf(N#Djt(PFmXBFFO;Ch>$VP zrCz;;UwrGkP56!i&nXk@Hbj50k#+b;{+a}Us`27IJq35uaHLB8INlbXTJ8JDlLeYV{$R!uT;u%l8>x5vqXU)r2L zz%AA#bJgg^KS+wSUQwzIB(smy-a*MldP7Cea$!yiB&{tUf6E40R6OZjD{6BjWhwD% zSpMjJOt%$(|Eodx#{RXPxuLY3xq%j4W1*LC36B%}rZXc4pjaK@Kc!~cWVLPw+M|Yq znbb-;_4&`fTYP)~Jk0bX;L=A+elTti;Kq?$=>7U+|0!BIvr1b| zn*$Ex#Z9dwc?8l*XaNMwp?Rkz9{WDOEob0fKR4F-Q0iCj&L=p@q_U=wB4%rH@@^H8 z1`Fx?{`HRsE4I1>+1WU6q67z2~$MMz} zjs~TJ3#9>^9z%vhPu&L?7Csl8=|mq|-_uii1zkTCMaJbgu{a&6>)P;*Z`hWbt>#g9 zT$RUOgl|IM=3q73#fV%m*1pT2T`oY29Us9Ta!G)P7P8=5JXnvaJYnIal$Ix@-O_eP z-}NyND@EWvr^G-MMvmGViCUq5_16=;TYmq!WuKx@Wn#yc=R#;5B*`?yoN= z^?9C|zsNhEP)y1_z%$4v`t$rUKKRTt;q$0+m69&o&+-bDX0InXeIM?=Wx=~MdAV-Q zyfQ37TvVGAY`)UiX2qUvj#e~cqyPzBFp!De1@7E~8WP}2k zvJj7nE6WKr!JBV@xf;0py=Wsw^H)l;tU2~S3fdR1>QRVzp#5-5nGh50a{hy-I?vRa zcGs$t=~h1xfyf^Z2W1zuq*4l1Yu)oRb`pw2S6V*-)j&j0*QSeC5>eD_2LmoF_& z5S~VFqn5#Pu^>N04eioLTRgc_{M0f`SCFOg~d8B{;=*_g+JJJ>J4O7`99a8 zL0vq@yT`ZpoSr&=r$ikCA=pr|8oGGH&{o0a!WLcI4-)6pzohYTn39O(!u7vh2Xy1ThqU<-*u}>bK`V8c0CFy3sCbzt zDb+0d$Ly{FX8f~5-(3>w^cs1Q9W-&dGnlP_5!W}mVbT(zE?2f?zob-Q)9$;JjS`$3 zqe$R3-(m2dyiNrp23m&gX>#{s<-cEQCaQ%(2<~+-rb1{wXArI_&hsi~6DIK!z>y>! zbpGpRcIj`5TjzY{BvOkA%8@5~FW*hLJ&cebFJ-KUR=2K!;@7to#m5lAfCm@9y#2<; zl7r-MCrMV<|3dA7Tk=9PX6klOF-?}ED z`Kh>D)cI|t+JfOatqa~w!8h=QxXux&L9Jn|?XAT~me>zP7by8k-q2I{is3-Q^;r*1 zIluhBrrqy*FPIWouC=?8S8Z=e-M3UD{IcW4tgfQ3v8ge$l86WH@TVZ#)oMD#x@Rm( z7*mD3@jh?=+F;gAD-T!-W0il;@ag`FPv;tyhG1J=$21?Zn_YcVg*%V3%!qGwWJ6Dy zIE4a->i_fu{zz%MT}$zITw0eqq%m!$VY;HRTjtS;!B0{1-q9{loizn3Cx6IQfIbQr zJ^wnUAN4C~+@w)7kT4m(Dl6snY$zwf#{r+v)a%15cgwh%gE#ZD^lXnemE zZI$C%_HHV|7>p$teK9gFrU1mtQjF!49fb$MAe{Gi-B3SU@VNJ&pu%$A-ivb5M^zTa3t(XbUtXN!(&0q4C ztRxF2@ujIh__25hfX^3e_D2Bkv`fL;*LJmc*PJ0MKbj+aA;vp{(|Sx0*Q{nB7bZF0 z`QxJ!d~Max@<$WV_zOL8vU5p0^OdSintOz14H}4Zrx>ZVDz+26aVp(_-WDATkF${( z-_DrSKJDFmYotAqShC-uvr;khCbwxrB6T!cS=8l>gqG?;6okqoIao1uv1@{>q^~Hx z^9)U4WU(lORy-V1@D4THUlb;Us?tp6fjET*R#X}+aokadiE36Nv>Y?=llYaJ z;PJrUfA)9oY51L*@O|M9#90dodua4a8k#YJLt?-y1uvpEjEF6}*)9=FB(iU{%5cne z%6F<7k~V&YNY){|+oh1yCG!73tH1?k`7P2%^L@ezhfLSxP{-@%G=kfohqcEIO_P3q zo%Dx{rvA+=_5B227o2nSSD{#RhF@qxs!AyIz3bx-A?+uZ)~Uqr<^?A#YDttAf`8;G z#0F1f1_(&#hn9X67Bg33z)3cnUSia^JF*uj>`tQ?y=c`L-R5k+|NKS4@Ms8Y^Fl*i zcNVgM$zZd+O z`$AjO0Re83cGdkvW|4z>5|qg_lX5l^7_1MolT8e3FScF$muYcOSG0cO$Y&$`SnIaB zE$Vm!!_zpQq&bi`aPn)&U9u3NGCK<8i~VY%9RoQ@GaI?xyly!17j|NF@fv2zW5;?` z{hen`;^*7R*fe%x&QT?;`EB_o3;%{GIqEW_^dp=={bA%Z{+>ItPPQo`#iESH_SY6Zf(+?TN^%T#(L}*pwWC0+YXyO z27rBymJ56${DSZySdjJl16vSZ71!vW{D`O6=K6BYP#R&gy-`DY#$?8H^mJ7p8#Syw zibi*w5_<)aM0#P2sK;(|QE&oAcIMV90xIss7BY3P$bHf$Nw|Kv0gsBv2c3eZ@{Q#E z2wA#&{ufLUZ`5o7Xbx&CECk*2 zKeoY4^g-S0M=kmJy(4?h=enp>4FcVy#hvB6KY~dV(B>``f;AUUn3AH~B(is-H=-@U zyzAd5OP05o5%~VJ>&~@%{GN3mIJgFQSM+SWN zC4x#5{ypKY_QS|6)^+@pr$XM1={?6a{uIV_EsMgnT^y-YAnU%wu7$Xy`v!I8@Y8fl ztbJMXxB@FNCyLA1gJbHhc(^YcJp2Z!!(D%O8Q^l5#2G1*)f6<&c?cr*E%yKNDs`co z1>Ka`)+EFXam@uPPv*n%HTXWi?Vpp?xOX1%_})OCW4n8Cg2fmRe$%poDJ!h!ZXZj* zFoJn`r|9nc!s?dNW}$e8X;bmTip!*4EQ0-p!{lU1o#37meC_f_4VUbM1B8+RQae?E z1f?>eZN9=cCD$nFZlbX}( zBg%zeF)V#KZ>Yf7m-?}Uo+?3}(SsV3S!eV!Y&*r*&q-Sdnq-k^mCK+Fx~qDVE!((6 z@%`kQ+qh80IMY?aNCM#u$)`U997->riJJNHv*9RyOu$9+MAOzjlZ1Q0*i%NQa_Cug zjxl+#PbxZUPhcI9s6>&j%{vH74YEe4tm0)*7RtY5RsC#@(aCsR9{E73zO1H6SX`kY zUJ3X7ATw0*b%4j!eKGk8-BI0H{P#;l=q9{Bw=UuGt^9K&H{CD~k) zx*f5GUBe*EjMMyx7h3KZa1_QFOrw)SzpY`Ot@0;(F3^0)Lv823!MX--m3Wo33bDzh zVEE0$bQJT^b~TXA6Oyr1O?!q?Ug~Qn)ZtA!#=XP%!qh-#c~ps?eKoa%Bj>YA;FX$2 z5g4iNC9LUUu%|UaTj-uK&hWOoX*S>VL#K4K-FC56cg2yKCJB za#D2d+s|JSJ?ImY7@Mh=VgvEi-Tt?dtZH7E>g#nW)@=DEdWSBA5QIe=F*N@M_8W_m zui7q3@Z4xdbdjc*L^>;&ZP2u=z0&LjCkxnehgiK}SFJCp61K3~z2GXu$Y=NV}V|XN{svl@og3`(hHGZMwIKs4lP;R4l(Fx23|a7Y%)o0nZEe50a0(M z>XTDos3a#1g==90Wn=-v8{*CSGnSHRUn?cVXUKJiOi0Md9 z2yHeX`#=FAWco4q4LnNw?h!S-Ey-B~1I^TckcRanJ?zVwi#fc=UFim*i%p)`=52Q? zQOu`_X*OHXB!<4EF@>WvbMg2AQ5v7MI4Q@6qSd^*wcX#a`k$J!XUP8@XmpU)t^aI! zmxU!?9R73L-X9;c4iDeRQ?Bp(g79y5J9jnQ#kTzkC|3fQg4AdcU$MvmE6p zSeN@50x*~bZ3#WC+U~4ElEF+zGBja~($sW4@j)rem#Uf`W1ZR}y)Wj}Yer5vUh@oh zB4g(x*czNozLlxoPeZ7~^O3+jsq)=x&p|^zZZILZ{cXYQN*}}eyq+HoQ>*M$$|;9! zOYYvDO<22XK$2`keab_BksP@$w=0RipqUm1S+!qs#4L#!2HQ8m8`*WxJV=Os1t^;n*g3nI1Y1fh}Y zoVXj(LX}X^Rh)?i{-_6Z@{SreOi+pPp=A;eTE30Sv}!ZNrPIB{JCyyQ7I`rx5bz|c zKSE-Dy3hORltmTJBOT)wrivmP0Gw_?ZqhiSOdPb7AlonOiVf4(S)1o*PjKG zyfl6}Ah@K-i+3yZ`^+TTc{KU+<77`IfBb}mTrR3TQij;n97l07Z}t0HCO3`uUYDI< zADk-qSnK8a8vjP_zmgx~Tp@dlTvyO01_PUdAu|ZBGlCgQ5OHJ=Ms%sCoe(q~GX+|x z-W=~iFcosgG}Tm6VcXHksj`J2oOe>u0Q=IxFH(7p!pZPM@c4C^vQ*T(MhM;yba7O7 z&Nn_>Qi5UF$wi#(2rWkm&u;+U796SD7MIR=5%2`noRjV^H|UvP z6pPKYRN;85gCp=cOmBFDS`^Lzswxl`<^Nipp`cn?9B17(q6oTNIbPBl_2eb zQN*&m=t<43lIo_W^S*lh+qF!Xz)bfp9%YmR31b=vKzM3a=rRIW%bZpNVdXCe!1^B( zpoAgX<4_JN#dX@u2l}_Oe;QW}x0`{ZGnzHNm05aCgL!IB=C5>`u>Jx*{FHO`#zM zAUv~IRb=Jfvrsh>PhY_#%42oA&F{D^DsO$l9-=>mHEm==*6X~IeIX+57(}ck$F#}d zOyo@buG1W9wX?*W^BD#=PEC-p+0%<#7=8qgto7RyH-#K=u>_7TVVY3^IQWw|5H6bL zrJ1BiKXLFyxq}dL+Ak_J0Hfi9%lS@9G4~1sszNsMMJQ712WF{H*d@(%ZArU2^Y?BX z0_W$fmv7!fW0BE5{c@R~7LL{rZKL1F)E124{Dnb_!HHp?yZn(^R#s9#$6a3Yre(Ds zk5b`*j+_sVV*u~|wZaM_jxguqBvPz4+!rqwieSevS}8vZ!l?mQMcr>0V@&od_mdts z#y0s6#kVelV9<*EE8aq`_x3b|oy9%h1*>jabXbvOpBNg58}7Y3b&B_G8u|?ZSfHk;#GG=YZy{r&jb@+2-f2Lwt*e{d1%Lt2xXe zMSX^7%c!CHT=I38b_!FOTN8s1Q1Clj1YP(w{SB%4#UI=5jZzmPq z^hKk+R?~i}85FLcpr=w$DfC>mW-3(mP8YLI``asMAT_;epGVK0&un#tFuZHN3|-=mcGk(tXq? z2?Jl*t#A`m6v{F0Ia+^D)-{)MurG5z1Ym`z!&X^}man-t4CnQJq7k$1r`s;GQHx$7 zyBscrU2b8>yg_PIn@J?aS(^hYEY^K~FB3>%P2Ug}8I6)g{pdCatUFNQ7&=h@_nVLQCI&&|mUuM{@ zD5(ckNJ4)Ua9^aRf)~xbPPcXNHG8;)OTafMPLs`ij;z1_G>EuAZNMZ}+<#U?tEeGh zjl(C)Fg)yay?mm!_|k}J4;U2K2FCW4S2dTVHG%3^6f>D#Cn`DeOdSVT;zd!N=fdB! zN>-Fe$=xCQwv5N+436hYTVORiw|PaNBZilx51%@sxLcE{(#J7ntYJa_Dioh1m;Sig zl;aALUdC`|dfftEpp#qVyy8&F+43MG=`ngXAeChl+r-@6d2!IQL*E`Sj#^0N`TnN zlsKdRsY{O>R_?#TH!b9J4_P;_je;I6x@>;j7iT;wsgk`EOB))D`IOuy;ZcL{;u5b$ zYi3FnWTt&oHQBYmjcSr^G2F$)jANmB?1yn%BL3e%;gl7qB2nwj}Xu+OEx59Z&A!e+Narlbl0l8 z3+y{@8{L0$r9})=$O{&`{OF%e4$x+G)ElwRvwUd`JWqxo+*Vc+&DXv0M4$wDZHW4H z16#qVO1Lq4nldo287OJWRcC*mX>u-g&jtMEkMPyxD0B9Q_u3?_Ymlh}galX!sxg~r z0y@Q-^lEhVo!+neKN5jT_JsQ|-BH8(T;4oKJBgFj-xp7B>~N@9@zto!Y&7Q#1QQBb z{J$K{EE{m16~}@qp)vP`gU9xtx{6Mm0u2&SJ`UbQO~}SG6v9o6sDTI%X&6Mtj=RpN zAEL&#JRO~H?jNweQY`Xz!KmpV2t z;(0(ymigk6&~BwP>dyT61NVroc59INM8GZ2n7)|}wr{;a7lAG4gI!a^i3*>gxS|OC zZMU|5eTL19rmMAVD#^@lb^w=IU3*^rUXEj4*4_xl_l^`BVJ|(3H+R#u^OR4sdKrV@EffcOC9|gg6X`uaprj< zRGbXHKyMTr9n@rW8+ahw^C-Gs0p_fUB>nGIHwhbQ3K$acQn)P3std{kM?eEcjvE9` zKCNsd>Tq=JhWcGFnu@!ba)sN{x-1|&<@_y9I_QV*4cHWg^~@1FMu|RF!T_gRaF= zcPhm1pm%8&&H+WYHFLWkNDX>j8hOKU5Hr>P=T;nsn5+mLeu9+NecRz=mJlDAktvu8p%kIexM^iJ2r*YrjDF@hMUS=H$C^qdAIX{!n z8zPa+dydTe4H1Uc9+u%)&@6 zf5C)LQ3;DC$>(^1B-uR+tDgejG;S;>W3hWlIPb;Hh-dMPNBW>TmnPKtaC- z?sXVHg_bs)w~8)NJ;wx5+>iu-oZ!trih+OR(M{L{@HUL89DNq52;fenA&|0#A$A{8 zc_TeDJFDxzdytlMBRg;OaVA56C}v?51unV~86X30r?&onlY`@k2-%Z1Et(e`TQ&xq zL7~ysN~+jejl7N6O$P6i{4ko>yO9llT=byN&1}Lifi$5sA}@*g$eyS3%!V-%Qr50h zV7Y{>i|R{S&!u*NBY)&_GYB>3*h21;g>=2w-dkBj`w8oH8+B65gB=0?A%3&|n6A~+ zFWw4wMErkLPIc$|zN}(n2n#jd^98fFyvb*4D=0r%>@cQF zjUu=0m3q*>O1?4~ZG51<<6}d7X;FHpN&(Uqs;1FbabrSM&XpIRxAh?uh9^vOJxvBh zZ}|a13D^4L#=mI~wFSUFIp_dI<9W&@)0UjrQx!qN+}sz^xEWwl7UwH`++RMm`;=$Y z@HCUsX$&S02T=Rk0DC*hU|7cH;-WAj91qLCS)KO_M2~wJNLD;>rQB^q? zlXB^wTc3@e*DK(_mS?_wFO@WA!gcb#ZI5Z#^?;OG+MBF%^@K^-T6I%*h1g+#rG&s{ zxt_kKagp|bIB+4IBF^RWZ+2FvjT*vrvQ;0sJc}Kc7w6nJ&SRO&^|riZ)t6Kx&`7)# zvh=6xzSM`qvWzU%V#(cGvH3)zK3AHg0bMP-%yy918qI@(IP)zip6`EomcP6PoAEXq zrSZs_wr;C}$P>}AzFZSZaOD54YkjhE2f&4PrfJhAvZM8BaewQl8G#R;al2^ya+wu>Ya5hUPUmK&CyT4X!HCDl4eHNT; zAkjJA55y31dZ0YfaW#BS&mUi+xA0FuD{G|2s3kY|f-SZS3{tx8E?~ylLND%47y1V( z3hiaQ@l^54Am#GDDH{o8th+VaxcE>QL46+5n<;*%)>&dKv?{a>#icWUMTMKn1` zZA#q`_&yEwVmRY2xc8We+#e`4W2IIJWN2J#*GP4cLOQE~^6UH69Lp#N&!V3XE^)gW zRHfJ>=!SZRTR@9awFu@nqAY1y3BP^1wW5uN!vi<=47VkSF(Pxybm0r@1xKx5?6RGq zNkjausz)~9=59zcbl)!G#Go^5wM$ikd&<+ByCgZw3wVdI`V$c8lfadbQA2m<%DWl* z-yspBlcP!NCRLuS^KIS8h-(urPt*&dqBld|i`c=PwEYi%H;1sf9?XZ8pcUsSWzB4C zy#872rcm^*<$ay>LjUU>?$D+9fL1(=2jB6dKELlG75pU$wwif(BEmB(e}9oWy?ei> z#n$JZ({g`vwtvDQknn(wVVM^m>iU1JGf9~2mtDD7Mu?d?doOoY*F47vTb3$GV@YVU zNpDPuFndxgI;JMFB)R~oOe;ZRNM#9k_GjiM6oK0Fbeor$7s^1wt|p~=92FqgE$`t`$=}2+%5NWg1lys7@7k|~ zf_|M3dKFA@JV-hX{QH1u#;Qh5kg;J40*iAz2P8%NI~wxm@LV&5Ka>uY)+?!33tyOLP@QePue%6IxD)JKZuJNryUx&LRR4>T zwAW6pZ$*#3lM5@{sit^;h(V7I^dvWrDDvSM9z&{i4>}no0xmB0~4i>?d;SbEG|0-QXh$yVggwPh9+?~V4hHZdAD7QZ% zu0OFE?~|OBTN22+Gf0JA{`HUF*AMOZ-Q#5i`cNQCe*cVM9F?LGKk+fjhT+4Glk?r* zsBsKFR8k%Dp!H`haUOT>FlIT}>XBcOxtx8pW=SJ!$niC*0AR?Sft1#$Us^9q4{YmTK2sNT({?(PP@K41 zX+S}z7v3|;D0%3OPgV+LlK@c`W`Kh&mX`O(mR#g|K7gJgqlfJejpFBfNO)u|RD_b4U=1 z%|gSt=siY`_$b)5yG3Do`@gFS7%a2a4K6m4;aOK$+2hTa{M{1Cp%-My=?_`2OVJDGPL8>nD9Cl<6I zR#VQ_S@K+O8u(z9L>Lc33R5=E?s+49ee!R7)MYQAGqPc7yQ;PL?}hz0PHHN^OTFTI z+cawjKH62(@S}I7ieeR31DxE0+r0-IYJ_B!v#ajx?riifTn6H?slW+&Kg!Sz2X$9T w67qs2@I#4g|5%mGFWtEh{{F^&TI@Up^U&BVO5SAELyJ4)=&os35im8vi@ZQ?G8GG(F^^pZc|@MX8bHadPrP z%2LjqrztU~CReuS&r3PH%$-+aqrU0(&ZjQ^v7kyY^aegc*m9O-DD)sh=_4}iR_?+N zwglb%FM?)m!D8pt-kV&gkGuaq`rdV##r9`~hd+6r-=epumhJ7xW_6-&xpYI|MyD*z zpeSQ54b4D9?>k=Zu;PWD-=9<(ubAQId^#K|PcV1x=@-XBVRP)cvTC6t1qMmf6lI(( z-R1Qy_ba=8B;3(hdmXKV`1pS6%FKnVCz1xav9vbI&C~@m!>#b@X~FImukv&Mga;b+>~MO9(um7uSF%Yjhmg?4HK&qu zzAP#i zyJ7b&Ww-_?wHH};2*$rewXo+Y+lX1>I&CS{RIi#QvY18hl7akWU^y{H@w7vfxS+J} zUYc@_iY{3nlkO$4L`=l8i7-6^UDCZ-b0Q^OU?6;Xr;B0juupoiZjIt4<6aI2iexM- zqb6x1Qhl}gp!4PkLL2Sf^WXBW zCX7x!L2!^9e#i&Kx}US8o1W=fCc`F$af zg)HQa(v8arGPiG0=JW)w8eicIhQ1WNe{udLw*mRkn`r-CUicN=#s&RX7Z6c9TeAL_8pzBdotqhd>m3UPGH*6F-dMU|`G)N~ z{2NCUrxgt+l?r)sNO8#dki_36E1dQBsKeE=`T8gtBB3qDGdeS@GvYH&GlCYE%OXcK zS>vxN$FK*zxD&4UfHgT`D9$r3CV@Y`R(bQsZ7bn&9wrQj%trKj<}B!_X>9@SG|M;KVM74{IuaW-+-7}2Hvs|3IWMK zv6_xEk>kJSWB+h~<%f)|mi5=MUj^SU%fZaQm?Nfznx8e-J*$p?+jl=l`Ppi;#_hf9 zgN!SWlcbRjOOshuQO_+e&t90#Z%d07xP?#qkQ$Pvq&D;+{o%3*B=u6tLTbrFkOj`N z?IFcOnv~*)5_bDZxjhyY+u%ZQagmgpCZ%_S0j=_4Ag z8haY}jFj%aZnt#Cj7-X^+XyND(cjqS#%k~CxjF}z8j!4){M>snwp7J>caK-|b$T_X zE;)6fHC^uZu71-SOZ@I;n}N-5r>SNIr*c|nEArrZ18#7eSa`DjJN?RG4`xcFDy}Fso-FfrAn%(SRJ3KhGvdP`n8hI%0xOy|PZ9O3FPl1N=o#p}=X=<* z$L${l;{`F68!d+I5tfXzGIt(3`HW9^j~z4}Og*DgY>h3ry?5VSz6lwE4UcLe4NP7X_78PNyWUX}D{oRLE%+wTCp~)4=P3+h1ulkISC1oKr0a@jxVf6I7C;VN;c zxlL##ZPjhvVy=;>&UD}1|iTQl+?vYRM5vp6UnWRhN4?HIn8u%{ZX+NxU0M`4UA zmd(rllx^?xbNd}_5iNwrv^4$ewnMJp{5#zN-EG~B!W{j_#bt&S?p=A6V)mta7xmXm zL`zH!h@KfQCvx|mu6j8>9Mx)=ZhqF+#+cUhSY%*iK*x#GiGT9gR<#p`ZS+2UQ8j35 ze>l`rIfLtVBp)+etzTI|F*=*Qh#kVL`gul8^fd@u*`xIneE-ct7qitDyNP z41Oo;Zi?63YF|PQv7No$H`a!E(=MggmC&+8qi?8KiOPr)_gT;x$*0L1&5K2BNxLl- zZH~TOtnU#);3cPCeV=U#o&p6*_b!YMz3N-F(5}<;b8G(*!v;Smz|?rh^>Bc8BY8*P zveeU9wZU`tXk<0x@rB2T@t=){GkROau~jw`_r|&F3InRP5nJd~>9y05iRDqBgTNm| z3QB6`@r@ZTkl%g3gM7b9wxgWRcI((xM75OI0L>8g#Qi6B!zRIo*-!O!;IQJTW@3jJ zbdl(hTr&x|G6`)58`RfvJL=V*<|5Nnii_TIFe10|uPGf$X_&n)Pf!@b1{apQd zgPH5nkAuRGTo>)X50_FSlJdr{y!@IC29wkmU3`tWgunjV{~1$`_$fC3yUu{cQ2*k?gIND+Ar5Sx?G|8V z`KyV$J&4snU5iD|*%iVf%Kwo6A*<9C78VvsS1W7r7xK^kaXRoHh}G8J{gt?YfR~pS zzn3t-v#X7OpqQALz(XMcAt64X1)rOble@V$pOYKg-<|xkA9;wIr7QH6JJi{U<*Z+G z3ug~^5G(81h5q&V`#vGw(0^aa$?YGH1w5d@*%1Lj{)Ynp>Kiy!@@%iT7StQ!0G5Y3 z0%HbTL+a7vN0NWF|G$p>`-=Z@roq3@6nrf5$Ep8u=>I%b*A3z-=j;ev(_QM{^YxGG z{^!AeoG2-9_UQjH6n~HNUweUpmbxM-@UKaex^ibH@)IzRccAi`I>0Zm%KrMS1Ap%W z-?QI!64Gm0?7zOCNl0W!l;xl5c$2PWkXO^TX6$3wGdZnYnrW$qKg!?i3$~SyRvCYm zDEXk|3(d{GmcFNg_w8n4zEp1#8ls}IbDlkQA+5R}A^u>43 zpyS`o8-0q*#Vl3b;;YpeUXwMfQ8a%o$@g3joyMQwnKT&6_;&i1HBzD&DfGgN29PPRU5cVBuG-TSjKGIt)bfqm4oO6@) zRt{)Czx!id$%L`q;6w5?9L#LYC2H@;wrE|&**f0C|j(I8oZ-O^F-80)D;-~OFn`r!GiDK+sT?{uS*Unm( z9~H3(%*66t-=(YvB2YhQyaJtz4Z_DdjRNA0s+}rrII@#&!|8~V^@8Fbi@5>*#80Eb zx@();{S?}-YWZ2e)buHT^bTwq>`dq!5=Wg{V8O)B$)M(U*D5}V#7YyFH78;uHa@iY z0oOU6`+P1&`otT*)y`a}N8wnB*s*MjauK%aR$ED51pCY8HY5WfADS^)NKIWqb5|qIK_=#cZMWa0; zO+t#WO^mcj;Ng7puXjg#5-N?w`{Qn!kG+5YYVuh*8Cl#$DQLfN8Fu)&Fo?_79KD6d{A`{jA)V=G-7wA(%SHDDG~%AVQ&Yw9_L!<+0q~ z`X(Xgf;i1q!nZ@jwa4SV(RV?$tksC^wakKut8WuI;KuzXufAUlJokjG1Vd!F1ThxAT;{tg5Y{zvPE6U%zuJWMDCRnqjPAB(-hd%t3rmc;Tc_ z*L38>F7ZP#V(C#N%C5&}kofxi^)KG>tPYw>!xVII6VDXNgb|-fpT$u4*Z~KMDPXsc zKixQ5-6lnKE?y>SexO^p`$@C%jYs;S+Rc7d0T274vUi1e!aZw$>%lx*12vzrSzghM%3O!W zVIxE`rfi4Gc7KN__I=Tk=O~0e=&*%G(>=|hT-kJ>w#hP7lqiK&^<4m56K@{)_*!5reGOtC@RAf*8!z`jLs;@1v{blhODyH>Fl7GPf+aVnzxiv zBNcsfd7LFMur-4hV1qS;&Bm!P%3BY=Mc)!+ouhKd~Lg>8cn8Pg0$Vm);FdOR^{6bH| zm0FP9dSKPUrwt{5YA7Spr}6KfBz`pQf~MQoV<65=}LI1bLOQE@Y1ghsneJ zG5pBfRmV3>vPP+Di(7eRcqUnDifm)Xri?PQIv!s|T;uQ)p+sAD}ReQeCS3~A`fBnt&?pEai4;ONtG zwx;)>emCdNoLlU|ZfNG(!EsW%k#BZ9LqBQOqsZGcvOlOjT#qax>hqZhZ%C6LhTZl3 z;@oz$TDpl`Ge+3&$=iUlihp9rrbEy$c@xLV!3U{)_O{_f*J)uv(Twe~&Fd42)VXj| zU+6_Y`Us5gR`dQ+ex4*|ET(I%@ptQc;{KR`mk)%GyJpln4V-)|_%4#V?TVxMbor$+ zdQPN##VuMFNCR_G0Hn<(UVN@KoLj&%z4f#9L@{FBLwatDt=^K_c44Q`Fp>&a&^LYi z&%q@nbANPBoeF0s@?r>zI=1xnxlTj3>sd#x)hUp!Hc-R)g=3D^Ww5glX6o1bCD1k6 z5cO;(3x+|?ze4ydFnGyXQSe%)_i=d6<9ih3#uqfG&6le6Y0~UN`uRD3i3r+3eZxmf4=> zVv1oU4ERiS(}KH|A^wp@jy@gkek)?mnpu3~_-BN1B?ovR2k%@iMWaOd0W?;PhYbmf z;S-4T6DA1w-^H+M!h95rdFkh)dtvG$7m9?MOxK=7s?(2cmd)oXCm%|wav3 zB*~?bzJw=FtloXgeEe;bfxA!Io>C7y6L@mCSYlM=L@SU32IKJig{2MKj+?m3!i!a6 zE4ii1*t|LhVe4w=Ai`Kf24FYPR*bVsQea^6{OIRj4@gVvwRuG1cO3Kq)z!t!Dg0W2`xT;Fl+4q=`P-e6hPH~aL1)-_Ou|e zcYlD57(=!?rMOlC_*;KQi33tF4sjV<1P6;Hqi zoqFMSGEc_|dsK#EOUBS7)O!q1sh%O9<>vsuIa$>(!{tt60am0)q-u)qJD0Ssil|3bSslhB9|l{?YWg#U&(;!ViR0kspl?TyM5Di|D=3R z%|=8VQ>Qi_hjUUJJsQpQ^ub45 zhynm?ZVq9moNhTK<oni``)6mP;fH;v2EgPC6_iV$r&i?V3$m+L6cn-G#vJ#F3qs_^<&WS8)ZzgGOY`4$ z`zOus9P(wd%BI0`hA8}(%adYFVZEo1eda4yq+Xa136GYUOBJ zz3BIxAkW5~jLm}}wLMhUHuuan;2ZP^NG7+Rf`P2E1k|W2Y_Pw@n1|KsQKYI`K=S8N z$uVtP9^58`OI+=J|E(dTQ59!r0gSKqtQaQLPhjFz^EIbXT0Ny~=#1hJuPgOo6GV^i z#i*~LxC9+TW(4Dy6i?ps_G}`Yztphy*;Eit*#1Y{y@o55uqdQ2?t=PcOF^ssKv_}m zXCi4OyCs!Pk6UY(p|~pmIs+w_eHkKA_3l|;Qf#&$s-Fwgj9^bth)%Zk)X|_U4zr<# zyye;vA71+0O$S*cAY}Pl#Bd^$|8}8~>-dpv*_gdR=1?EqoiU=d%PvzP`VBO{#j;spR6yB$ z>t`B$@s0q;n14dbUnw=$6TEJO04urwV9I=#_wl_#IV^= zTCU<97x{8$%@3ccPFQ8130Joap=l+JxRI^nX|lJg?Xwk}GxIVgJ*9}d*olFB zZ6XJ_(AG>^{dRaFr{#62gO_<7<-GbR#Ij^WlxA+Yqgicz`63XHIZbjl8+*kcYaS@O zeI-w=4vO}jUf7g(hgn-_&u3o2djh3G zQz1~50-ypT9Y$xLGOB%U_4#P`@I>!@}7;{(WZ92ZrF6tm^;6!79Sr} zV=A}2r4L_VYDx|_xr$%DYIdOlDcByDzme}TY*NL_IDrMyTIlvwWaLmo{pMRc*|eg5 zcS^pxwem?7(QaqyE54jA9+ziG!YiCy$(!fB)wS z5nhL^5?#8dVbA664sed55f{5+SQ+QD?1i? z%lV3ee4Fmqa|Vt2e#X+z1Nm;xVS@bEe`94=@O7*94&PfD?>w7%g@^B?m8 zHeWOc9=nzw#Ctq_QltA>GE;daXOzelsm3Ql7vZRP5#5^&`A{|KYfrJ=Zosb+)vFgQ zBsLxRia%hA^HszC03qP!f^L^HKCLMDr|gfJ)p=TSR@ZVXVuxnn3^6O$WyotwkvU-Q z()Bw_vn#E1=-0ZXiL_rqW;+OVFvNhc-9V|v+q)@(Zn5OQnf5H^&N6D;RkcoM&vrg} z8Z6TMhW>25my7UItFS*w6%JNg1QTDb!&yA%ll2YDMHMl5sl9akhaiiV_dkgvRxV%Q z`Mc8Zk9gM&J(ydgdVF#8+K7>s&#G>NHPP{sS;dIOkm#Ip{ByQ3_@H&u9~@kaO(4KYK$!MQ+TI#W}bqeG-0faI(9g=Rjmufg4>LT)8!e`s6(fvKl zIsU$d1_rFcBFOdpsVeTK2To3w<`uZ)eH;}gH-1lNBCS=pD>oT%&w-l5+KEgH4u{$j zn#>}ft-@iRhOE$o-_zk2bLBIB^Z63Bg8)Fv>6icG+)IE}aWhnDE8?$woVIU^af7U+ zbm#my&`BNH&&BZg-5m>p^tVn1Eu(;FC!s7&Ya-OY{g<22jU4`B3C>c-d|mcQT$C*g z*ZS5OA;4pyF7vrq!U7BBegcM`&AhRDpTY)e%xTA^JVZ3;-d?-2Em?;8C_M+C3a<%ZZaqge=5nPPT~!J}t0n z)OF6>FimZkg4eo+L(p`l!Wh@z)qkB)^rsOV^I|~H?5n+?BDik_!XP%NdrsZf91hQ` z6xbwu!@=or4x^irq*y|1#fTO^rr)6Ub4WWz04w z`gqbq>cwxWll?Bj_2=$q%fJ(0e2(a8Z<^8BQ=1e8UP<)Z8`Pgj)RbB#`si&ISI1hk z*?`s~xi#Y~us+}NDeJtpLr}g58qX;hI`;NxgN~b6=%1cI?IT1sjgv&g=N=p{`s$V< z&AD>x#3j~y7GsW8d(d^hN^ijk1g_IdXjd&v%r9)jewM*n)%|%vp>}tKg{n#m80zd8+Fz{;g*TePv zJj4p#yKs*ntfr00G~L(T+Dm(mXDeZJ z{hWKNGQ>&*jxu-@%)41tydSRJ;Ccl5c+%qv(-g^4pGxe^`GPTgfwaXvYvGZQT6HuV z-7bnQTjoVGtxtB);8y;^iy{R9LmDxEvZQWgNO@aE>A-q=@_`GE`*)pM?uxE{zs&3o z&;{j`{fyJpH2?{lQNLA4ah4ZXiDGcQTRApW z>U`u(sxSuT8>X7GGU|YU@&gb)pWlawD6z@uw&_7+@W#`}2!lV5pjlOKrjd|gMDCTW zwSYe;iUh@Pf%(uS^OdD~wIi!#L^vE^NhnsC`A2hYK1^B(%sQM+Zl#*3!VF5qwgUa6 zSY!I+`7{Mx?cFOBkp9piGD9Jqo>?UJB$I|qWA{5}TPqCSZM0at(fIXxdH2wV#Kh!} zoKEvCy}Xj0ubvx*h(5PnU7^KwMy-x)jZ$rdq2_KgzE};c*zDJ`O^kEEoWlvTm=tQo z^+YCWXwMnXgluD-GkZJi^m?9xWy1JB2dFsDR^TvZ=3)!6d_Dc`cL7(8@`82JR7eFJ z9l$vowxXR69z@**;h3?7Bp+m8TzSXagjOUxPrPasK#Td6+i zp9c=|gw7;-l!U0shcFQ1-}bpTsTgf*Hlla@=giHLKQgh!GVYbDB^FkWcPDXcTo!Si>FK;LB*o`cX{kpjXf4GXI-Ejs3-Kq|NlSnn z&vSk;3It%eV)EN(9@mfDv7ixMlYzRO&&jK!n#Xak^3kVsUg}C4Fm9CM&UAtukht#* z`4kcaOfILrq#T2z28!Ag4nuqBLr{;VgAk0129YR$k2R)mA>2zdot3qpaNZBNdBxE% zVLar+?xsdzik?_pWs>OytZ;dsQ(F{6ptLeLc+Bn!m|$hcTz{6Dn|MtOUSkqRs*3>D zq_pq%^c>*0|r{(q1^i-zZj4uGoJUf^%F#Y%^i!CS11=iv4 zM5NX$s(U%6{GksLsPfU?o)@x;l^X3gZ@b~7AKF{Am*`8_NyogsF8)KY&jp}%w@!Lz zJ`J^GQqQTA3~KkV&SLdZorFpU?JI(Kcy@!eMzjF4ZxIgpQac**ij^9YzcAuZgzN&{;4G-wR-+iii- zFW8^x;WLx%71z(_b`Z2|u~MNZNgF^SAAKLdBYGY`3#eh>b+ zcxnBPAwF$lGpiS1=zGN0M>+G#N(3`KVa}70rppI=>8lq-&`a#_z`kdDLsC&-)isrm z+MxNS{bl2|%B!{MrZTA@_CoYGYI?DVQ2Il z0fzDwDK+M`!dYd@=QE{nM%!10PsCE!;1pcE+mpSb6h-yq*}kizWC)-2o#^yam*rfz z_eu;?3e6H`+ocJ{8T(ShPYk<;}6qQ$i%=g$= zMGusR@P^-uvUBF6d!8W$3SaolLt3vjcYdyD=%&HR?n5$Z{+>>&x6J=pKe%Bl8T^^d z(KkmACg!%-y(a=wP=xcF2-%ksU4e2}=v*r7JC^-T`i0Amll=Dgh&u&A7W4gy24(Xd zDoT1XI00opy$`SQf<8T34(#B7A8hYtRc`4z0hKF<6M!-sw%Oap2yo5+DEP*ydX|&v z@w(78bD&DryDEx$^`cUxJV#6#)R!=4G>)#62@L*n?DbL09H|FEfp z>#_^Teu!7#$x3w+zR2T>a$Xy_Nc5;tGPR-ZwVqAzxbr3bH(hJ_wiQy zgkbxf#JNPLO#TqMq2=Uz1JdQ6hMk?FA#P=od;uUu9;2Ss*;whxP2rT}OxW&#D#+Cr zwhXrEc}kF~a32g24R%F0t_Xs+x&uA9K1aR(1pCw!|KsBKi29$TK>;nBn0Us4XPe}h-mOi-HIozo+ zRTb@!+4=p?sL{pNdoyc2Uh(eM=?(gP&m(5jTm})lq^gAul~U!)#KelW+xF~mOANN9 z?u4{-ej7Gekj%eXOB7D_nrOnRlEUUK4wFEr$2~$c`F2KNMD!9~h=yr8#Macz*4X|H zCpt%qy*)u9+s< zr7?C#B)o3}%<)`3ddXrMdT**pB%jL#)N_qlYTU7I9YqV5 z%PO9cZBw=lXT^-un|j}4(}TnzE>C;;dsqI%za=*_Kay;W?x|l@XtL~#WU?t+E@E1D zk#6y41_xm7oPh%-IHDH;@Y^r> zC1gCvHYbV*0$tDdlC;Gq7WdOtt2AA#T-d1u91;WNlt_lr9PmIFE?8bqQFmGVSfbgwLaAuD>V=jVOavKRBU24(a*1!Bk*ueS_JeMiyQ;uaUp1u&(nqPUW zo#JbVfcOlJNhLEjkQVRQNm>#FL^D<_OMU`N2Mbv)WZJ8z3gguW)*X*J$Xp>*6r2W_ zt9@vM&^@-BWc{inVGPTM0+X)PwpJI52udYtwXZhi!CEO7^Lv(7Db-)yb!nk0@!FiE zn~N>myNao}nl>7gdZ!V%wSYQ@7o(k(vw?(lIIq67`LtkUWJ#DF$lQPL?ZHi#>4ro= z0^z2er%{|zl-m2bWk0H5k9b2^%3iNTCFBE;t%GgqnJpS(3x0K@+3h(eD4^eeP>&pt z7k^(%UHJm4`FUlaX^&Ahd)PY7(fX+eGnd+jUw9Ra@4J|uNQ>f`*C0x1zet-~e4(?;5#$|QZN zB+Cuh3KO1lODD&_-cr`*`Wys>gAYlac-o5@CC7z-wAUxbMPr!-W@Z9*(;zD^dcNH z2b0zKXy3Nm(FY)=mdkW_3}JsO8R26@0e@Pzm^Tx(#pi{w}9#_)ZmV zl?(0ps^=2!XlXM&+UXm(+oz~;njW-2YO`@h(PjSH94a<4Z}E);(>ylw-wMSZs8Mu> z3fsMC@g1YrEbX_)Y;zp2CyIp)l!Kmm~G8T*1A1trV1% zg_HdjK zan!xkX@;r%&OVDMZgH>oMK6&zAyE|Y_9$h|+y89zC!yD^PLfsZhG(w)C&T+u;~Z`Z z3PjL|P6a<{XrtPnMS*`-lD;)5lVmwguX!0>`-e}Gk&z(~TAr*!hKtZA7Atc9%LRw1 zWo+&FoK@cb`$_&O*83w>x?vy#XbbsRn3RS8xZ^)O;6G%2hN{3rRMvA}`vcSc58b{b zz4y8o_1c}i>wh@-=jCnrNJCIV+nS9Jc;}@wvZ1-$NUIw!R&n4q&-)=jQF6QR105c0bOr@>;-9#|~l%!0BC#e+g zOzRm!b5pl?--H`Bo)R}r0H?uMJ>SmcHQv_+RBGS^0O+&NXL`?sZ)Ant-Cm?O^Di^9 zFRo0m-(CYWYEDB%V5c4s-u>-m%93imxw+-MOivn4wO7Etbi$d(GT{2|)cf(NNTQj? zpzg+5rJeeGS8?OXkyBoVr>l`eS?I;e)hz%Co~{}f>U78A89u~FR&pcK`O!^KQIK6V}p6vu)n z*D89X=yY5h=z1b#Z+pMkm#PKq8Z%$7xeUn*o(Rin$XhU=o;e5=k_P<4^oEQT%&V;W7aoDgpof9Qf@S zxOX~B&>a&$AUinIbQ~AW!Q(&|!muxH10`IZjYigWZAQzs%0aW`igBPQ zv&>)p>s(dS6&@X9r@sdF6}2BTal@<8Es;~b!0VZ*Y;qeSvfHD2M%>G0#jWdq6zl;Sst=lrA?{6>sXd zF7nuYskeX}X5tC<0;Cmn{3f+J(fUYc3!Kfx_Fc2J&2v17e+I0--4Ih4Fskj#krxX% z0cBLUtSITxKb1;5ak~8grj(8r7X49k{C%<&0!Tvoo zUAKnI>&pm>wW}qC2K}gtsEQH74?sTm0Ao?}#a@$N6SS8kvGOIyUl&VEE(vlVH2DL} zr37HGp?hkC!f1eMF$qwSp4s`Fmw;u%+^Ya+yvyLD{q;W!a`PVnENtWAIa;sEb}#w&R%&OBZHf5iUx& z!a=|KyG7$mC=HgH_R;l$Zs)+AG^h2-VNXLl`}|y(owl%j6|alzYDKS#O!#%su9kE> zl)=CU7s+QYAP}j)eoMGJ$*j+PvwkNAxD3&s)?M?gLPc1g1^l81z+iJi2cNkONv-?n zzst+5nmrTUwbyPOo=Khp6TE;La974!rMqBzwsF5t+Q?BwCh=#a49-iX1lCK}oQojz z=s%cY`y8+rwdzjGrHNR>6JB`x`iwt|u5eEo?B-BYOA%`AN*BYT0rLzrUa3>+k>jIH z^V}O(ER)qKtg-FT0srdka8-HpK3zU_(|=K7vXpYYCT}sn<}AIoG42HNZ16jeE~v#(7Gl43Uv6#l&8IBy{{e0+ zJp|rgSnvv6pBA+3Woc{y>MkK54i_a=#uV(ff9lJ@$veZqL&Vq`<_}5LONtiFOg+U<4x^Usbk_> zw5%SaF8Xd(O*VbMKJi~7ElFt1etrCiwZ=>wAK(2lgCQr^<#5$Vv-MkmT(f`Fn-afU zR;#=#2B<)Z`4_|{unQL6hK5WW?sh@qMux)moTpDOGbOF5e`wI#mwXqQY;Gx|%B1o# zu~mr*P&+=UC|{{L<&7Cz1T@C@%fWlfW|0t*P6P!PzM%2q=xM7a$Sk9VCdEM%@=had1O(JDG#m)hM zWGN|MC7K#%vC5!vHy@GuDv69kwQ|@jXrd!qQ5MFD{Mm6^Q_s;g!X`V7^P3fzsy2u? z9OPkA>rVcbI_Q32E=Gy93qKC1o;Cmr-nA)zHV|k+{Qxj0jUeO!lP(|&O5wdU&kul^ zE8o@3MFsg9(|HE~Mhui!wdq*H7cE>dpmhfgkbsKxa|!@e4?~6rG=P@jTl~9twE%IIY+uuH1^gsBRo zF*IYhlLu|9)h5sMNbQwvM`wDZY9K7MEsI%LZx&6vxw*qGX zsjWYv!U1GEK>uwqr13+o-sKHd$1n*nC>*kcr}<{d2ymJN5%+xhgqrtEI450?8m?!*FD}mFx<~O}I-J#6J3B0t z^k`Fy0I@#jlP>6C)AzTR3E16!55qPMm~!ejZSiD|yyN6NFt3P;ZDesCXThU*jy&aY zK(sSVSeH{(0ZI&cjv@EzCJR6~hwKM8qjWBlKTmB*T;d#_luav@Z&7?An1Qk$2o%L> zDsBKG&S=8CK+rOAI#3X97Fa|77J0J(rkaFhM;ze+RK1URJ?X#GC3Q?0bnsRJP(-0m z0cftaFSbJpD9I-wgQJZ>rw^YT1-@s>tku*R&ezU6^0iIy2VQd7&VIu=q^r5t*z&cv z^*3@iHC=y_nyJ;KNnz>HbOFJeuwF&^Hs`p^Sm#5yoiAq!nWK5FspGPeiGX{ZHFI1Jp8$Y_EGo z8+&B4_>kv1SNdN;YXS6Twb9Orp$AjjHcb^p%aZb=KV@_>{Ik3sAAJKb=lm-_w!dHa zV3?TD(3BsmN9H(VPxnl`bA{i%{025{!hNCOFXZ5ot5m@sXUReqyFdX&EEGI=$hImY9kWcRgt)If{^nl6?0^ zDqm$!;T(tEf6nCp)+)Ls=K5(2=!tgxramS8I`N>;jMzE6T2#@!JrpZWPC&tW^^#QC zHX6%GEOGQGlX+UEVe^Sc`<0TGOG!PznXeQpf{O=k8gABNL%+H z)&a^-AZUmziw}h;cn#x7GPsx5p|eguoTEq?WIC5aE1M?I4tD5(grCoP8PA2}E-s4u z8H&*a8~ELIwD8S|7TxxHo4cC8@%|k_MEM4`-x%kn-t-zq`Iii-!3kBLT}JW$QTLT$ zS*Bat>R=F#O4=wbDWD=P2ui2KLrO^_-60Ant+X^qcRX|{NOwp)l(c|!_qT3mX1_Dv zID6jj=l9FK_puM2`@XO1TGv|VI?r=;r(Ui7VxBUs-AMjUf^n#u1__L^JK;T{I#CR` z$$Qp6_(Q8b8NO(?2&@G-F;DE3lf>}E@1I+1T{X;8YZf84!sJUeOZ3*%(vPA0!qraP zCXqO$Kmd#YNKu(WyfOHI0ht+pw+z~tnQROkhVwiddOD;*n{j>spS5jnOw;NeW%lCP z{QHGVlpDb+yC?no_k=_#nqS^oKwB;^kJSV^NX5JKWvK@0Ez}AU(I894q_5}sKPv{8 zA$IefDpbuhw*|f0?nUPr_mH7kTWhtXQ7ug08|R$TpI(7$0JKDm+Q1dKmosH$xJ_lP z7I}LpL=7)flixe`b{6XT9eyO5eW?%YFY&u#QXE(GwOhdXZXZsZc*twbaB640E)+qt zIVy1MkpJ{ij;B6@YYOFDm&D#zeQcQ$$m{Jp6&`O?L}HP7YT+l`IiAJcJBh9e3aJhh z%Gfs7C@oJM)n=efC-cmbe}vHTlS?ryerZQ3(jWE@U2#G=Cwy3k09Tklczn$&XjHi~ z#_`}njjTT9oc{X)GXC;l9fC${wfPhW(Y@wcbL`rc^|i#54*6%1(BRBf(Hw-jCw(K7 zo{Q2)ADfeX<*a*hm>F@i;FPFhx5WcdA$bdZI#{9ZIUPW_Uy$tc z6+cOIFTH@tM;ZOwO_QO8Q8!d!Gbk4eyZtH3elE4r)`{d`J(*WrfpU$@$YLa)HY(@0 zm{iRXyfY%!_tPq$gAFjFodezY0Qw>db2*Ff8(jkLB0qcoU~rf~-ff;|)XnnP{Fz{o z#Gnwe^);gI%x0H{PrX$QbdSbV9`4R7OV#GW=4WuZ=q0E@iF9M1dnV_!F+x90s<|>z zm1ZP-Y7&7WBzShTNv(k6x2gGHX{3SxS5P&l;zU7v7(LcxPkZ6aAmrl8TS(&l`ZZ}C z7a^NgJ<6!mw6XewKM!Vr8@vao#Ja1)#MeW|QXO^lsSMc(YO3>A_c5oJvRw`gN2czL zDRGF4IzUQrZ)@&L^TGZMZ+3%viia22w~&E_ zR>};`OP?skLN|fCb+JefbYiUcR2A##T}FycCT*FUdzKw2(it`Sw^t?(M2T>^Svm`i zEH6?ws{=?d*q7nlV1TRf6P(mi9INhHe!CLs;es7_g>MD-wljQBngBGHW# z$DqPbn@(DdU4tx*iqI30vW%&{B(e8hkB&(&jXjHZ7kEn0r=3fQOIX6ChDa(Q)i3hb zYWZyy&XvM}`;?hejZ?`pR*&Q(*PXY=yzJ*bW4&eJ3T|5s{FvcT^qw(96y;4LZcXQ_ zf)^@3>XN(QeXUP$w|@=|kYrh)1jj95{2Eqam9DiOSxI)3Nt3(NbYok}+*{IN zHX)-;g8(iKq5OR#lJ~0&r%3b?kzB89gZ?Q>_EjB~z8vVzv4V+MhUT>_t zB!)S!&4)&N;RELc2ZgwP0w5!>*5E!54LREVxx6}vMY9~X7TxVxBHwl%>gKWFsM6pi zy5GarW2#sWC}IgHkp4#DxB!Sh4-FxbTTe$#^~tz}ZwE9JaA+T=W`4{liC!UL+dqhly$;dU zaFXCXu-tmhyC0{y)ABZEys7Ka#NZGl{lSXHpwx~2!FTS;K;@TzjVC}gC?26WuHB8x z_B^_W%)TwkoIW z22weGHh&kZvDclLz5y+1sy^+h?WiTk`lZvU2P9LP#dqcoMf>h9aRK5m!I6DkqZpe} zA2;G+v__@QK1$OejHi4QdYsXqG2vb4J4`rz&GPNJUWC9S9FGnVmHFlq9BAmd{vuDwDQ&lwgEqOgNAHY4RQbk2syUX!Y&u+=2uQBF7P-mp|qF>^12 z-R2~vq{r%g+0!0>i=R5Txf)V02wx*8XHTWVp{LP0(Om=D!>*^Ve*+qGSg_nPMH)gm z0t^LLT7~&6wyOI8kM`;ewy_4vi-h%EvSq9JJ?6~)T9uc|-+avM<43WWYrhEWLv9pG zfh$PvTOy~RdbcE`AR$2&ueQ76B{gz!B(LftVcoqWMIW;ktx5YS!b2GT0^O{5&tS$Zd$YceIk&1j-cFb zwl50DjbYwhAon(4kW;QBT6k@b>GN@>>3%f+R{`;lu+(w*qyFH#6eliRt<$aTq~oxm z7;2!Y=CG*8L=h3&S73USD7BBBIZVjJfOZc|4euF%ha_xenxWxqRG~rNqA!dlQMG(m z^rEv%9Z}WmENNRMPo1PJ?ZK!E2DaxFc7N^%4fj$n(Ay|t(2sWzks6O;vjpal4h}#8 z2`huMz2yCfuU#9kUz#(4ug;T};n#D@;-(>PcP=ZoF~08|=Tghr@rd&h+aAV!wH@J- z1jBQzsCoB5KW_VVQ_L$SO9U~^KZxvVw%4C+0{&;$<31KU{K1x5zx*)ExRb7RvGnwN zn+t|=EAeTVTgiJIhD-FSbTzUdczhl5qBO~j!ut&Kug%>B7fEUomDtDDCZOou*ua~3 zeM0CG;|5TduFb9V8K}1uhsb$n+}z0cGU7-jl%7rbNCi}x!ogOgv(79e&D~w0>`msf z?I+kPw&Qd>3aC?5it$q09>y_j@^o#5yKyf3k}{K-p*%I>yl4D;Bef!HGB27bb8?*S zf}>k|Wk=K9H{%r>Xr`8QJv$MC-++GvL#(H0H^7X~7rj!8Wu{Z*c3CKEAMcmmQ+~#9 zGh<#5SloaT#e9kR85SM&TPUQbL*IHYz))dk+(vcvu7;M$#}~&@xCvkq*vkR`bnqHj>R@<{|rg?B{WGY;pK#5Kn8Nlo4i=#Dm@zO%#Qu=zb< z1c#Kzv3L-wb_S7%4^&z@oo}bQ2)gsc?8n?m^5W&)RPARIO?>b63RNSH16!m=E2NDj zA^!W#>W4DYlBz1_-R>DC&>uI7;c_=FolR0}021R>ppbMc@1i6o9`6mg(KD#&VW-^o z?KBm8y;q95d`+tkWyB%w5o0rAC%j_N4*D6@Pn6A?WRc&BJ`374b&0*yY2eqVJTGb+ zB8uxE|EPsIL{}bF;Fh9@EF;Rw7d!n6&lCzAlisH2n0;_8SWk27>!@ADTceo>E#5oh@sGNRu}?7!=MW-$?2b!jnqilcurg| zz3_>Kyv-;C9EoOXjB=mG)dd#{!#6<6b^GLe!g8|V9TCB1cuoLOXof|Qf|8!>Da;j7 zv+6wPRZR&}3`)|Wt#NS~l-schsJ1IopoLLz`^r-2GMV9B&o>2;R3#==2iBv{2*`t| z7|{AhiBO^UHnqW3NNEdp4F_(BWyISAg=-t9%0J_E(4P9&z&4|bb3^M}&r%*YZr2}V(EXf~!SgokU6C*UKIk}PJ!-d5?lj-NgDc%(&}zO2H+ zFKJ6baSvZ}aFga&jy8=v&g&(vm->EgJ}s2Vk~B8Iv6X?f@2Rd=L1(_IypuLPLxyz*@~l4P%X$MJNDr4^^HeUV2T-C> z9!NzBI^Xtv^Jm2J>lbAuJ&l#x%)o8IDlJ_g!|&Uj0WPgNJnBumW+AK3_Mw@9^heq! z2cVf-EnIMC28nc!)-!`YaW0ilP+u*mk6?&Wp!3bDhbqvLRx~gkAvuu7e8KvhQCh$J zgojpkYgyBqVS354T&Fd!iT(<4bG{L~#(tg(NB4n(wF+IO8sXc53@i%n)6I)Sav{3? zB!UMP(=BFs1<^}PGs9hu5S@p9th1usQq6tN{gU=-pT zC=3Qx^s$?fV-b3dNxr_#JR{jjXD=@Qc3kvNlHo~$2YSt5xuyWf-hvD>&R>-rKLO2B zz>G)U^}~VWc?_oNT*~pE5EK+h1!R{?_?n__AR}r#|L^;9uw< zfBVadrqF0(D0Uj0RW5{MQd(j$BafZ{#~n+!ZBx!nVEwH%_Xqjnw?{&shH7&&R^t6# zY_Ko6wumTtS3K}8jU)1~qVIXly<(5Cev-8Rd8IpOp+L$c6K796(_n6l*suV7b-iB3 z)AQdX1gHeYNp@fHl{ME(Z73pMT=75B;0}&l<9X{tWDWd|wgE zk8|AXZs^F4Fc@^<)aQ_7t~!6x80~`3_@X`Zbe<}&Pse5cYD@9o4^J#5JYi)WmoerJwT3S2 z6XV$I;&HDiIu#N=4*iWVFujIz7jwjSv+;G6XYPb zo|^60Nz$P*o98k0h*^ArFhV%9KB#TA70r zW`nTyIc9+NZ4FG6%^A)GbRg$?$HPFwY3AdiGkMOO-~zJG5@4;$AN;x8mu;l~<$kVF zDMI&99!ZVPqnXDP7zV^-d;<$3Mi<5^fhshea&+SJb8!rWL^`*ML0^lqy8qul;W5`= zA3lvE#-RN%P%bTUYC(qgelK@GO#{DJtOdt`i7;nXAnSJJPIH38gfH9B+nWy^yPP%$ z>XG6%#Fw|p+~n&=*O4k94wR7l-H!(sCFo~UnFM!O10Scj>Tn}_5j>#GU}t7$zub)B zoQbqxVZg58_`ig>e?Mmne8_V_ERbNDJX22Ji@q}n%q800F>p?>`r$rP1|U<#7p04z zurl%-LW6}73z{-5KlVBj2goK%uXEeG$t@ph)OeIY)?#m1nBg?u6cpN~q8U*Fyo|7- zxW~%0T`a>E?c4;0{J0bUx`6nu?n-)rdOCYKM6-k_g-jqt5lM@GY{Ighsm{dJS0gRSgh`33lW1c^}v?1HE4 z^mW@Ctb?X8g&FsCnwsfj-Cgl<#GiIwx63oB`70&+FF%HCSXaMC$cN7=nZ9fTCu^6d z5kJ|>G6Y)@Ji*MN>!SXSf=A;0kNOEt1X?nA*Lnd{AkJR3H@q~IARHLEc~`cqD@)97 z7QJ9*A}VYG(DWiWml*rgN>TmGm6#%e{4|8ArANM(r;ayjW&jE!r=g?jLzL5$@FV6S zyVN_BNDT>VPjtZj$O;_3Eb~e^r-EcLDonC!94m;m1{Onp3yTSCL9w=oDR%Jd8fID};ae5x znycr*49EQSRr5Dh(Yh#wr1G2tjR72WAe#*VALi;KeXpb6?}hXH%Cg@AZvFQ!;ClmE z>=U*FI-w=t(>0JE6n0m;;Vn)UV$=4+jBTsx$Z2bE^ssO|Ttf3?M&4{ScRyk)fH1-K zO3U{a%PQ3n549O6i_-(YpfJIp)+;LN=uKB(N-muE*WE^*(}8O|cOLbsk+$UrZ>WMw zZC3mD+K-r$y$(lFryH?1I@s;2vLon)1 z7bwy1bHa*dBER_mtknGT8t5@02xlWw$t;2&kqxLatknT^xELi~UimlgN=piOq|}kOC;M97vkn7KZVEva2%Y{zRKWX@*PqV>BYLmKY%!39$^86aAUDB<4M#;N{me8B*UrR*S;+`^BAW*dQ) zY$$zbssM?KsjD(vakz=p`2T(HoY$i|?pFA$i*m{1d>DAg?E$xIKQB7Z=-*E~k^zyg zpRoFAM)~JO`eq5~0EcR*w8p=rV-QCE|NEnFsBW8$3v3lt9ke>lE03Bh8yCn0w1bJq z@b`iIgB?V+?KUyHjfJ@KAa0Din+x*@6}1INGKOau5pWx4ZOnGpCmUMCH6CXhvHg&} z_6O5;vWvrIbEqh)>YV;RmLJQKu-|;#B*dRW;Ic&ITgJeDJRMLN`4cK@+zgVjBdu!$ zlWtFjNNECAaTc&wLY2XUt|kT7{Rm;U;`@3PLspe`%SY@s_hRwH5Fi%Os9@@7+iKsd z?0KyPz@w~Ls5&|{u8z3&)CytgFYRtE#SlWIyvPO#=OkBqKG4(4;3l0?Rr^{!U-6ve z{*60l{wb^fhDJ7tai6r=g%sWDL!=JSs`qQfy7g|shqcWV{{b=858JxV506OsXZJ3?F+Pz2F9Nn&4M2P;PKN}zak`pglM~H*2y&4!#LWw^KH5D z58BN{&Dd{#AGj4h)CqLZlDyc@2Z0a1JK^&F^Rc_;3qRuRFbOGv1^9EmFY4)>+(Rxs zB;Wm^e7L=^6r@|r>ac0VhM05Bbc1i;QY8?-%f84fx7LpW$!8G#xk*Vk%4!H<;;=!| zUj!~<_RW_WDr_Ll*wb0)Rn$8B9J`0=)y{FGYVl_Stf6RswRw-_G~UIxB;44fyv~l} z>WKd++|Yq*kL)z5zN`O6jFnP9iP8bySVBC9CS5ZPdw~Tcn~heeawk}F=f=+&PNQh1 zAtcQ96seY3i+8z4UyK&{Q1K1@IkqVZBANpf7D&1_L#%2ktv>QA=l8GPxy4F{d2swR zI*FV&{O2%$E(l1aKh^_hfJ~M=0oj;@D1k~y_Yz*QwrjZ+hB=>qKAa6iz{08}it0`1 z1`e2q&T?bKI(&&R8BE^m4ri$DAt!mJJ~*Uof83jCzbn=LSx>1=a|nsWsK-LhxxeNK zf8yH5egGiA&`k0>q{eDA$Rb8zT@#l|9tOr2=9wlZI!~m&&#ub(VCYtIq>Nc(Q`(vR zUG0O7zO8t18WX+uJPV#g9M~3cFyqw&nD9c?MRBgX?5{|WsfGl;C1GTsG5ZG3)8?h5 zpxEMeP;53a1dX8iX6^-a_t2v&#yv^D)=3aQALB70j;8FmML?kP2Zc4BY}DR%5sbYS zp>{Au^0;RDe6hUmI*Knsu5&?h6jj%bFw@|#FQNsWg&-IDFfixmm~QzpC|{&Q*ZN4N z_qiNxxo|GzhFE{u)Dx{|AP3i@ha=I>fJOP20SCe<_%Z`Jcy{Zpuowq+_J>ElIQhc+ zNV(@iOmX~{{UqdEnW7;-T^K7AX z@GFBfM-`lHhfiHG`Uv_7N!qZvY8!S7d|0ubv4`w`l(J<-ocRbm~W+ zR_^7|rj9gy=>RsRPP=ctnHhjaEN|I_rk5mO(dMS~re#OTrb>co3OV(BEi@u9*T7(F zq-kNf=G>MU!m=r3E71Zn!vqKkInS=Dg@mfEcL6!zU}ZDwcE>NvJ*vRC}rvCx-fF7d`(vKl1`tMQ83UvQ9ZnnaSOCy3ntyJPy~HE zHxG@EmKfMaY^>h7`H(2!HqA|`abNb8kT(=#@@@3x=*bxRp{_PJKT{(b2^w@_x7!S& zb}M|6Sglp38>arPXr&(ydUm8fp&|J_BUaPsNyNk>@{O?_e*`#nTnCRjM3$g$QDRi3 zd`hP~^=#gH_W@&bx9LE)R8X+G=5t7jnWCb9CU5vXKaL9i5lq|M!q)@b(mXPnDc&{+ z3+?>yITGX%i|lYK2}umf&#TtK6Tf{QEN~ChjDbSw6Y_G%vRj;vJ|4}B>UB0!l936q z3=d;#rkT>oBF8nZziQ#*0ODOIxrY~8=dwOzd^IVVA||E4y^r#1ywuIYA3^`JrJ6oO zmqvTi^IGN0HQpyS1$>K15hjoo7Y*&xwQWW0N=n4P`%Dg|gAs}CoX>pQmCUX%z*&4% zSF+M2-Av=bc$Kb|5aYF8_9eZ=)((pH04JORyreqYZ`#c4re7bBQZ)BNsZLh@{XnP5 zFfi=cKi?@)VaR9TY<7-awrsODWeEesKao4&IHST3`AHyo7GU;iJU~8{`7e)s9 z;k_NF^2wsSYVKmOCxJTt0&FGf2XhK0yX&*gE=;IWeReK}lQh)ccRgRq`<>h0o%o}g z=WSJXmj0tMANE%wlN6n5gl&(s3PC?R=oo1_E3BpTNY4-p4xiVe6$wrh++S6LpagML zUe{P?SBk$wh#_P`r-`v_W3;NZE>gL?UwJQKIAeY8j*_1D!s%gUgrk_P)@zG>vYabU zJi}$HY!pQ>c=h4@=Vi?S=OVODm#}5XHhpMeLczZFr~6iOPoB3@ePUXG+FhcV{U6hB z*ETUopld61`F%6mw4Fk%saV|P^}NV6#6|BLcvc!PqJ?tv{;<1>p=U_D)e+*$x*{I9 zPt6eU+0NzcL2XI<5_^FI{;aoP38^&_o*X z_A55zj|3IdxGFPB>IFVcU2QH6b5^*d)8+d+F`?2R&#eW&wqQSH^KDDsX9neHrZ?yQ zd3WHRfdq@#M%zc~18w+4sw2cj7S?$J!2q{>pF2L}Gg<%Ed3ay1BUZa>v?-vVNz!Fk z)@9hOa1`_ei-Yev#(!kM*i_JsKF7&UFiaKIyWk@aUk#2SU>>2EM=q{qr6@|b3(b7Uwh6_$0$7# zE+p8e2%qnSQL`XSe9zja`QU7Hz!;)a=j}ELXla)X-~HYi9_CzW4U`#RSkvBrHCFg@ zO7}n)+Hbw%*L^M+@PGRJImfaM~OaVnUCj6&wO}}yQczd ze~mAONtJ+xwiG%}h5Dr%5B1*lpNXc@b~_kMTJ|}Hl5mFT;CxAj;jzKQb8m)1Q=r@% zMG!KBh26H1?DqaKt$sc4!hYvs0L6#E=KH&0s1>i9L*!jiJ-WdqojTF}nbM^)0A!L3 zTR2z@OQZ!EWz`~Du>btgr)n?h<8{Cd9k|iK;K*}O*Y#oqa zBF^!d#~IF*-Lse$W73}GBE($.wL)|AXuco8fYCFuzZ9H$|9nXIYtm$GamxLLEk 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 literal 0 HcmV?d00001 diff --git a/docs/docs/common/logging.md b/docs/docs/common/logging.md index 2f068f009d..f45302656f 100644 --- a/docs/docs/common/logging.md +++ b/docs/docs/common/logging.md @@ -2,259 +2,282 @@ title: Logging --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; +Foal provides an advanced built-in logger. This page shows how to use it. +## Recommended Configuration -## HTTP Request Logging - -FoalTS uses [morgan](https://www.npmjs.com/package/morgan) to log the HTTP requests. You can specify the output format using the environment variable `SETTINGS_LOGGER_FORMAT` or the `config/default.json` file: - - - - -```yaml -settings: - loggerFormat: tiny +*config/default.json* +```json +{ + "settings": { + "loggerFormat": "foal" + } +} ``` - - - +*config/development.json* ```json { "settings": { - "loggerFormat": "tiny" + "logger": { + "format": "dev" + } } } ``` - - +## Accessing and Using the Logger -```javascript -module.exports = { - settings: { - loggerFormat: "tiny" +To log a message anywhere in the application, you can inject the `Logger` service and use its `info` method. This methods takes two parameters: +- a required `message` string, +- and an optional `params` object if you wish to add additional data to the log. + +*Example with a controller* +```typescript +import { dependency, Logger, Post } from '@foal/core'; + +export class AuthController { + @dependency + logger: Logger; + + @Post('/signup') + signup() { + ... + this.logger.info('Someone signed up!'); } + } ``` - - +*Example with a hook* +```typescript +import { Hook, Logger } from '@foal/core'; -## Disabling HTTP Request Logging +export function LogUserId() { + return Hook((ctx, services) => { + const logger = services.get(Logger); + logger.info(`Logging user ID`, { userId: ctx.user.id }); + }); +} +``` -In some scenarios and environments, you might want to disable http request logging. You can achieve this by setting the `loggerFormat` configuration option to `none`. +## Levels of Logs - - +The logger supports four levels of logs: +- the `debug` level which is commonly used to log debugging data, +- the `info` level which logs informative data, +- the `warn` level which logs data that requires attention, +- and the `error` level which logs errors. -```yaml -settings: - loggerFormat: none +*Examples* +```typescript +this.logger.debug('This a debug message'); +this.logger.info('This an info message'); +this.logger.warn('This a warn message'); +this.logger.error('This an error message'); + +this.logger.log('debug', 'This a debug message'); ``` - - +By default, only the `info`, `warn` and `error` messages are logged in the console. If you wish to log all messages, you can update your configuration as follows: ```json { "settings": { - "loggerFormat": "none" + "logger": { + "logLevel": "DEBUG" + } } } ``` - - +| Value of `settings.logger.logLevel` | Levels of logs displayed | +| --- | --- | +| `DEBUG` | error, warn, info, debug | +| `INFO` | error, warn, info | +| `WARN` | error, warn | +| `ERROR` | error | + +## Log Ouput Formats + +Foal's logger lets you log your messages in three different ways: `raw` (default), `dev` and `json`. -```javascript -module.exports = { - settings: { - loggerFormat: "none" +*Example of configuration* +```json +{ + "settings": { + "logger": { + "format": "json" + } } } ``` - - +### The `dev` format + +With this format, the logged output contains a small timestamp, beautiful colors and the message. The logger also displays an `error` if one is passed as parameter and it prettifies the HTTP request logs. -## Disabling Error Logging +This format is adapted to a development environment and focuses on reducing noise. -In some scenarios, you might want to disable error logging (error stack traces that are displayed when an error is thrown in a controller or hook). You can achieve this by setting the `allErrors` configuration option to false. +![dev format](./images/dev-format.png) - - +### The `raw` format -```yaml -settings: - allErrors: false +This format aims to log much more information and is suitable for a production environment. + +The output contains a complete time stamp, the log level, the message and all parameters passed to the logger if any. + +![raw format](./images/raw-format.png) + +### The `json` format + +Similar to the `raw` one, this format prints the same information except that it is displayed with a JSON. This format is useful if you need to diggest the logs with another log tool (such as an aggregator for example). + +![raw format](./images/json-format.png) + +### Hiding logs: the `none` format + +If you wish to completly mask logs, you can use the `none` format. + +## HTTP Request Logging + +Each request received by Foal is logged with the INFO level. + +With the configuration key `settings.loggerFormat` set to `"foal"`, the messages start with `HTTP request -` and end with the request method and URL. The log parameters include the response status code and content length as well as the response time and the request method and URL. + +> Note: the query parameters are not logged to avoid logging sensitive data (such as an API key). + +### Adding other parameters to the logs + +If the default logged HTTP parameters are not sufficient in your case, you can extend them with the option `getHttpLogParams` in `createApp`: + +```typescript +import { createApp, getHttpLogParamsDefault } from '@foal/core'; + +const app = await createApp({ + getHttpLogParams: (tokens, req, res) => ({ + ...getHttpLogParamsDefault(tokens, req, res), + myCustomHeader: req.get('my-custom-header'), + }) +}) ``` - - +### Formatting the log message (deprecated) + +If you wish to customize the HTTP log messages, you can set the value of the `loggerFormat.loggerFormat` configuration to a format supported by [morgan](https://www.npmjs.com/package/morgan). With this technique, no parameters will be logged though. ```json { "settings": { - "allErrors": false + "loggerFormat": "tiny" } } ``` - - +### Disabling HTTP Request Logging -```javascript -module.exports = { - settings: { - allErrors: false +In some scenarios and environments, you might want to disable HTTP request logging. You can achieve this by setting the `loggerFormat` configuration option to `none`. + +```json +{ + "settings": { + "loggerFormat": "none" } } ``` - - +## Error Logging -## Logging Hook +When an error is thrown (or rejected) in a hook, controller or service and is not caught, the error is logged using the `Logger.error` method. -FoalTS provides a convenient hook for logging debug messages: `Log(message: string, options: LogOptions = {})`. +### Disabling Error Logging -```typescript -interface LogOptions { - body?: boolean; - params?: boolean; - headers?: string[]|boolean; - query?: boolean; -} -``` +In some scenarios, you might want to disable error logging. You can achieve this by setting the `allErrors` configuration option to false. -*Example:* -```typescript -import { Get, HttpResponseOK, Log } from '@foal/core'; - -@Log('AppController', { - body: true, - headers: [ 'X-CSRF-Token' ], - params: true, - query: true -}) -export class AppController { - @Get() - index() { - return new HttpResponseOK(); +```json +{ + "settings": { + "allErrors": false } } ``` -## Advanced Logging +## Log correlation (by HTTP request, user ID, etc) -If you need advanced logging, you might be interested in using [winston](https://www.npmjs.com/package/winston), a popular logger in the Node.js ecosystem. +When logs are generated in large quantities, we often like to aggregate them by request or user. This can be done using Foal's log context. -Here's an example on how to use it with Foal: +When receiving a request, Foal adds the request ID to the logger context. On each subsequent call to the logger, it will behave as if the request ID had been passed as a parameter. -*LoggerService* +*Example* ```typescript -import * as winston from 'winston'; - -export class LoggerService { - private logger: any; - - constructor() { - this.logger = winston.createLogger({ - transports: [ - new winston.transports.Console(), - new winston.transports.File({ - filename: 'logs.txt' - }) - ] - }); - } +export class AppController { + @dependency + logger: Logger; - info(msg: string) { - this.logger.info(msg); - } + @Get('/foo') + getFoo(ctx: Context) { + this.logger.info('Hello world'); + // equivalent to this.logger.info('Hello world', { requestId: ctx.request.id }); - warn(msg: string) { - this.logger.warn(msg); + setTimeout(() => { + this.logger.info('Hello world'); + // equivalent to this.logger.info('Hello world', { requestId: ctx.request.id }); + }, 1000) + return new HttpResponseOK(); } +} +``` - error(msg: string) { - this.logger.error(msg); - } +In the same way, the authentification hooks `@JWTRequired`, `@JWTOptional` and `@UseSessions` will add the `userId` (if any) to the logger context. -} +This mecanism helps filter logs of a specific request or specific user in a logging tool. +If needed, you call also add manually custom parameters to the logger context with this fonction: +```typescript +logger.addLogContext('myKey', 'myValue'); ``` -*LogUserId hook* -```typescript -import { Hook } from '@foal/core'; -// LoggerService import. +## Transports -export function LogUserId() { - return Hook((ctx, services) => { - const logger = services.get(LoggerService); - logger.info(`UserId is: ${ctx.user.id}`); - }); -} +All logs are printed using the `console.log` function. -``` +If you also wish to consume the logs in another way (for example, to send them to a third-party error-tracking or logging tool), you can add one or more transports to the logger: -*ProductController* ```typescript -import { Get } from '@foal/core'; -// LogUserId import. +logger.addTransport((level: 'debug'|'warn'|'info'|'error', log: string) => { + // Do something +}) +``` -export class ProductController { +## Logging Hook (deprecated) - @Get('/') - @LogUserId() - readProducts() { - ... - } +> This hook is deprecated and will be removed in a next release. Use the `Logger` service in a custom hook instead. -} +FoalTS provides a convenient hook for logging debug messages: `Log(message: string, options: LogOptions = {})`. +```typescript +interface LogOptions { + body?: boolean; + params?: boolean; + headers?: string[]|boolean; + query?: boolean; +} ``` -*AuthController* +*Example:* ```typescript -import { dependency, Post } from '@foal/core'; -// LoggerService import. - -export class AuthController { - @dependency - logger: LoggerService; +import { Get, HttpResponseOK, Log } from '@foal/core'; - @Post('/signup') - signup() { - ... - this.logger.info('Someone signed up!'); +@Log('AppController', { + body: true, + headers: [ 'X-CSRF-Token' ], + params: true, + query: true +}) +export class AppController { + @Get() + index() { + return new HttpResponseOK(); } - } - -``` \ No newline at end of file +``` diff --git a/docs/i18n/fr/docusaurus-plugin-content-docs/current/architecture/controllers.md b/docs/i18n/fr/docusaurus-plugin-content-docs/current/architecture/controllers.md index 5030eb6d06..e5e8dfa971 100644 --- a/docs/i18n/fr/docusaurus-plugin-content-docs/current/architecture/controllers.md +++ b/docs/i18n/fr/docusaurus-plugin-content-docs/current/architecture/controllers.md @@ -250,6 +250,11 @@ class AppController { > In order to use signed cookies, you must provide a secret with the configuration key `settings.cookieParser.secret`. +#### Read the request ID + +On each request, a request ID is generated randomly. It can be read through `ctx.request.id`. + +If the `X-Request-ID` header exists, then the header value is used as the request identifier. #### The Controller Method Arguments From 40ab005fce318fa7f494d747fbe043d30aaae843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Poullain?= Date: Tue, 24 Oct 2023 00:02:54 +0200 Subject: [PATCH 37/37] [Blog] Add v4.1 release notes --- .../assets/version-4.1-is-here/banner.png | Bin 0 -> 17182 bytes docs/blog/version-4.1-release-notes.md | 49 ++++++++++++++++++ .../version-4.1-release-notes.png | Bin 0 -> 32560 bytes 3 files changed, 49 insertions(+) create mode 100644 docs/blog/assets/version-4.1-is-here/banner.png create mode 100644 docs/blog/version-4.1-release-notes.md create mode 100644 docs/static/blog/twitter-banners/version-4.1-release-notes.png diff --git a/docs/blog/assets/version-4.1-is-here/banner.png b/docs/blog/assets/version-4.1-is-here/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..555415de3252ae766bcec05194550a206fa9f949 GIT binary patch literal 17182 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 J*FLcc|6j^8Um*Yh literal 0 HcmV?d00001 diff --git a/docs/blog/version-4.1-release-notes.md b/docs/blog/version-4.1-release-notes.md new file mode 100644 index 0000000000..9749d5e6ba --- /dev/null +++ b/docs/blog/version-4.1-release-notes.md @@ -0,0 +1,49 @@ +--- +title: Version 4.1 release notes +author: Loïc Poullain +author_title: Creator of FoalTS. Software engineer. +author_url: https://loicpoullain.com +author_image_url: https://avatars1.githubusercontent.com/u/13604533?v=4 +image: blog/twitter-banners/version-4.1-release-notes.png +tags: [release] +--- + +![Banner](./assets/version-4.1-is-here/banner.png) + +Version 4.1 of [Foal](https://foalts.org/) is out! + + + +## Better logging + +Foal now features a true logging system. Full documentation can be found [here](../docs/common/logging). + +### New recommended configuration + +It is recommended to switch to this configuration to take full advantage of the new logging system. + +*config/default.json* +```json +{ + "settings": { + "loggerFormat": "foal" + } +} +``` + +*config/development.json* +```json +{ + "settings": { + "logger": { + "format": "dev" + } + } +} +``` + +## Request IDs + +On each request, a request ID is now generated randomly. It can be read through `ctx.request.id`. + +If the `X-Request-ID` header exists in the request, then the header value is used as the request identifier. diff --git a/docs/static/blog/twitter-banners/version-4.1-release-notes.png b/docs/static/blog/twitter-banners/version-4.1-release-notes.png new file mode 100644 index 0000000000000000000000000000000000000000..c742d8947b8c497bf841e0122577d0655c66f3b7 GIT binary patch literal 32560 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