diff --git a/src/constants/redact-strings-paths.constant.ts b/src/constants/redact-strings-paths.constant.ts index 1e2fc3bb..648fd4e5 100644 --- a/src/constants/redact-strings-paths.constant.ts +++ b/src/constants/redact-strings-paths.constant.ts @@ -7,4 +7,6 @@ export const REDACT_STRING_PATHS = [ 'stack', 'originalError.message', 'originalError.stack', + 'err.message', + 'err.stack', ]; diff --git a/src/helpers/redact-strings-in-log-args.helper.test.ts b/src/helpers/redact-strings-in-log-args.helper.test.ts new file mode 100644 index 00000000..7e8386e9 --- /dev/null +++ b/src/helpers/redact-strings-in-log-args.helper.test.ts @@ -0,0 +1,96 @@ +import { REDACT_STRING_PATHS } from '@ukef/constants'; +import { RandomValueGenerator } from '@ukef-test/support/generator/random-value-generator'; + +import { redactStringsInLogArgs } from './redact-strings-in-log-args.helper'; + +describe('Redact errors helper', () => { + const valueGenerator = new RandomValueGenerator(); + + describe('redactStringsInLogArgs', () => { + const domain = valueGenerator.httpsUrl(); + const otherSensitivefield = valueGenerator.word(); + const message = `ConnectionError: Failed to connect to ${domain}, ${otherSensitivefield}`; + const redactedMessage = `ConnectionError: Failed to connect to [RedactedDomain], [Redacted]`; + const redactStrings = [ + { searchValue: domain, replaceValue: '[RedactedDomain]' }, + { searchValue: otherSensitivefield, replaceValue: '[Redacted]' }, + ]; + const args = [ + { + message: message, + stack: message, + originalError: { + message: message, + stack: message, + safe: 'Nothing sensitive', + }, + driverError: { + message: message, + stack: message, + originalError: { + message: message, + stack: message, + safe: 'Nothing sensitive', + }, + }, + }, + ]; + const expectedResult = [ + { + message: redactedMessage, + stack: redactedMessage, + originalError: { + message: redactedMessage, + stack: redactedMessage, + safe: 'Nothing sensitive', + }, + driverError: { + message: redactedMessage, + stack: redactedMessage, + originalError: { + message: redactedMessage, + stack: redactedMessage, + safe: 'Nothing sensitive', + }, + }, + }, + ]; + + it('replaces sensitive data in input object', () => { + const redacted = redactStringsInLogArgs(true, REDACT_STRING_PATHS, redactStrings, args); + + expect(redacted).toStrictEqual(expectedResult); + }); + + it('returns original input if redactLogs is set to false', () => { + const redacted = redactStringsInLogArgs(false, REDACT_STRING_PATHS, redactStrings, args); + + expect(redacted).toStrictEqual(args); + }); + + it('replaces sensitive data in different input object', () => { + const args = [ + { + field1: message, + field2: { + field3: message, + safe: 'Nothing sensitive', + }, + }, + ]; + const expectedResult = [ + { + field1: redactedMessage, + field2: { + field3: redactedMessage, + safe: 'Nothing sensitive', + }, + }, + ]; + const redactPaths = ['field1', 'field2.field3']; + const redacted = redactStringsInLogArgs(true, redactPaths, redactStrings, args); + + expect(redacted).toStrictEqual(expectedResult); + }); + }); +}); diff --git a/src/helpers/redact-strings-in-log-args.helper.ts b/src/helpers/redact-strings-in-log-args.helper.ts new file mode 100644 index 00000000..345a08c2 --- /dev/null +++ b/src/helpers/redact-strings-in-log-args.helper.ts @@ -0,0 +1,31 @@ +import { get, set } from 'lodash'; + +// This helper function is used to redact sensitive data in Error object strings. +export const redactStringsInLogArgs = ( + redactLogs: boolean, + redactPaths: string[], + redactStrings: { searchValue: string | RegExp; replaceValue: string }[], + args: any, +): any => { + if (!redactLogs) { + return args; + } + args.forEach((arg, index) => { + redactPaths.forEach((path) => { + const value: string = get(arg, path); + if (value) { + const safeValue = redactString(redactStrings, value); + set(args[index], path, safeValue); + } + }); + }); + return args; +}; + +const redactString = (redactStrings: { searchValue: string | RegExp; replaceValue: string }[], string: string): string => { + let safeString: string = string; + redactStrings.forEach((redact) => { + safeString = safeString.replaceAll(redact.searchValue, redact.replaceValue); + }); + return safeString; +}; diff --git a/src/logging/console-logger-with-redact.ts b/src/logging/console-logger-with-redact.ts index 5523d9d7..b76a9973 100644 --- a/src/logging/console-logger-with-redact.ts +++ b/src/logging/console-logger-with-redact.ts @@ -9,12 +9,18 @@ export class ConsoleLoggerWithRedact extends ConsoleLogger { } error(message: any, stack?: string, context?: string) { + let cleanMessage = message; let cleanStack = stack; + if (typeof message == 'string') { + this.stringPatternsToRedact.forEach((redact) => { + cleanMessage = cleanMessage.replace(redact.searchValue, redact.replaceValue); + }); + } if (typeof stack == 'string') { this.stringPatternsToRedact.forEach((redact) => { - cleanStack = stack.replace(redact.searchValue, redact.replaceValue); + cleanStack = cleanStack.replace(redact.searchValue, redact.replaceValue); }); } - super.error(message, cleanStack, context); + super.error(cleanMessage, cleanStack, context); } } diff --git a/src/main.module.ts b/src/main.module.ts index b0583583..a86aae59 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -33,7 +33,7 @@ import { LoggingInterceptor } from './logging/logging-interceptor.helper'; }, }, hooks: { - logMethod(inputArgs, method) { + logMethod(inputArgs: any, method) { return method.apply(this, redactStringsInLogArgs(config.get('app.redactLogs'), REDACT_STRING_PATHS, REDACT_STRINGS, inputArgs)); }, }, diff --git a/src/main.ts b/src/main.ts index 851011bc..e835eed2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,8 +6,10 @@ import { REDACT_STRINGS } from './constants'; import { ConsoleLoggerWithRedact } from './logging/console-logger-with-redact'; const main = async () => { + // ConsoleLoggerWithRedact is used just if NestFactory.create is fails completely. + // Buffered logs are passed to PinoLogger if NestFactory.create is successfull, ConsoleLoggerWithRedact is not used. const logger = new ConsoleLoggerWithRedact(REDACT_STRINGS); - const nestApp: NestApplication = await NestFactory.create(MainModule, { logger, bufferLogs: false }); + const nestApp: NestApplication = await NestFactory.create(MainModule, { logger, bufferLogs: true }); const app = new App(nestApp); logger.log(`==========================================================`); diff --git a/src/modules/markets/markets.service.ts b/src/modules/markets/markets.service.ts index c7520f6a..68fb6406 100644 --- a/src/modules/markets/markets.service.ts +++ b/src/modules/markets/markets.service.ts @@ -44,7 +44,7 @@ export class MarketsService { })); return mappedResults; - } catch (err: any) { + } catch (err) { this.logger.error(err); throw new InternalServerErrorException(); }