diff --git a/src/evaluator/combiners/and.ts b/src/evaluator/combiners/and.ts index b2981f29..7f0dbc95 100644 --- a/src/evaluator/combiners/and.ts +++ b/src/evaluator/combiners/and.ts @@ -3,7 +3,7 @@ import { ILogger } from '../../logger/types'; import thenable from '../../utils/promise/thenable'; import { MaybeThenable } from '../../dtos/types'; import { IMatcher } from '../types'; -import { DEBUG_0 } from '../../logger/constants'; +import { DEBUG_ENGINE_COMBINER_AND } from '../../logger/constants'; export default function andCombinerContext(log: ILogger, matchers: IMatcher[]) { @@ -11,7 +11,7 @@ export default function andCombinerContext(log: ILogger, matchers: IMatcher[]) { // Array.prototype.every is supported by target environments const hasMatchedAll = results.every(value => value); - log.debug(DEBUG_0, [hasMatchedAll]); + log.debug(DEBUG_ENGINE_COMBINER_AND, [hasMatchedAll]); return hasMatchedAll; } diff --git a/src/evaluator/matchers/__tests__/ew.spec.ts b/src/evaluator/matchers/__tests__/ew.spec.ts index aeb9eb0a..c5980d0b 100644 --- a/src/evaluator/matchers/__tests__/ew.spec.ts +++ b/src/evaluator/matchers/__tests__/ew.spec.ts @@ -11,11 +11,11 @@ test('MATCHER ENDS_WITH / should return true ONLY when the value ends with ["a", value: ['a', 'b', 'c'] } as IMatcherDto) as IMatcher; - expect(matcher('america')).toBe(true); // america end with ["a", "b", "c"] - expect(matcher('blob')).toBe(true); // blob end with ["a", "b", "c"] - expect(matcher('zodiac')).toBe(true); // zodiac end with ["a", "b", "c"] - expect(matcher('violin')).toBe(false); // t end with ["a", "b", "c"] - expect(matcher('manager')).toBe(false); // t end with ["a", "b", "c"] + expect(matcher('america')).toBe(true); // america ends with ["a", "b", "c"] + expect(matcher('blob')).toBe(true); // blob ends with ["a", "b", "c"] + expect(matcher('zodiac')).toBe(true); // zodiac ends with ["a", "b", "c"] + expect(matcher('violin')).toBe(false); // violin doesn't end with ["a", "b", "c"] + expect(matcher('manager')).toBe(false); // manager doesn't end with ["a", "b", "c"] }); test('MATCHER ENDS_WITH / should return true ONLY when the value ends with ["demo.test.org"]', function () { diff --git a/src/evaluator/matchers/__tests__/sw.spec.ts b/src/evaluator/matchers/__tests__/sw.spec.ts index 312ae8b1..ec8cdb5b 100644 --- a/src/evaluator/matchers/__tests__/sw.spec.ts +++ b/src/evaluator/matchers/__tests__/sw.spec.ts @@ -11,9 +11,9 @@ test('MATCHER STARTS_WITH / should return true ONLY when the value starts with [ value: ['a', 'b', 'c'] } as IMatcherDto) as IMatcher; - expect(matcher('awesome')).toBe(true); // awesome start with ["a", "b", "c"] - expect(matcher('black')).toBe(true); // black start with ["a", "b", "c"] - expect(matcher('chello')).toBe(true); // chello start with ["a", "b", "c"] - expect(matcher('violin')).toBe(false); // t start with ["a", "b", "c"] - expect(matcher('manager')).toBe(false); // t start with ["a", "b", "c"] + expect(matcher('awesome')).toBe(true); // awesome starts with ["a", "b", "c"] + expect(matcher('black')).toBe(true); // black starts with ["a", "b", "c"] + expect(matcher('chello')).toBe(true); // chello starts with ["a", "b", "c"] + expect(matcher('violin')).toBe(false); // violin doesn't start with ["a", "b", "c"] + expect(matcher('manager')).toBe(false); // manager doesn't start with ["a", "b", "c"] }); diff --git a/src/evaluator/value/index.ts b/src/evaluator/value/index.ts index a7245066..47a928bc 100644 --- a/src/evaluator/value/index.ts +++ b/src/evaluator/value/index.ts @@ -2,7 +2,7 @@ import { SplitIO } from '../../types'; import { IMatcherDto } from '../types'; import { ILogger } from '../../logger/types'; import sanitizeValue from './sanitize'; -import { DEBUG_24, WARN_1, WARN_0 } from '../../logger/constants'; +import { DEBUG_24, WARN_ENGINE_NO_ATTRIBUTES, WARN_ENGINE_INVALID_VALUE } from '../../logger/constants'; function parseValue(log: ILogger, key: string, attributeName: string | null, attributes: SplitIO.Attributes) { let value = undefined; @@ -11,7 +11,7 @@ function parseValue(log: ILogger, key: string, attributeName: string | null, att value = attributes[attributeName]; log.debug(DEBUG_24, [attributeName, value]); } else { - log.warn(WARN_1, [attributeName]); + log.warn(WARN_ENGINE_NO_ATTRIBUTES, [attributeName]); } } else { value = key; @@ -31,7 +31,7 @@ export default function value(log: ILogger, key: string, matcherDto: IMatcherDto if (sanitizedValue !== undefined) { return sanitizedValue; } else { - log.warn(WARN_0, [valueToMatch, attributeName ? ' for attribute ' + attributeName : '']); + log.warn(WARN_ENGINE_INVALID_VALUE, [valueToMatch + (attributeName ? ' for attribute ' + attributeName : '')]); return; } } diff --git a/src/integrations/ga/GaToSplit.ts b/src/integrations/ga/GaToSplit.ts index 10bfd135..befa31f1 100644 --- a/src/integrations/ga/GaToSplit.ts +++ b/src/integrations/ga/GaToSplit.ts @@ -12,9 +12,9 @@ import { SplitIO } from '../../types'; import { Identity, GoogleAnalyticsToSplitOptions } from './types'; import { ILogger } from '../../logger/types'; import { IIntegrationFactoryParams } from '../types'; -// import { logFactory } from '../../logger/sdkLogger'; -// const log = logFactory('splitio-ga-to-split'); -const logNameMapper = 'splitio-ga-to-split:mapper'; + +const logPrefix = 'ga-to-split: '; +const logNameMapper = 'ga-to-split:mapper'; /** * Provides a plugin to use with analytics.js, accounting for the possibility @@ -169,7 +169,7 @@ export function fixEventTypeId(log: ILogger, eventTypeId: any) { .replace(INVALID_PREFIX_REGEX, '') .replace(INVALID_SUBSTRING_REGEX, '_'); const truncated = fixed.slice(0, 80); - if (truncated.length < fixed.length) log.warn('EventTypeId was truncated because it cannot be more than 80 characters long.'); + if (truncated.length < fixed.length) log.warn(logPrefix + 'EventTypeId was truncated because it cannot be more than 80 characters long.'); return truncated; } @@ -210,19 +210,19 @@ export default function GaToSplit(sdkOptions: GoogleAnalyticsToSplitOptions, par const validIdentities = validateIdentities(opts.identities); if (validIdentities.length === 0) { - log.warn('No valid identities were provided. Please check that you are passing a valid list of identities or providing a traffic type at the SDK configuration.'); + log.warn(logPrefix + 'No valid identities were provided. Please check that you are passing a valid list of identities or providing a traffic type at the SDK configuration.'); return; } const invalids = validIdentities.length - opts.identities.length; if (invalids) { - log.warn(`${invalids} identities were discarded because they are invalid or duplicated. Identities must be an array of objects with key and trafficType.`); + log.warn(logPrefix + `${invalids} identities were discarded because they are invalid or duplicated. Identities must be an array of objects with key and trafficType.`); } opts.identities = validIdentities; // Validate prefix if (!isString(opts.prefix)) { - log.warn('The provided `prefix` was ignored since it is invalid. Please check that you are passing a string object as `prefix`.'); + log.warn(logPrefix + 'The provided `prefix` was ignored since it is invalid. Please check that you are passing a string object as `prefix`.'); opts.prefix = undefined; } @@ -239,7 +239,7 @@ export default function GaToSplit(sdkOptions: GoogleAnalyticsToSplitOptions, par try { if (opts.filter && !opts.filter(model)) return; } catch (err) { - log.warn(`GaToSplit custom filter threw: ${err}`); + log.warn(logPrefix + `custom filter threw: ${err}`); return; } @@ -249,7 +249,7 @@ export default function GaToSplit(sdkOptions: GoogleAnalyticsToSplitOptions, par try { eventData = opts.mapper(model, eventData as SplitIO.EventData); } catch (err) { - log.warn(`GaToSplit custom mapper threw: ${err}`); + log.warn(logPrefix + `custom mapper threw: ${err}`); return; } if (!eventData) @@ -278,7 +278,7 @@ export default function GaToSplit(sdkOptions: GoogleAnalyticsToSplitOptions, par } }); - log.info('Started GA-to-Split integration'); + log.info(logPrefix + 'integration started'); } } diff --git a/src/integrations/ga/SplitToGa.ts b/src/integrations/ga/SplitToGa.ts index 0ac29ceb..df933c21 100644 --- a/src/integrations/ga/SplitToGa.ts +++ b/src/integrations/ga/SplitToGa.ts @@ -5,9 +5,8 @@ import { SplitIO } from '../../types'; import { IIntegration } from '../types'; import { SplitToGoogleAnalyticsOptions } from './types'; import { ILogger } from '../../logger/types'; -// import { logFactory } from '../../logger/sdkLogger'; -// const log = logFactory('splitio-split-to-ga'); +const logPrefix = 'split-to-ga: '; const noGaWarning = '`ga` command queue not found.'; const noHit = 'No hit was sent.'; @@ -64,7 +63,7 @@ export default class SplitToGa implements IIntegration { static validateFieldsObject(log: ILogger, fieldsObject: any): fieldsObject is UniversalAnalytics.FieldsObject { if (fieldsObject && fieldsObject.hitType) return true; - log.warn('your custom mapper returned an invalid FieldsObject instance. It must be an object with at least a `hitType` field.'); + log.warn(logPrefix + 'your custom mapper returned an invalid FieldsObject instance. It must be an object with at least a `hitType` field.'); return false; } @@ -90,8 +89,8 @@ export default class SplitToGa implements IIntegration { this.events = options.events; } - log.info('Started Split-to-GA integration'); - if (typeof SplitToGa.getGa() !== 'function') log.warn(`${noGaWarning} No hits will be sent until it is available.`); + log.info(logPrefix + 'integration started'); + if (typeof SplitToGa.getGa() !== 'function') log.warn(logPrefix + `${noGaWarning} No hits will be sent until it is available.`); } queue(data: SplitIO.IntegrationData) { @@ -116,7 +115,7 @@ export default class SplitToGa implements IIntegration { if (!fieldsObject || !SplitToGa.validateFieldsObject(this.log, fieldsObject)) return; } } catch (err) { - this.log.warn(`SplitToGa queue method threw: ${err}. ${noHit}`); + this.log.warn(logPrefix + `queue method threw: ${err}. ${noHit}`); return; } @@ -129,7 +128,7 @@ export default class SplitToGa implements IIntegration { ga(sendCommand, fieldsObject); }); } else { - this.log.warn(`${noGaWarning} ${noHit}`); + this.log.warn(logPrefix + `${noGaWarning} ${noHit}`); } } diff --git a/src/integrations/ga/__tests__/SplitToGa.spec.ts b/src/integrations/ga/__tests__/SplitToGa.spec.ts index 04b0ccdf..33581122 100644 --- a/src/integrations/ga/__tests__/SplitToGa.spec.ts +++ b/src/integrations/ga/__tests__/SplitToGa.spec.ts @@ -96,8 +96,8 @@ describe('SplitToGa', () => { // @ts-expect-error integration.queue('fake-data'); expect(loggerMock.warn.mock.calls).toEqual([ // Warn when creating and queueing while ga command queue does not exist - ['`ga` command queue not found. No hits will be sent until it is available.'], - ['`ga` command queue not found. No hit was sent.'] + ['split-to-ga: `ga` command queue not found. No hits will be sent until it is available.'], + ['split-to-ga: `ga` command queue not found. No hit was sent.'] ]); }); @@ -138,7 +138,7 @@ describe('SplitToGa', () => { }) as SplitToGa; ga.mockClear(); instance2.queue(fakeImpression); - expect(ga.mock.calls.length === 0).toBe(true); // t queue `ga send` if a Split data (impression or event) is filtered + expect(ga).not.toBeCalled(); // shouldn't queue `ga send` if a Split data (impression or event) is filtered instance2.queue(fakeEvent); expect(ga.mock.calls).toEqual([ @@ -171,7 +171,7 @@ describe('SplitToGa', () => { }) as SplitToGa; ga.mockClear(); instance4.queue(fakeImpression); - expect(ga.mock.calls.length === 0).toBe(true); // t queue `ga send` if a custom mapper throw an exception + expect(ga).not.toBeCalled(); // shouldn't queue `ga send` if a custom mapper throw an exception // `impressions` flags const instance5 = new SplitToGa(loggerMock, { @@ -179,7 +179,7 @@ describe('SplitToGa', () => { }) as SplitToGa; ga.mockClear(); instance5.queue(fakeImpression); - expect(ga.mock.calls.length === 0).toBe(true); // t queue `ga send` for an impression if `impressions` flag is false + expect(ga).not.toBeCalled(); // shouldn't queue `ga send` for an impression if `impressions` flag is false // `impressions` flags const instance6 = new SplitToGa(loggerMock, { @@ -187,7 +187,7 @@ describe('SplitToGa', () => { }) as SplitToGa; ga.mockClear(); instance6.queue(fakeEvent); - expect(ga.mock.calls.length === 0).toBe(true); // t queue `ga send` for a event if `events` flag is false + expect(ga).not.toBeCalled(); // shouldn't queue `ga send` for a event if `events` flag is false // test teardown gaRemove(); diff --git a/src/listeners/__tests__/browser.spec.ts b/src/listeners/__tests__/browser.spec.ts index cc428917..03a98c79 100644 --- a/src/listeners/__tests__/browser.spec.ts +++ b/src/listeners/__tests__/browser.spec.ts @@ -157,7 +157,7 @@ test('Browser JS listener / Impressions debug mode', (done) => { expect((fakeSplitApi.postTestImpressionsCount as jest.Mock).mock.calls.length).toBe(0); // pre-check and call stop - expect((global.window.removeEventListener as jest.Mock).mock.calls.length === 0).toBe(true); + expect(global.window.removeEventListener).not.toBeCalled(); listener.stop(); // removed correct listener from correct signal on stop. @@ -191,7 +191,7 @@ test('Browser JS listener / Impressions debug mode without sendBeacon API', (don expect((fakeSplitApi.postTestImpressionsCount as jest.Mock).mock.calls.length).toBe(0); // pre-check and call stop - expect((global.window.removeEventListener as jest.Mock).mock.calls.length === 0).toBe(true); + expect(global.window.removeEventListener).not.toBeCalled(); listener.stop(); // removed correct listener from correct signal on stop. diff --git a/src/listeners/__tests__/node.spec.ts b/src/listeners/__tests__/node.spec.ts index cc16aff4..94f6e14c 100644 --- a/src/listeners/__tests__/node.spec.ts +++ b/src/listeners/__tests__/node.spec.ts @@ -17,7 +17,7 @@ test('Node JS listener / Signal Listener class methods and start/stop functional expect(processOnSpy.mock.calls).toEqual([['SIGTERM', listener._sigtermHandler]]); // pre-check and call stop - expect(processRemoveListenerSpy.mock.calls.length === 0).toBe(true); + expect(processRemoveListenerSpy).not.toBeCalled(); listener.stop(); // removed correct listener from correct signal on stop. diff --git a/src/listeners/browser.ts b/src/listeners/browser.ts index 32f36130..a5e14508 100644 --- a/src/listeners/browser.ts +++ b/src/listeners/browser.ts @@ -14,6 +14,7 @@ import { DEBUG_26, DEBUG_27 } from '../logger/constants'; // 'unload' event is used instead of 'beforeunload', since 'unload' is not a cancelable event, so no other listeners can stop the event from occurring. const UNLOAD_DOM_EVENT = 'unload'; +const EVENT_NAME = 'for unload page event.'; /** * We'll listen for 'unload' event over the window object, since it's the standard way to listen page reload and close. @@ -39,7 +40,7 @@ export default class BrowserSignalListener implements ISignalListener { */ start() { if (typeof window !== 'undefined' && window.addEventListener) { - this.settings.log.debug(DEBUG_26); + this.settings.log.debug(DEBUG_26, [EVENT_NAME]); window.addEventListener(UNLOAD_DOM_EVENT, this.flushData); } } @@ -51,7 +52,7 @@ export default class BrowserSignalListener implements ISignalListener { */ stop() { if (typeof window !== 'undefined' && window.removeEventListener) { - this.settings.log.debug(DEBUG_27); + this.settings.log.debug(DEBUG_27, [EVENT_NAME]); window.removeEventListener(UNLOAD_DOM_EVENT, this.flushData); } } diff --git a/src/listeners/node.ts b/src/listeners/node.ts index 35d8f0e9..3d799c92 100644 --- a/src/listeners/node.ts +++ b/src/listeners/node.ts @@ -3,7 +3,10 @@ import { ISignalListener } from './types'; import thenable from '../utils/promise/thenable'; import { MaybeThenable } from '../dtos/types'; import { ISettings } from '../types'; -import { DEBUG_28, DEBUG_29, DEBUG_30, ERROR_1 } from '../logger/constants'; +import { CLEANUP_LB, DEBUG_26, DEBUG_27 } from '../logger/constants'; + +const SIGTERM = 'SIGTERM'; +const EVENT_NAME = 'for SIGTERM signal.'; /** * We'll listen for SIGTERM since it's the standard signal for server shutdown. @@ -22,15 +25,15 @@ export default class NodeSignalListener implements ISignalListener { } start() { - this.settings.log.debug(DEBUG_28); + this.settings.log.debug(DEBUG_26, [EVENT_NAME]); // eslint-disable-next-line no-undef - process.on('SIGTERM', this._sigtermHandler); + process.on(SIGTERM, this._sigtermHandler); } stop() { - this.settings.log.debug(DEBUG_29); + this.settings.log.debug(DEBUG_27, [EVENT_NAME]); // eslint-disable-next-line no-undef - process.removeListener('SIGTERM', this._sigtermHandler); + process.removeListener(SIGTERM, this._sigtermHandler); } /** @@ -43,17 +46,17 @@ export default class NodeSignalListener implements ISignalListener { // This handler prevented the default behaviour, start again. // eslint-disable-next-line no-undef - process.kill(process.pid, 'SIGTERM'); + process.kill(process.pid, SIGTERM); }; - this.settings.log.debug(DEBUG_30); + this.settings.log.debug(CLEANUP_LB + 'Split SDK graceful shutdown after SIGTERM.'); let handlerResult = null; try { handlerResult = this.handler(); } catch (err) { - this.settings.log.error(ERROR_1, [err]); + this.settings.log.error(CLEANUP_LB + `Error with Split SDK graceful shutdown: ${err}`); } if (thenable(handlerResult)) { diff --git a/src/logger/browser/debugLogger.ts b/src/logger/browser/debugLogger.ts new file mode 100644 index 00000000..119b2f05 --- /dev/null +++ b/src/logger/browser/debugLogger.ts @@ -0,0 +1,11 @@ +import { Logger } from '../index'; +import { codesError } from '../messages/error'; +import { codesWarn } from '../messages/warn'; +import { codesInfo } from '../messages/info'; +import { codesDebug } from '../messages/debug'; +import { _Map } from '../../utils/lang/maps'; + +export const debugLogger = new Logger( + 'splitio', { logLevel: 'DEBUG' }, + new _Map(codesError.concat(codesWarn, codesInfo, codesDebug)) +); diff --git a/src/logger/browser/errorLogger.ts b/src/logger/browser/errorLogger.ts new file mode 100644 index 00000000..7423d7fc --- /dev/null +++ b/src/logger/browser/errorLogger.ts @@ -0,0 +1,8 @@ +import { Logger } from '../index'; +import { codesError } from '../messages/error'; +import { _Map } from '../../utils/lang/maps'; + +export const errorLogger = new Logger( + 'splitio', { logLevel: 'ERROR' }, + new _Map(codesError) +); diff --git a/src/logger/browser/infoLogger.ts b/src/logger/browser/infoLogger.ts new file mode 100644 index 00000000..c88c7c8d --- /dev/null +++ b/src/logger/browser/infoLogger.ts @@ -0,0 +1,10 @@ +import { Logger } from '../index'; +import { codesError } from '../messages/error'; +import { codesWarn } from '../messages/warn'; +import { codesInfo } from '../messages/info'; +import { _Map } from '../../utils/lang/maps'; + +export const infoLogger = new Logger( + 'splitio', { logLevel: 'INFO' }, + new _Map(codesError.concat(codesWarn, codesInfo)) +); diff --git a/src/logger/browser/warnLogger.ts b/src/logger/browser/warnLogger.ts new file mode 100644 index 00000000..348d4284 --- /dev/null +++ b/src/logger/browser/warnLogger.ts @@ -0,0 +1,9 @@ +import { Logger } from '../index'; +import { codesError } from '../messages/error'; +import { codesWarn } from '../messages/warn'; +import { _Map } from '../../utils/lang/maps'; + +export const warnLogger = new Logger( + 'splitio', { logLevel: 'WARN' }, + new _Map(codesError.concat(codesWarn)) +); diff --git a/src/logger/constants.ts b/src/logger/constants.ts index eecf9f1c..775577be 100644 --- a/src/logger/constants.ts +++ b/src/logger/constants.ts @@ -1,151 +1,141 @@ -// commons -export const DEBUG_0 = 'splitio-engine:combiner => [andCombiner] evaluates to %s'; -export const DEBUG_1 = 'splitio-engine:combiner => Treatment found: %s'; -export const DEBUG_2 = 'splitio-engine:combiner => All predicates evaluated, no treatment found.'; -export const DEBUG_3 = 'splitio-engine => [engine] using algo \'murmur\' bucket %s for key %s using seed %s - treatment %s'; -export const DEBUG_4 = 'splitio-engine:matcher => [allMatcher] is always true'; -export const DEBUG_5 = 'splitio-engine:matcher => [betweenMatcher] is %s between %s and %s? %s'; -export const DEBUG_6 = 'splitio-engine:matcher => [booleanMatcher] %s === %s'; -export const DEBUG_7 = 'splitio-engine:matcher => [containsAllMatcher] %s contains all elements of %s? %s'; -export const DEBUG_8 = 'splitio-engine:matcher => [containsAnyMatcher] %s contains at least an element of %s? %s'; -export const DEBUG_9 = 'splitio-engine:matcher => [containsStringMatcher] %s contains %s? %s'; -export const DEBUG_10 = 'splitio-engine:matcher => [dependencyMatcher] Parent split "%s" evaluated to "%s" with label "%s". %s evaluated treatment is part of [%s] ? %s.'; -export const DEBUG_11 = 'splitio-engine:matcher => [dependencyMatcher] will evaluate parent split: "%s" with key: %s %s'; -export const DEBUG_12 = 'splitio-engine:matcher => [equalToMatcher] is %s equal to %s? %s'; -export const DEBUG_13 = 'splitio-engine:matcher => [equalToSetMatcher] is %s equal to set %s? %s'; -export const DEBUG_14 = 'splitio-engine:matcher => [endsWithMatcher] %s ends with %s? %s'; -export const DEBUG_15 = 'splitio-engine:matcher => [greaterThanEqualMatcher] is %s greater than %s? %s'; -export const DEBUG_16 = 'splitio-engine:matcher => [lessThanEqualMatcher] is %s less than %s? %s'; -export const DEBUG_17 = 'splitio-engine:matcher => [partOfMatcher] %s is part of %s? %s'; -export const DEBUG_18 = 'splitio-engine:matcher => [asyncSegmentMatcher] evaluated %s / %s => %s'; -export const DEBUG_19 = 'splitio-engine:matcher => [segmentMatcher] evaluated %s / %s => %s'; -export const DEBUG_20 = 'splitio-engine:matcher => [stringMatcher] does %s matches with %s? %s'; -export const DEBUG_21 = 'splitio-engine:matcher => [stringMatcher] %s is an invalid regex'; -export const DEBUG_22 = 'splitio-engine:matcher => [startsWithMatcher] %s starts with %s? %s'; -export const DEBUG_23 = 'splitio-engine:matcher => [whitelistMatcher] evaluated %s in [%s] => %s'; -export const DEBUG_24 = 'splitio-engine:value => Extracted attribute [%s], [%s] will be used for matching.'; -export const DEBUG_25 = 'splitio-engine:sanitize => Attempted to sanitize [%s] which should be of type [%s]. \n Sanitized and processed value => [%s]'; -export const DEBUG_31 = 'splitio => Retrieving SDK client.'; -export const DEBUG_32 = 'splitio => Retrieving default SDK client.'; // @TODO remove and use 'splitio => Retrieving SDK client.' -export const DEBUG_33 = 'splitio => Retrieving existing SDK client.'; -export const DEBUG_36 = 'splitio-producer:offline => Splits data: '; -export const DEBUG_42 = 'splitio-sync:split-changes => Spin up split update using since = %s'; -export const DEBUG_43 = 'splitio-sync:split-changes => New splits %s'; -export const DEBUG_44 = 'splitio-sync:split-changes => Removed splits %s'; -export const DEBUG_45 = 'splitio-sync:split-changes => Segment names collected %s'; -export const DEBUG_46 = 'splitio-sync:sse-handler => New SSE message received, with data: %s.'; -export const DEBUG_47 = 'splitio-sync:task => Starting %s. Running each %s millis'; -export const DEBUG_48 = 'splitio-sync:task => Running %s'; -export const DEBUG_49 = 'splitio-sync:task => Stopping %s'; -export const DEBUG_50 = 'splitio-client:impressions-tracker => Successfully stored %s impression%s.'; -export const DEBUG_51 = 'Factory instantiation: splits filtering criteria is \'%s\'.'; +/** + * Message codes used to trim string log messages from commons and client-side API modules, + * in order to reduce the minimal SDK size for Browser and eventually other client-side environments. + * + * Modules related to the server-side API (e.g., segmentsSyncTask), platform-specific components (e.g., signal listeners) + * and pluggable components (e.g., pluggable integrations & storages) can use the logger with string literals. + */ +export const DEBUG_ENGINE_COMBINER_AND = 0; +export const DEBUG_1 = 1; +export const DEBUG_2 = 2; +export const DEBUG_3 = 3; +export const DEBUG_4 = 4; +export const DEBUG_5 = 5; +export const DEBUG_6 = 6; +export const DEBUG_7 = 7; +export const DEBUG_8 = 8; +export const DEBUG_9 = 9; +export const DEBUG_10 = 10; +export const DEBUG_11 = 11; +export const DEBUG_12 = 12; +export const DEBUG_13 = 13; +export const DEBUG_14 = 14; +export const DEBUG_15 = 15; +export const DEBUG_16 = 16; +export const DEBUG_17 = 17; +export const DEBUG_18 = 18; +export const DEBUG_19 = 19; +export const DEBUG_20 = 20; +export const DEBUG_21 = 21; +export const DEBUG_22 = 22; +export const DEBUG_23 = 23; +export const DEBUG_24 = 24; +export const DEBUG_25 = 25; +export const DEBUG_26 = 26; +export const DEBUG_27 = 27; +export const DEBUG_32 = 32; +export const DEBUG_33 = 33; +export const DEBUG_36 = 36; +export const DEBUG_42 = 42; +export const DEBUG_43 = 43; +export const DEBUG_44 = 44; +export const DEBUG_45 = 45; +export const DEBUG_46 = 46; +export const DEBUG_47 = 47; +export const DEBUG_48 = 48; +export const DEBUG_49 = 49; +export const DEBUG_50 = 50; +export const DEBUG_SPLITS_FILTER = 51; -// browser -export const DEBUG_26 = 'splitio-client:cleanup => Registering flush handler when unload page event is triggered.'; -export const DEBUG_27 = 'splitio-client:cleanup => Deregistering flush handler when unload page event is triggered.'; +export const ERROR_0 = 300; +export const ERROR_2 = 302; +export const ERROR_CLIENT_LISTENER = 303; +export const ERROR_4 = 304; +export const ERROR_5 = 305; +export const ERROR_7 = 307; +export const ERROR_9 = 309; +export const ERROR_10 = 310; +export const ERROR_11 = 311; +export const ERROR_12 = 312; +export const ERROR_EVENT_TYPE_FORMAT = 314; +export const ERROR_NOT_PLAIN_OBJECT = 318; +export const ERROR_SIZE_EXCEEDED = 319; +export const ERROR_NOT_FINITE = 320; +export const ERROR_CLIENT_DESTROYED = 321; +export const ERROR_NULL = 322; +export const ERROR_TOO_LONG = 323; +export const ERROR_INVALID_KEY_OBJECT = 326; +export const ERROR_INVALID = 332; +export const ERROR_EMPTY = 333; +export const ERROR_EMPTY_ARRAY = 334; +export const ERROR_INVALID_IMPRESSIONS_MODE = 338; +export const ERROR_39 = 339; -// node -export const DEBUG_28 = 'splitio-client:cleanup => Registering cleanup handlers.'; -export const DEBUG_29 = 'splitio-client:cleanup => Deregistering cleanup handlers.'; -export const DEBUG_30 = 'splitio-client:cleanup => Split SDK graceful shutdown after SIGTERM.'; -export const DEBUG_39 = 'splitio-sync:segment-changes => Processed %s with till = %s. Added: %s. Removed: %s'; -export const DEBUG_40 = 'splitio-sync:segment-changes => Processing segment %s'; -export const DEBUG_41 = 'splitio-sync:segment-changes => Started segments update'; -export const DEBUG_34 = 'splitio-offline:splits-fetcher => Ignoring empty line or comment at #%s'; -export const DEBUG_35 = 'splitio-offline:splits-fetcher => Ignoring line since it does not have exactly two columns #%s'; -export const DEBUG_37 = 'splitio-sync:polling-manager => Splits will be refreshed each %s millis'; // @TODO remove since we already log it in syncTask debug log? -export const DEBUG_38 = 'splitio-sync:polling-manager => Segments will be refreshed each %s millis'; // @TODO remove since we already log it in syncTask debug log? +export const INFO_CLIENT_READY_FROM_CACHE = 100; +export const INFO_CLIENT_READY = 101; +export const INFO_2 = 102; +export const INFO_3 = 103; +export const INFO_4 = 104; +export const INFO_5 = 105; +export const INFO_6 = 106; +export const INFO_7 = 107; +export const INFO_8 = 108; +export const INFO_9 = 109; +export const INFO_10 = 110; +export const INFO_11 = 111; +export const INFO_12 = 112; +export const INFO_13 = 113; +export const INFO_14 = 114; +export const INFO_15 = 115; +export const INFO_16 = 116; +export const INFO_17 = 117; +export const INFO_18 = 118; +export const INFO_19 = 119; +export const INFO_20 = 120; +export const INFO_21 = 121; -// commons -export const ERROR_0 = 'splitio-engine:combiner => Invalid Split provided, no valid conditions found'; -export const ERROR_2 = 'splitio-utils:logger => Invalid Log Level - No changes to the logs will be applied.'; -export const ERROR_3 = 'A listener was added for %s on the SDK, which has already fired and won\'t be emitted again. The callback won\'t be executed.'; -export const ERROR_4 = 'splitio => Manager instance is not available. Provide the manager module on settings.'; -export const ERROR_5 = 'splitio-services:service => %s The SDK will not get ready.'; -export const ERROR_7 = 'splitio-producer:offline => There was an issue loading the mock Splits data, no changes will be applied to the current cache. %s'; -export const ERROR_9 = 'splitio-sync:sse-handler => Fail to connect to streaming, with error message: %s'; -export const ERROR_10 = 'splitio-sync:push-manager => Failed to authenticate for streaming. Error: "%s".'; -export const ERROR_11 = 'splitio-client:impressions-tracker => Could not store impressions bulk with %s impression%s. Error: %s'; -export const ERROR_12 = 'splitio-client:impressions-tracker => Impression listener logImpression method threw: %s.'; -export const ERROR_13 = '%s: attributes must be a plain object.'; -export const ERROR_14 = '%s: you passed "%s", event_type must adhere to the regular expression /^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$/g. This means an event_type must be alphanumeric, cannot be more than 80 characters long, and can only include a dash, underscore, period, or colon as separators of alphanumeric characters.'; -export const ERROR_15 = '%s: you passed a null or undefined event_type, event_type must be a non-empty string.'; -export const ERROR_16 = '%s: you passed an invalid event_type, event_type must be a non-empty string.'; -export const ERROR_17 = '%s: you passed an empty event_type, event_type must be a non-empty string.'; -export const ERROR_18 = '%s: properties must be a plain object.'; -export const ERROR_19 = '%s: The maximum size allowed for the properties is 32768 bytes, which was exceeded. Event not queued.'; -export const ERROR_20 = '%s: value must be a finite number.'; -export const ERROR_21 = 'Client has already been destroyed - no calls possible.'; -export const ERROR_22 = '%s: you passed a null or undefined %s, %s must be a non-empty string.'; -export const ERROR_23 = '%s: %s too long, %s must be 250 characters or less.'; -export const ERROR_24 = '%s: you passed an invalid %s type, %s must be a non-empty string.'; -export const ERROR_25 = '%s: you passed an empty string, %s must be a non-empty string.'; -export const ERROR_26 = '%s: Key must be an object with bucketingKey and matchingKey with valid string properties.'; -export const ERROR_32 = '%s: you passed an invalid %s, %s must be a non-empty string.'; -export const ERROR_33 = '%s: you passed an empty %s, %s must be a non-empty string.'; -export const ERROR_34 = '%s: %s must be a non-empty array.'; -export const ERROR_35 = '%s: you passed a null or undefined traffic_type_name, traffic_type_name must be a non-empty string.'; -export const ERROR_36 = '%s: you passed an invalid traffic_type_name, traffic_type_name must be a non-empty string.'; -export const ERROR_37 = '%s: you passed an empty traffic_type_name, traffic_type_name must be a non-empty string.'; -export const ERROR_38 = 'splitio-settings => You passed an invalid impressionsMode, impressionsMode should be one of the following values: \'%s\' or \'%s\'. Defaulting to \'%s\' mode.'; -export const ERROR_39 = 'Response status is not OK. Status: %s. URL: %s. Message: %s'; -export const ERROR_API_KEY = 'Factory instantiation: %s, api_key must be a non-empty string'; +export const WARN_ENGINE_INVALID_VALUE = 200; +export const WARN_ENGINE_NO_ATTRIBUTES = 201; +export const WARN_CLIENT_NO_LISTENER = 202; +export const WARN_4 = 204; +export const WARN_5 = 205; +export const WARN_6 = 206; +export const WARN_7 = 207; +export const WARN_8 = 208; +export const WARN_9 = 209; +export const WARN_10 = 210; +export const WARN_11 = 211; +export const WARN_SETTING_NULL = 212; +export const WARN_TRIMMING_PROPERTIES = 213; +export const WARN_CLIENT_NOT_READY = 214; +export const WARN_CONVERTING = 215; +export const WARN_TRIMMING = 217; +export const WARN_NOT_EXISTENT_SPLIT = 218; +export const WARN_LOWERCASE_TRAFFIC_TYPE = 219; +export const WARN_NOT_EXISTENT_TT = 220; +export const WARN_INTEGRATION_INVALID = 221; +export const WARN_SPLITS_FILTER_IGNORED = 222; +export const WARN_SPLITS_FILTER_INVALID = 223; +export const WARN_SPLITS_FILTER_EMPTY = 224; +export const WARN_STORAGE_INVALID = 225; +export const WARN_API_KEY = 226; -// node -export const ERROR_1 = 'splitio-client:cleanup => Error with Split graceful shutdown: %s'; -export const ERROR_8 = 'splitio-sync:segment-changes => Factory instantiation: you passed a Browser type authorizationKey, please grab an Api Key from the Split web console that is of type SDK.'; -export const ERROR_6 = 'splitio-offline:splits-fetcher => Ignoring entry on YAML since the format is incorrect.'; - -// commons -export const INFO_0 = 'Split SDK is ready from cache.'; -export const INFO_1 = 'Split SDK is ready.'; -export const INFO_2 = 'splitio-client => Split: %s. Key: %s. Evaluation: %s. Label: %s'; -export const INFO_3 = 'splitio-client => Queueing corresponding impression.'; -export const INFO_4 = 'splitio => New shared client instance created.'; -export const INFO_5 = 'splitio => New Split SDK instance created.'; -export const INFO_6 = 'splitio => Manager instance retrieved.'; -export const INFO_7 = 'splitio-sync:polling-manager => Turning segments data polling %s.'; -export const INFO_8 = 'splitio-sync:polling-manager => Starting polling'; -export const INFO_9 = 'splitio-sync:polling-manager => Stopping polling'; -export const INFO_10 = 'splitio-sync:split-changes => Retrying download of splits #%s. Reason: %s'; -export const INFO_11 = 'splitio-sync:push-manager => Refreshing streaming token in %s seconds.'; -export const INFO_12 = 'splitio-sync:push-manager => Attempting to reconnect in %s seconds.'; -export const INFO_13 = 'splitio-sync:push-manager => Connecting to push streaming.'; -export const INFO_14 = 'splitio-sync:push-manager => Streaming is not available. Switching to polling mode.'; -export const INFO_15 = 'splitio-sync:push-manager => Disconnecting from push streaming.'; -export const INFO_16 = 'splitio-sync:submitters => Flushing full events queue and reseting timer.'; -export const INFO_17 = 'splitio-sync:submitters => Pushing %s %s.'; -export const INFO_18 = 'splitio-sync:sync-manager => Streaming not available. Starting periodic fetch of data.'; -export const INFO_19 = 'splitio-sync:sync-manager => Streaming couldn\'t connect. Continue periodic fetch of data.'; -export const INFO_20 = 'splitio-sync:sync-manager => PUSH (re)connected. Syncing and stopping periodic fetch of data.'; -export const INFO_21 = 'splitio-client:event-tracker => Successfully qeued %s'; - -// commons -export const WARN_0 = 'splitio-engine:value => Value %s %sdoesn\'t match with expected type.'; -export const WARN_1 = 'splitio-engine:value => Defined attribute [%s], no attributes received.'; -export const WARN_2 = 'No listeners for SDK Readiness detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet ready.'; -export const WARN_4 = 'splitio-sync:my-segments => Retrying download of segments #%s. Reason: %s'; -export const WARN_5 = 'splitio-sync:split-changes => Error while doing fetch of Splits. %s'; -export const WARN_6 = 'splitio-sync:sse-handler => Error parsing SSE error notification: %s'; -export const WARN_7 = 'splitio-sync:sse-handler => Error parsing new SSE message notification: %s'; -export const WARN_8 = 'splitio-sync:push-manager => %sFalling back to polling mode.'; -export const WARN_9 = 'splitio-sync:submitters => Droping %s %s after retry. Reason %s.'; -export const WARN_10 = 'splitio-sync:submitters => Failed to push %s %s, keeping data to retry on next iteration. Reason %s.'; -export const WARN_11 = 'splitio-client:event-tracker => Failed to queue %s'; -export const WARN_12 = '%s: Property %s is of invalid type. Setting value to null.'; -export const WARN_13 = '%s: Event has more than 300 properties. Some of them will be trimmed when processed.'; -export const WARN_14 = '%s: the SDK is not ready, results may be incorrect. Make sure to wait for SDK readiness before using this method.'; -export const WARN_15 = '%s: %s "%s" is not of type string, converting.'; -export const WARN_17 = '%s: %s "%s" has extra whitespace, trimming.'; -export const WARN_18 = '%s: you passed "%s" that does not exist in this environment, please double check what Splits exist in the web console.'; -export const WARN_19 = '%s: traffic_type_name should be all lowercase - converting string to lowercase.'; -export const WARN_20 = '%s: Traffic Type %s does not have any corresponding Splits in this environment, make sure you\'re tracking your events to a valid traffic type defined in the Split console.'; -export const WARN_21 = 'splitio-settings => %s integration %s at settings %s invalid. %s'; -export const WARN_22 = 'Factory instantiation: split filters have been configured but will have no effect if mode is not \'%s\', since synchronization is being deferred to an external tool.'; -export const WARN_23 = 'Factory instantiation: split filter at position \'%s\' is invalid. It must be an object with a valid filter type (\'byName\' or \'byPrefix\') and a list of \'values\'.'; -export const WARN_24 = 'Factory instantiation: splitFilters configuration must be a non-empty array of filter objects.'; -export const WARN_25 = 'splitio-settings => The provided storage is invalid. Fallbacking into default MEMORY storage'; -export const WARN_API_KEY = 'Factory instantiation: %s. We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application'; - -// node -export const WARN_3 = 'splitio-offline:splits-fetcher => .split mocks will be deprecated soon in favor of YAML files, which provide more targeting power. Take a look in our documentation.'; +// Log prefixes/tags/categories +export const SETTINGS_LB = 'settings'; +export const INSTANTIATION_LB = 'Factory instantiation'; +export const ENGINE_LB = 'engine'; +export const ENGINE_COMBINER_LB = ENGINE_LB + ':combiner: '; +export const ENGINE_MATCHER_LB = ENGINE_LB + ':matcher: '; +export const ENGINE_VALUE_LB = ENGINE_LB + ':value: '; +export const SYNC_LB = 'sync'; +export const SYNC_MANAGER_LB = SYNC_LB + ':sync-manager: '; +export const SYNC_OFFLINE_LB = SYNC_LB + ':offline: '; +export const SYNC_STREAMING_LB = SYNC_LB + ':streaming: '; +export const SYNC_SPLITS_LB = SYNC_LB + ':split-changes: '; +export const SYNC_SEGMENTS_LB = SYNC_LB + ':segment-changes: '; +export const SYNC_MYSEGMENTS_LB = SYNC_LB + ':my-segments: '; +export const SYNC_POLLING_LB = SYNC_LB + ':polling-manager: '; +export const SYNC_SUBMITTERS_LB = SYNC_LB + ':submitter: '; +export const IMPRESSIONS_TRACKER_LB = 'impressions-tracker: '; +export const EVENTS_TRACKER_LB = 'events-tracker: '; +export const CLEANUP_LB = 'cleanup: '; diff --git a/src/logger/index.ts b/src/logger/index.ts index 5513fb39..27b43ab0 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -2,6 +2,7 @@ import objectAssign from 'object-assign'; import { ILoggerOptions, ILogger } from './types'; import { find } from '../utils/lang'; import { LogLevel } from '../types'; +import { IMap, _Map } from '../utils/lang/maps'; export const LogLevels: { [level: string]: LogLevel } = { DEBUG: 'DEBUG', @@ -31,36 +32,42 @@ export class Logger implements ILogger { private category: string; private options: Required; + private codes: IMap; - constructor(category: string, options?: ILoggerOptions) { + constructor(category: string, options?: ILoggerOptions, codes?: IMap) { this.category = category; this.options = objectAssign({}, defaultOptions, options); + this.codes = codes || new _Map(); } setLogLevel(logLevel: LogLevel) { this.options.logLevel = logLevel; } - debug(msg: string, args?: any[]) { + debug(msg: string | number, args?: any[]) { this._log(LogLevels.DEBUG, msg, args); } - info(msg: string, args?: any[]) { + info(msg: string | number, args?: any[]) { this._log(LogLevels.INFO, msg, args); } - warn(msg: string, args?: any[]) { + warn(msg: string | number, args?: any[]) { this._log(LogLevels.WARN, msg, args); } - error(msg: string, args?: any[]) { + error(msg: string | number, args?: any[]) { this._log(LogLevels.ERROR, msg, args); } - private _log(level: LogLevel, text: string, args?: any[]) { + private _log(level: LogLevel, msg: string | number, args?: any[]) { if (this._shouldLog(level)) { - if (args) text = sprintf(text, args); - const formattedText = this._generateLogMessage(level, text); + if (typeof msg === 'number') { + const format = this.codes.get(msg); + if (format) msg = sprintf(format, args); + else msg = `Message code ${msg}${args ? ', with args: ' + args.toString() : ''}`; + } + const formattedText = this._generateLogMessage(level, msg); console.log(formattedText); } diff --git a/src/logger/messages/debug.ts b/src/logger/messages/debug.ts new file mode 100644 index 00000000..714ff483 --- /dev/null +++ b/src/logger/messages/debug.ts @@ -0,0 +1,49 @@ +import { DEBUG_ENGINE_COMBINER_AND, DEBUG_1, DEBUG_2, DEBUG_3, DEBUG_4, DEBUG_5, DEBUG_6, DEBUG_7, DEBUG_8, DEBUG_9, DEBUG_10, DEBUG_11, DEBUG_12, DEBUG_13, DEBUG_14, DEBUG_15, DEBUG_16, DEBUG_17, DEBUG_18, DEBUG_19, DEBUG_20, DEBUG_21, DEBUG_22, DEBUG_23, DEBUG_24, DEBUG_25, DEBUG_32, DEBUG_33, DEBUG_36, DEBUG_42, DEBUG_43, DEBUG_44, DEBUG_45, DEBUG_46, DEBUG_47, DEBUG_48, DEBUG_49, DEBUG_50, DEBUG_SPLITS_FILTER, SETTINGS_LB, ENGINE_LB, ENGINE_COMBINER_LB, ENGINE_MATCHER_LB, ENGINE_VALUE_LB, SYNC_OFFLINE_LB, IMPRESSIONS_TRACKER_LB, SYNC_LB, SYNC_SPLITS_LB, SYNC_STREAMING_LB, CLEANUP_LB, DEBUG_26, DEBUG_27 } from '../constants'; + +export const codesDebug: [number, string][] = [ + // evaluator + [DEBUG_ENGINE_COMBINER_AND, ENGINE_COMBINER_LB + '[andCombiner] evaluates to %s'], + [DEBUG_1, ENGINE_COMBINER_LB + 'Treatment found: %s'], + [DEBUG_2, ENGINE_COMBINER_LB + 'All predicates evaluated, no treatment found.'], + [DEBUG_3, ENGINE_LB + ': using algo "murmur" bucket %s for key %s using seed %s - treatment %s'], + [DEBUG_4, ENGINE_MATCHER_LB + '[allMatcher] is always true'], + [DEBUG_5, ENGINE_MATCHER_LB + '[betweenMatcher] is %s between %s and %s? %s'], + [DEBUG_6, ENGINE_MATCHER_LB + '[booleanMatcher] %s === %s'], + [DEBUG_7, ENGINE_MATCHER_LB + '[containsAllMatcher] %s contains all elements of %s? %s'], + [DEBUG_8, ENGINE_MATCHER_LB + '[containsAnyMatcher] %s contains at least an element of %s? %s'], + [DEBUG_9, ENGINE_MATCHER_LB + '[containsStringMatcher] %s contains %s? %s'], + [DEBUG_10, ENGINE_MATCHER_LB + '[dependencyMatcher] Parent split "%s" evaluated to "%s" with label "%s". %s evaluated treatment is part of [%s] ? %s.'], + [DEBUG_11, ENGINE_MATCHER_LB + '[dependencyMatcher] will evaluate parent split: "%s" with key: %s %s'], + [DEBUG_12, ENGINE_MATCHER_LB + '[equalToMatcher] is %s equal to %s? %s'], + [DEBUG_13, ENGINE_MATCHER_LB + '[equalToSetMatcher] is %s equal to set %s? %s'], + [DEBUG_14, ENGINE_MATCHER_LB + '[endsWithMatcher] %s ends with %s? %s'], + [DEBUG_15, ENGINE_MATCHER_LB + '[greaterThanEqualMatcher] is %s greater than %s? %s'], + [DEBUG_16, ENGINE_MATCHER_LB + '[lessThanEqualMatcher] is %s less than %s? %s'], + [DEBUG_17, ENGINE_MATCHER_LB + '[partOfMatcher] %s is part of %s? %s'], + [DEBUG_18, ENGINE_MATCHER_LB + '[asyncSegmentMatcher] evaluated %s / %s => %s'], + [DEBUG_19, ENGINE_MATCHER_LB + '[segmentMatcher] evaluated %s / %s => %s'], + [DEBUG_20, ENGINE_MATCHER_LB + '[stringMatcher] does %s matches with %s? %s'], + [DEBUG_21, ENGINE_MATCHER_LB + '[stringMatcher] %s is an invalid regex'], + [DEBUG_22, ENGINE_MATCHER_LB + '[startsWithMatcher] %s starts with %s? %s'], + [DEBUG_23, ENGINE_MATCHER_LB + '[whitelistMatcher] evaluated %s in [%s] => %s'], + [DEBUG_24, ENGINE_VALUE_LB + 'Extracted attribute [%s], [%s] will be used for matching.'], + [DEBUG_25, ENGINE_LB + ':sanitize: Attempted to sanitize [%s] which should be of type [%s]. Sanitized and processed value => [%s]'], + // SDK + [DEBUG_26, CLEANUP_LB + 'Registering cleanup handler %s'], + [DEBUG_27, CLEANUP_LB + 'Deregistering cleanup handler %s'], + [DEBUG_32, ' Retrieving default SDK client.'], + [DEBUG_33, ' Retrieving existing SDK client.'], + // synchronizer + [DEBUG_36, SYNC_OFFLINE_LB + 'Splits data: \n%s'], + [DEBUG_42, SYNC_SPLITS_LB + 'Spin up split update using since = %s'], + [DEBUG_43, SYNC_SPLITS_LB + 'New splits %s'], + [DEBUG_44, SYNC_SPLITS_LB + 'Removed splits %s'], + [DEBUG_45, SYNC_SPLITS_LB + 'Segment names collected %s'], + [DEBUG_46, SYNC_STREAMING_LB + 'New SSE message received, with data: %s.'], + [DEBUG_47, SYNC_LB + ': Starting %s. Running each %s millis'], + [DEBUG_48, SYNC_LB + ': Running %s'], + [DEBUG_49, SYNC_LB + ': Stopping %s'], + [DEBUG_50, IMPRESSIONS_TRACKER_LB + 'Successfully stored %s impression%s.'], + // initialization / settings validation + [DEBUG_SPLITS_FILTER, SETTINGS_LB + ': splits filtering criteria is "%s".'] +]; diff --git a/src/logger/messages/error.ts b/src/logger/messages/error.ts new file mode 100644 index 00000000..48eaa32b --- /dev/null +++ b/src/logger/messages/error.ts @@ -0,0 +1,33 @@ +import { ERROR_0, ERROR_2, ERROR_CLIENT_LISTENER, ERROR_4, ERROR_5, ERROR_7, ERROR_9, ERROR_10, ERROR_11, ERROR_12, ERROR_EVENT_TYPE_FORMAT, ERROR_NOT_PLAIN_OBJECT, ERROR_SIZE_EXCEEDED, ERROR_NOT_FINITE, ERROR_CLIENT_DESTROYED, ERROR_NULL, ERROR_TOO_LONG, ERROR_INVALID_KEY_OBJECT, ERROR_INVALID, ERROR_EMPTY, ERROR_EMPTY_ARRAY, ERROR_INVALID_IMPRESSIONS_MODE, ERROR_39, SETTINGS_LB, ENGINE_COMBINER_LB, SYNC_OFFLINE_LB, SYNC_STREAMING_LB, IMPRESSIONS_TRACKER_LB } from '../constants'; + +export const codesError: [number, string][] = [ + // evaluator + [ERROR_0, ENGINE_COMBINER_LB + 'Invalid Split, no valid rules found'], + // SDK + [ERROR_2, 'logger: Invalid Log Level - No changes to the logs will be applied.'], + [ERROR_4, ' Manager instance is not available.'], // @TODO remove if the manager is not pluggable + [ERROR_5, ' The SDK will not get ready. Reason: %s'], + // synchronizer + [ERROR_7, SYNC_OFFLINE_LB + 'There was an issue loading the mock Splits data, no changes will be applied to the current cache. %s'], + [ERROR_9, SYNC_STREAMING_LB + 'Fail to connect to streaming, with error message: %s'], + [ERROR_10, SYNC_STREAMING_LB + 'Failed to authenticate for streaming. Error: "%s".'], + [ERROR_11, IMPRESSIONS_TRACKER_LB + 'Could not store impressions bulk with %s impression%s. Error: %s'], + [ERROR_12, IMPRESSIONS_TRACKER_LB + 'Impression listener logImpression method threw: %s.'], + [ERROR_39, ' Response status is not OK. Status: %s. URL: %s. Message: %s'], + // client status + [ERROR_CLIENT_LISTENER, 'A listener was added for %s on the SDK, which has already fired and won\'t be emitted again. The callback won\'t be executed.'], + [ERROR_CLIENT_DESTROYED, '%s: Client has already been destroyed - no calls possible.'], + // input validation + [ERROR_EVENT_TYPE_FORMAT, '%s: you passed "%s", event_type must adhere to the regular expression /^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$/g. This means an event_type must be alphanumeric, cannot be more than 80 characters long, and can only include a dash, underscore, period, or colon as separators of alphanumeric characters.'], + [ERROR_NOT_PLAIN_OBJECT, '%s: %s must be a plain object.'], + [ERROR_SIZE_EXCEEDED, '%s: the maximum size allowed for the properties is 32768 bytes, which was exceeded. Event not queued.'], + [ERROR_NOT_FINITE, '%s: value must be a finite number.'], + [ERROR_NULL, '%s: you passed a null or undefined %s. It must be a non-empty string.'], + [ERROR_TOO_LONG, '%s: %s too long. It must have 250 characters or less.'], + [ERROR_INVALID_KEY_OBJECT, '%s: Key must be an object with bucketingKey and matchingKey with valid string properties.'], + [ERROR_INVALID, '%s: you passed an invalid %s. It must be a non-empty string.'], + [ERROR_EMPTY, '%s: you passed an empty %s. It must be a non-empty string.'], + [ERROR_EMPTY_ARRAY, '%s: %s must be a non-empty array.'], + // initialization / settings validation + [ERROR_INVALID_IMPRESSIONS_MODE, SETTINGS_LB + ': you passed an invalid "impressionsMode". It should be one of the following values: %s. Defaulting to "%s" mode.'], +]; diff --git a/src/logger/messages/info.ts b/src/logger/messages/info.ts new file mode 100644 index 00000000..665ca8d8 --- /dev/null +++ b/src/logger/messages/info.ts @@ -0,0 +1,31 @@ +import { INFO_CLIENT_READY_FROM_CACHE, INFO_CLIENT_READY, INFO_2, INFO_3, INFO_4, INFO_5, INFO_6, INFO_7, INFO_8, INFO_9, INFO_10, INFO_11, INFO_12, INFO_13, INFO_14, INFO_15, INFO_16, INFO_17, INFO_18, INFO_19, INFO_20, INFO_21, EVENTS_TRACKER_LB, SYNC_MANAGER_LB, SYNC_POLLING_LB, SYNC_SPLITS_LB, SYNC_STREAMING_LB, SYNC_SUBMITTERS_LB, IMPRESSIONS_TRACKER_LB } from '../constants'; + +const READY_MSG = 'Split SDK is ready'; + +export const codesInfo: [number, string][] = [ + // client status + [INFO_CLIENT_READY_FROM_CACHE, READY_MSG + ' from cache'], + [INFO_CLIENT_READY, READY_MSG], + // SDK + [INFO_2, IMPRESSIONS_TRACKER_LB +'Split: %s. Key: %s. Evaluation: %s. Label: %s'], + [INFO_3, IMPRESSIONS_TRACKER_LB +'Queueing corresponding impression.'], + [INFO_4, ' New shared client instance created.'], + [INFO_5, ' New Split SDK instance created.'], + [INFO_6, ' Manager instance retrieved.'], + [INFO_21, EVENTS_TRACKER_LB + 'Successfully qeued %s'], + // synchronizer + [INFO_7, SYNC_POLLING_LB + 'Turning segments data polling %s.'], + [INFO_8, SYNC_POLLING_LB + 'Starting polling'], + [INFO_9, SYNC_POLLING_LB + 'Stopping polling'], + [INFO_10, SYNC_SPLITS_LB + 'Retrying download of splits #%s. Reason: %s'], + [INFO_16, SYNC_SUBMITTERS_LB + 'Flushing full events queue and reseting timer.'], + [INFO_17, SYNC_SUBMITTERS_LB + 'Pushing %s %s.'], + [INFO_11, SYNC_STREAMING_LB + 'Refreshing streaming token in %s seconds.'], + [INFO_12, SYNC_STREAMING_LB + 'Attempting to reconnect in %s seconds.'], + [INFO_13, SYNC_STREAMING_LB + 'Connecting to streaming.'], + [INFO_14, SYNC_STREAMING_LB + 'Streaming is disabled for given Api key. Switching to polling mode.'], + [INFO_15, SYNC_STREAMING_LB + 'Disconnecting from streaming.'], + [INFO_18, SYNC_MANAGER_LB + 'Streaming not available. Starting polling.'], + [INFO_19, SYNC_MANAGER_LB + 'Streaming couldn\'t connect. Continue polling.'], + [INFO_20, SYNC_MANAGER_LB + 'Streaming (re)connected. Syncing and stopping polling.'], +]; diff --git a/src/logger/messages/warn.ts b/src/logger/messages/warn.ts new file mode 100644 index 00000000..f351cd2d --- /dev/null +++ b/src/logger/messages/warn.ts @@ -0,0 +1,35 @@ +import { WARN_ENGINE_INVALID_VALUE, WARN_ENGINE_NO_ATTRIBUTES, WARN_CLIENT_NO_LISTENER, WARN_4, WARN_5, WARN_6, WARN_7, WARN_8, WARN_9, WARN_10, WARN_11, WARN_SETTING_NULL, WARN_TRIMMING_PROPERTIES, WARN_CLIENT_NOT_READY, WARN_CONVERTING, WARN_TRIMMING, WARN_NOT_EXISTENT_SPLIT, WARN_LOWERCASE_TRAFFIC_TYPE, WARN_NOT_EXISTENT_TT, WARN_INTEGRATION_INVALID, WARN_SPLITS_FILTER_IGNORED, WARN_SPLITS_FILTER_INVALID, WARN_SPLITS_FILTER_EMPTY, WARN_STORAGE_INVALID, WARN_API_KEY, SETTINGS_LB, ENGINE_VALUE_LB, EVENTS_TRACKER_LB, SYNC_MYSEGMENTS_LB, SYNC_SPLITS_LB, SYNC_STREAMING_LB, SYNC_SUBMITTERS_LB } from '../constants'; + +export const codesWarn: [number, string][] = [ + // evaluator + [WARN_ENGINE_INVALID_VALUE, ENGINE_VALUE_LB + 'Value %s doesn\'t match with expected type.'], + [WARN_ENGINE_NO_ATTRIBUTES, ENGINE_VALUE_LB + 'Defined attribute [%s], no attributes received.'], + // synchronizer + [WARN_4, SYNC_MYSEGMENTS_LB + 'Retrying download of segments #%s. Reason: %s'], + [WARN_5, SYNC_SPLITS_LB + 'Error while doing fetch of Splits. %s'], + [WARN_6, SYNC_STREAMING_LB + 'Error parsing SSE error notification: %s'], + [WARN_7, SYNC_STREAMING_LB + 'Error parsing new SSE message notification: %s'], + [WARN_8, SYNC_STREAMING_LB + 'Falling back to polling mode. Reason: %s'], + [WARN_9, SYNC_SUBMITTERS_LB + 'Droping %s %s after retry. Reason: %s.'], + [WARN_10, SYNC_SUBMITTERS_LB + 'Failed to push %s %s, keeping data to retry on next iteration. Reason: %s.'], + // SDK + [WARN_11, EVENTS_TRACKER_LB + 'Failed to queue %s'], + // client status + [WARN_CLIENT_NOT_READY, '%s: the SDK is not ready, results may be incorrect. Make sure to wait for SDK readiness before using this method.'], + [WARN_CLIENT_NO_LISTENER, 'No listeners for SDK Readiness detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet ready.'], + // input validation + [WARN_SETTING_NULL, '%s: Property "%s" is of invalid type. Setting value to null.'], + [WARN_TRIMMING_PROPERTIES, '%s: Event has more than 300 properties. Some of them will be trimmed when processed.'], + [WARN_CONVERTING, '%s: %s "%s" is not of type string, converting.'], + [WARN_TRIMMING, '%s: %s "%s" has extra whitespace, trimming.'], + [WARN_NOT_EXISTENT_SPLIT, '%s: split "%s" does not exist in this environment, please double check what splits exist in the web console.'], + [WARN_LOWERCASE_TRAFFIC_TYPE, '%s: traffic_type_name should be all lowercase - converting string to lowercase.'], + [WARN_NOT_EXISTENT_TT, '%s: traffic type "%s" does not have any corresponding split in this environment, make sure you\'re tracking your events to a valid traffic type defined in the web console.'], + // initialization / settings validation + [WARN_INTEGRATION_INVALID, SETTINGS_LB+': %s integration %s at settings %s invalid. %s'], + [WARN_SPLITS_FILTER_IGNORED, SETTINGS_LB+': split filters have been configured but will have no effect if mode is not "%s", since synchronization is being deferred to an external tool.'], + [WARN_SPLITS_FILTER_INVALID, SETTINGS_LB+': split filter at position %s is invalid. It must be an object with a valid filter type ("byName" or "byPrefix") and a list of "values".'], + [WARN_SPLITS_FILTER_EMPTY, SETTINGS_LB+': splitFilters configuration must be a non-empty array of filter objects.'], + [WARN_STORAGE_INVALID, SETTINGS_LB+': The provided storage is invalid. Fallbacking into default MEMORY storage'], + [WARN_API_KEY, SETTINGS_LB+': You already have %s. We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application'] +]; diff --git a/src/logger/sdkLogger.ts b/src/logger/sdkLogger.ts index 94c8b5fa..32840ea5 100644 --- a/src/logger/sdkLogger.ts +++ b/src/logger/sdkLogger.ts @@ -2,8 +2,6 @@ import { LogLevels, isLogLevelString } from './index'; import { ILoggerAPI } from '../types'; import { ILogger } from './types'; import { ERROR_2 } from './constants'; -// const log = logFactory('splitio-utils:logger'); - /** * The public Logger utility API exposed via SplitFactory, used to update the log level. diff --git a/src/logger/types.ts b/src/logger/types.ts index 5fa1d2c4..d691d255 100644 --- a/src/logger/types.ts +++ b/src/logger/types.ts @@ -8,11 +8,11 @@ export interface ILoggerOptions { export interface ILogger { setLogLevel(logLevel: LogLevel): void - debug(msg: string, args?: any[]): void + debug(msg: string | number, args?: any[]): void - info(msg: string, args?: any[]): void + info(msg: string | number, args?: any[]): void - warn(msg: string, args?: any[]): void + warn(msg: string | number, args?: any[]): void - error(msg: string, args?: any[]): void + error(msg: string | number, args?: any[]): void } diff --git a/src/readiness/__tests__/sdkReadinessManager.spec.ts b/src/readiness/__tests__/sdkReadinessManager.spec.ts index 7034592c..cde58e8f 100644 --- a/src/readiness/__tests__/sdkReadinessManager.spec.ts +++ b/src/readiness/__tests__/sdkReadinessManager.spec.ts @@ -4,6 +4,7 @@ import { IEventEmitter } from '../../types'; import { SDK_READY, SDK_READY_FROM_CACHE, SDK_READY_TIMED_OUT, SDK_UPDATE } from '../constants'; import sdkReadinessManagerFactory from '../sdkReadinessManager'; import { IReadinessManager } from '../types'; +import { ERROR_CLIENT_LISTENER, INFO_CLIENT_READY_FROM_CACHE, INFO_CLIENT_READY, WARN_CLIENT_NO_LISTENER } from '../../logger/constants'; const EventEmitterMock = jest.fn(() => ({ on: jest.fn(), @@ -81,7 +82,7 @@ describe('SDK Readiness Manager - Event emitter', () => { const readyFromCacheEventCB = gateMock.once.mock.calls[2][1]; readyFromCacheEventCB(); expect(loggerMock.info.mock.calls.length).toBe(1); // If the SDK_READY_FROM_CACHE event fires, we get a info message. - expect(loggerMock.info.mock.calls[0]).toEqual(['Split SDK is ready from cache.']); // Telling us the SDK is ready to be used with data from cache. + expect(loggerMock.info).toBeCalledWith(INFO_CLIENT_READY_FROM_CACHE); // Telling us the SDK is ready to be used with data from cache. }); test('The event callbacks should work as expected - SDK_READY emits with no callbacks', () => { @@ -93,21 +94,21 @@ describe('SDK Readiness Manager - Event emitter', () => { emitReadyEvent(sdkReadinessManager.readinessManager); expect(loggerMock.warn.mock.calls.length).toBe(1); // If the SDK_READY event fires and we have no callbacks for it (neither event nor ready promise) we get a warning. - expect(loggerMock.warn.mock.calls[0]).toEqual(['No listeners for SDK Readiness detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet ready.']); // Telling us there were no listeners and evaluations before this point may have been incorrect. + expect(loggerMock.warn).toBeCalledWith(WARN_CLIENT_NO_LISTENER); // Telling us there were no listeners and evaluations before this point may have been incorrect. expect(loggerMock.info.mock.calls.length).toBe(1); // If the SDK_READY event fires, we get a info message. - expect(loggerMock.info.mock.calls[0]).toEqual(['Split SDK is ready.']); // Telling us the SDK is ready. + expect(loggerMock.info).toBeCalledWith(INFO_CLIENT_READY); // Telling us the SDK is ready. // Now it's marked as ready. addListenerCB('this event we do not care'); expect(loggerMock.error.mock.calls.length).toBe(0); // Now if we add a listener to an event unrelated with readiness, we get no errors logged. addListenerCB(SDK_READY); - expect(loggerMock.error.mock.calls).toEqual([['A listener was added for SDK_READY on the SDK, which has already fired and won\'t be emitted again. The callback won\'t be executed.']]); // If we try to add a listener to SDK_READY we get the corresponding warning. + expect(loggerMock.error).toBeCalledWith(ERROR_CLIENT_LISTENER, ['SDK_READY']); // If we try to add a listener for the already emitted SDK_READY event, we get the corresponding error. loggerMock.error.mockClear(); addListenerCB(SDK_READY_TIMED_OUT); - expect(loggerMock.error.mock.calls).toEqual([['A listener was added for SDK_READY_TIMED_OUT on the SDK, which has already fired and won\'t be emitted again. The callback won\'t be executed.']]); // If we try to add a listener to SDK_READY_TIMED_OUT we get the corresponding warning. + expect(loggerMock.error).toBeCalledWith(ERROR_CLIENT_LISTENER, ['SDK_READY_TIMED_OUT']); // If we try to add a listener for the already emitted SDK_READY_TIMED_OUT event, we get the corresponding error. }); test('The event callbacks should work as expected - SDK_READY emits with callbacks', () => { @@ -125,7 +126,7 @@ describe('SDK Readiness Manager - Event emitter', () => { expect(loggerMock.error.mock.calls.length).toBe(0); // As we had at least one listener, we get no errors. expect(loggerMock.info.mock.calls.length).toBe(1); // If the SDK_READY event fires, we get a info message. - expect(loggerMock.info.mock.calls[0]).toEqual(['Split SDK is ready.']); // Telling us the SDK is ready. + expect(loggerMock.info).toBeCalledWith(INFO_CLIENT_READY); // Telling us the SDK is ready. }); test('The event callbacks should work as expected - If we end up removing the listeners for SDK_READY, it behaves as if it had none', () => { @@ -145,7 +146,7 @@ describe('SDK Readiness Manager - Event emitter', () => { removeListenerCB(SDK_READY); emitReadyEvent(sdkReadinessManager.readinessManager); - expect(loggerMock.warn.mock.calls[0]).toEqual(['No listeners for SDK Readiness detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet ready.']); // We get the warning. + expect(loggerMock.warn).toBeCalledWith(WARN_CLIENT_NO_LISTENER); // We get the warning. }); test('The event callbacks should work as expected - If we end up removing the listeners for SDK_READY, it behaves as if it had none', () => { @@ -271,7 +272,7 @@ describe('SDK Readiness Manager - Ready promise', () => { const readyEventCB = sdkReadinessManager.readinessManager.gate.once.mock.calls[0][1]; readyEventCB(); - expect(loggerMock.warn.mock.calls).toEqual([['No listeners for SDK Readiness detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet ready.']]); // We would get the warning if the SDK get\'s ready before attaching any callbacks to ready promise. + expect(loggerMock.warn).toBeCalledWith(WARN_CLIENT_NO_LISTENER); // We would get the warning if the SDK get\'s ready before attaching any callbacks to ready promise. loggerMock.warn.mockClear(); readyPromise.then(() => { @@ -282,7 +283,7 @@ describe('SDK Readiness Manager - Ready promise', () => { }); readyEventCB(); - expect(loggerMock.warn.mock.calls.length).toBe(0); // But if we have a listener there are no warnings. + expect(loggerMock.warn).not.toBeCalled(); // But if we have a listener there are no warnings. }); test('.ready() rejected promises have a default onRejected handler that just logs the error', (done) => { @@ -295,7 +296,7 @@ describe('SDK Readiness Manager - Ready promise', () => { () => { throw new Error('It should be a promise that was rejected on SDK_READY_TIMED_OUT, not resolved.'); } ); - expect(loggerMock.error.mock.calls.length === 0).toBe(true); // not called until promise is rejected + expect(loggerMock.error).not.toBeCalled(); // not called until promise is rejected setTimeout(() => { expect(loggerMock.error.mock.calls).toEqual([[timeoutErrorMessage]]); // If we don\'t handle the rejected promise, an error is logged. diff --git a/src/readiness/sdkReadinessManager.ts b/src/readiness/sdkReadinessManager.ts index d2bfffe7..f1ada0fa 100644 --- a/src/readiness/sdkReadinessManager.ts +++ b/src/readiness/sdkReadinessManager.ts @@ -5,7 +5,7 @@ import { ISdkReadinessManager } from './types'; import { IEventEmitter } from '../types'; import { SDK_READY, SDK_READY_TIMED_OUT, SDK_READY_FROM_CACHE, SDK_UPDATE } from './constants'; import { ILogger } from '../logger/types'; -import { ERROR_3, INFO_0, INFO_1, WARN_2 } from '../logger/constants'; +import { ERROR_CLIENT_LISTENER, INFO_CLIENT_READY_FROM_CACHE, INFO_CLIENT_READY, WARN_CLIENT_NO_LISTENER } from '../logger/constants'; const NEW_LISTENER_EVENT = 'newListener'; const REMOVE_LISTENER_EVENT = 'removeListener'; @@ -35,7 +35,7 @@ export default function sdkReadinessManagerFactory( readinessManager.gate.on(NEW_LISTENER_EVENT, (event: any) => { if (event === SDK_READY || event === SDK_READY_TIMED_OUT) { if (readinessManager.isReady()) { - log.error(ERROR_3, [event === SDK_READY ? 'SDK_READY' : 'SDK_READY_TIMED_OUT']); + log.error(ERROR_CLIENT_LISTENER, [event === SDK_READY ? 'SDK_READY' : 'SDK_READY_TIMED_OUT']); } else if (event === SDK_READY) { readyCbCount++; } @@ -46,7 +46,7 @@ export default function sdkReadinessManagerFactory( const readyPromise = generateReadyPromise(); readinessManager.gate.once(SDK_READY_FROM_CACHE, () => { - log.info(INFO_0); + log.info(INFO_CLIENT_READY_FROM_CACHE); }); // default onRejected handler, that just logs the error, if ready promise doesn't have one. @@ -57,9 +57,9 @@ export default function sdkReadinessManagerFactory( function generateReadyPromise() { const promise = promiseWrapper(new Promise((resolve, reject) => { readinessManager.gate.once(SDK_READY, () => { - log.info(INFO_1); + log.info(INFO_CLIENT_READY); - if (readyCbCount === internalReadyCbCount && !promise.hasOnFulfilled()) log.warn(WARN_2); + if (readyCbCount === internalReadyCbCount && !promise.hasOnFulfilled()) log.warn(WARN_CLIENT_NO_LISTENER); resolve(); }); readinessManager.gate.once(SDK_READY_TIMED_OUT, reject); diff --git a/src/sdkClient/clientInputValidation.ts b/src/sdkClient/clientInputValidation.ts index 93e7fca8..c0fc26bf 100644 --- a/src/sdkClient/clientInputValidation.ts +++ b/src/sdkClient/clientInputValidation.ts @@ -32,7 +32,7 @@ export default function clientInputValidationDecorator S throw new Error('Shared Client not supported by the storage mechanism. Create isolated instances instead.'); } - log.debug(DEBUG_31); + log.debug(DEBUG_32); return clientInstance; }; } diff --git a/src/sdkClient/sdkClientMethodCS.ts b/src/sdkClient/sdkClientMethodCS.ts index c50c3986..28ae2ac9 100644 --- a/src/sdkClient/sdkClientMethodCS.ts +++ b/src/sdkClient/sdkClientMethodCS.ts @@ -47,7 +47,7 @@ export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key? } // Validate the key value. The trafficType (2nd argument) is ignored - const validKey = validateKey(log, key, `Shared ${method}`); + const validKey = validateKey(log, key, method); if (validKey === false) { throw new Error('Shared Client needs a valid key.'); } diff --git a/src/sdkManager/index.ts b/src/sdkManager/index.ts index 631d1c0c..a929d082 100644 --- a/src/sdkManager/index.ts +++ b/src/sdkManager/index.ts @@ -67,7 +67,7 @@ export function sdkManagerFactory { let updated = cache.killLocally('nonexistent_split', 'other_treatment', 101); const nonexistentSplit = cache.getSplit('nonexistent_split'); - expect(updated).toBe(false); // t exist + expect(updated).toBe(false); // killLocally resolves without update if split doesn't exist expect(nonexistentSplit).toBe(null); // non-existent split keeps being non-existent // kill an existent split diff --git a/src/storages/inLocalStorage/index.ts b/src/storages/inLocalStorage/index.ts index 4ca9ee2d..6a4c8624 100644 --- a/src/storages/inLocalStorage/index.ts +++ b/src/storages/inLocalStorage/index.ts @@ -10,8 +10,8 @@ import MySegmentsCacheInMemory from '../inMemory/MySegmentsCacheInMemory'; import SplitsCacheInMemory from '../inMemory/SplitsCacheInMemory'; import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser'; import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS'; -// import { logFactory } from '../../logger/sdkLogger'; -// const log = logFactory('splitio-storage:localstorage'); + +export const logPrefix = 'storage:localstorage: '; export interface InLocalStorageOptions { prefix?: string @@ -28,7 +28,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}) { // Fallback to InMemoryStorage if LocalStorage API is not available if (!isLocalStorageAvailable()) { - params.log.warn('LocalStorage API is unavailable. Fallbacking into default MEMORY storage'); + params.log.warn(logPrefix + 'LocalStorage API is unavailable. Fallbacking into default MEMORY storage'); return InMemoryStorageCSFactory(params); } diff --git a/src/storages/inRedis/EventsCacheInRedis.ts b/src/storages/inRedis/EventsCacheInRedis.ts index 839faee1..5a8caab5 100644 --- a/src/storages/inRedis/EventsCacheInRedis.ts +++ b/src/storages/inRedis/EventsCacheInRedis.ts @@ -4,8 +4,8 @@ import KeyBuilderSS from '../KeyBuilderSS'; import { Redis } from 'ioredis'; import { SplitIO } from '../../types'; import { ILogger } from '../../logger/types'; -// import { logFactory } from '../../logger/sdkLogger'; -// const log = logFactory('splitio-storage:redis'); + +const logPrefix = 'storage:redis: '; export default class EventsCacheInRedis implements IEventsCacheAsync { @@ -33,7 +33,7 @@ export default class EventsCacheInRedis implements IEventsCacheAsync { // We use boolean values to signal successful queueing .then(() => true) .catch(err => { - this.log.error(`Error adding event to queue: ${err}.`); + this.log.error(logPrefix + `Error adding event to queue: ${err}.`); return false; }); } diff --git a/src/storages/inRedis/RedisAdapter.ts b/src/storages/inRedis/RedisAdapter.ts index fb7673ec..7d0636bc 100644 --- a/src/storages/inRedis/RedisAdapter.ts +++ b/src/storages/inRedis/RedisAdapter.ts @@ -4,8 +4,8 @@ import { merge, isString } from '../../utils/lang'; import { _Set, setToArray, ISet } from '../../utils/lang/sets'; import thenable from '../../utils/promise/thenable'; import timeout from '../../utils/promise/timeout'; -// import { logFactory } from '../../logger/sdkLogger'; -// const log = logFactory('splitio-storage:redis-adapter'); + +const logPrefix = 'storage:redis-adapter: '; // If we ever decide to fully wrap every method, there's a Commander.getBuiltinCommands from ioredis. const METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', 'pipeline', 'expire', 'mget']; @@ -55,16 +55,16 @@ export default class RedisAdapter extends ioredis { _listenToEvents() { this.once('ready', () => { const commandsCount = this._notReadyCommandsQueue ? this._notReadyCommandsQueue.length : 0; - this.log.info(`Redis connection established. Queued commands: ${commandsCount}.`); + this.log.info(logPrefix + `Redis connection established. Queued commands: ${commandsCount}.`); this._notReadyCommandsQueue && this._notReadyCommandsQueue.forEach(queued => { - this.log.info(`Executing queued ${queued.name} command.`); + this.log.info(logPrefix + `Executing queued ${queued.name} command.`); queued.command().then(queued.resolve).catch(queued.reject); }); // After the SDK is ready for the first time we'll stop queueing commands. This is just so we can keep handling BUR for them. this._notReadyCommandsQueue = undefined; }); this.once('close', () => { - this.log.info('Redis connection closed.'); + this.log.info(logPrefix + 'Redis connection closed.'); }); } @@ -78,7 +78,7 @@ export default class RedisAdapter extends ioredis { const params = arguments; function commandWrapper() { - instance.log.debug(`Executing ${method}.`); + instance.log.debug(logPrefix + `Executing ${method}.`); // Return original method const result = originalMethod.apply(instance, params); @@ -93,7 +93,7 @@ export default class RedisAdapter extends ioredis { result.then(cleanUpRunningCommandsCb, cleanUpRunningCommandsCb); return timeout(instance._options.operationTimeout, result).catch(err => { - instance.log.error(`${method} operation threw an error or exceeded configured timeout of ${instance._options.operationTimeout}ms. Message: ${err}`); + instance.log.error(logPrefix + `${method} operation threw an error or exceeded configured timeout of ${instance._options.operationTimeout}ms. Message: ${err}`); // Handling is not the adapter responsibility. throw err; }); @@ -126,19 +126,19 @@ export default class RedisAdapter extends ioredis { setTimeout(function deferedDisconnect() { if (instance._runningCommands.size > 0) { - instance.log.info(`Attempting to disconnect but there are ${instance._runningCommands.size} commands still waiting for resolution. Defering disconnection until those finish.`); + instance.log.info(logPrefix + `Attempting to disconnect but there are ${instance._runningCommands.size} commands still waiting for resolution. Defering disconnection until those finish.`); Promise.all(setToArray(instance._runningCommands)) .then(() => { - instance.log.debug('Pending commands finished successfully, disconnecting.'); + instance.log.debug(logPrefix + 'Pending commands finished successfully, disconnecting.'); originalMethod.apply(instance, params); }) .catch(e => { - instance.log.warn(`Pending commands finished with error: ${e}. Proceeding with disconnection.`); + instance.log.warn(logPrefix + `Pending commands finished with error: ${e}. Proceeding with disconnection.`); originalMethod.apply(instance, params); }); } else { - instance.log.debug('No commands pending execution, disconnect.'); + instance.log.debug(logPrefix + 'No commands pending execution, disconnect.'); // Nothing pending, just proceed. originalMethod.apply(instance, params); } diff --git a/src/storages/inRedis/SplitsCacheInRedis.ts b/src/storages/inRedis/SplitsCacheInRedis.ts index 9e8d0201..c601b549 100644 --- a/src/storages/inRedis/SplitsCacheInRedis.ts +++ b/src/storages/inRedis/SplitsCacheInRedis.ts @@ -3,8 +3,8 @@ import KeyBuilderSS from '../KeyBuilderSS'; import { ISplitsCacheAsync } from '../types'; import { Redis } from 'ioredis'; import { ILogger } from '../../logger/types'; -// import { logFactory } from '../../logger/sdkLogger'; -// const log = logFactory('splitio-storage:redis'); + +const logPrefix = 'storage:redis: '; /** * Discard errors for an answer of multiple operations. @@ -83,7 +83,7 @@ export default class SplitsCacheInRedis implements ISplitsCacheAsync { */ getSplit(name: string): Promise { if (this.redisError) { - this.log.error(this.redisError); + this.log.error(logPrefix + this.redisError); throw this.redisError; } @@ -137,14 +137,14 @@ export default class SplitsCacheInRedis implements ISplitsCacheAsync { .then((ttCount: string | null | number) => { ttCount = parseInt(ttCount as string, 10); if (!isFiniteNumber(ttCount) || ttCount < 0) { - this.log.info(`Could not validate traffic type existance of ${trafficType} due to data corruption of some sorts.`); + this.log.info(logPrefix + `Could not validate traffic type existance of ${trafficType} due to data corruption of some sorts.`); return false; } return ttCount > 0; }) .catch(e => { - this.log.error(`Could not validate traffic type existance of ${trafficType} due to an error: ${e}.`); + this.log.error(logPrefix + `Could not validate traffic type existance of ${trafficType} due to an error: ${e}.`); // If there is an error, bypass the validation so the event can get tracked. return true; }); @@ -169,7 +169,7 @@ export default class SplitsCacheInRedis implements ISplitsCacheAsync { */ getSplits(names: string[]): Promise> { if (this.redisError) { - this.log.error(this.redisError); + this.log.error(logPrefix + this.redisError); throw this.redisError; } @@ -183,7 +183,7 @@ export default class SplitsCacheInRedis implements ISplitsCacheAsync { return Promise.resolve(splits); }) .catch(e => { - this.log.error(`Could not grab splits due to an error: ${e}.`); + this.log.error(logPrefix + `Could not grab splits due to an error: ${e}.`); return Promise.reject(e); }); } diff --git a/src/storages/inRedis/__tests__/RedisAdapter.spec.ts b/src/storages/inRedis/__tests__/RedisAdapter.spec.ts index 93c4bdb1..918c895e 100644 --- a/src/storages/inRedis/__tests__/RedisAdapter.spec.ts +++ b/src/storages/inRedis/__tests__/RedisAdapter.spec.ts @@ -6,6 +6,7 @@ import { _Set, setToArray } from '../../../utils/lang/sets'; // Mocking sdkLogger import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; +const logPrefix = 'storage:redis-adapter: '; // Mocking ioredis @@ -155,23 +156,23 @@ describe('STORAGE Redis Adapter', () => { }); test('instance methods - _listenToEvents', (done) => { - expect(ioredisMock.once.mock.calls.length).toBe(0); // Control assertion - expect(ioredisMock[METHODS_TO_PROMISE_WRAP[0]].mock.calls.length).toBe(0); // Control assertion + expect(ioredisMock.once).not.toBeCalled(); // Control assertion + expect(ioredisMock[METHODS_TO_PROMISE_WRAP[0]]).not.toBeCalled(); // Control assertion const instance = new RedisAdapter(loggerMock, { url: 'redis://localhost:6379/0' }); - expect(ioredisMock.once.mock.calls.length).toBe(2); // If the method was called, it should have called the `once` function twice. If that it the case we can assume that the method was called on creation. + expect(ioredisMock.once).toBeCalledTimes(2); // If the method was called, it should have called the `once` function twice. If that it the case we can assume that the method was called on creation. // Reset stubs again, we'll check the behaviour calling the method directly. clearAllMocks(); - expect(ioredisMock.once.mock.calls.length).toBe(0); // Control assertion - expect(ioredisMock[METHODS_TO_PROMISE_WRAP[METHODS_TO_PROMISE_WRAP.length - 1]].mock.calls.length).toBe(0); // Control assertion + expect(ioredisMock.once).not.toBeCalled(); // Control assertion + expect(ioredisMock[METHODS_TO_PROMISE_WRAP[METHODS_TO_PROMISE_WRAP.length - 1]]).not.toBeCalled(); // Control assertion instance._listenToEvents(); - expect(ioredisMock.once.mock.calls.length).toBe(2); // The "once" method of the instance should be called twice. + expect(ioredisMock.once).toBeCalledTimes(2); // The "once" method of the instance should be called twice. const firstCallArgs = ioredisMock.once.mock.calls[0]; @@ -183,21 +184,21 @@ describe('STORAGE Redis Adapter', () => { expect(secondCallArgs[0]).toBe('close'); // First argument for the first call should be the "close" event. expect(typeof secondCallArgs[1]).toBe('function'); // second argument for the first call should be a callback function. - expect(loggerMock.warn.mock.calls.length).toBe(0); // Control assertion + expect(loggerMock.warn).not.toBeCalled(); // Control assertion secondCallArgs[1](); // Execute the callback for "close" - expect(loggerMock.info.mock.calls.length).toBe(1); // The callback for the "close" event will only log info to the user about what is going on. - expect(loggerMock.info.mock.calls[0]).toEqual(['Redis connection closed.']); // The callback for the "close" event will only log info to the user about what is going on. + expect(loggerMock.info).toBeCalledTimes(1); // The callback for the "close" event will only log info to the user about what is going on. + expect(loggerMock.info.mock.calls[0]).toEqual([logPrefix + 'Redis connection closed.']); // The callback for the "close" event will only log info to the user about what is going on. loggerMock.info.mockClear(); - expect(loggerMock.info.mock.calls.length).toBe(0); // Control assertion + expect(loggerMock.info).not.toBeCalled(); // Control assertion expect(Array.isArray(instance._notReadyCommandsQueue)).toBe(true); // Control assertion // Without any offline commands queued, execute the callback for "ready" firstCallArgs[1](); - expect(loggerMock.info.mock.calls.length).not.toBe(0); // The callback for the "ready" event will inform the user about the trigger. - expect(loggerMock.info.mock.calls[0]).toEqual(['Redis connection established. Queued commands: 0.']); // The callback for the "ready" event will inform the user about the trigger. + expect(loggerMock.info).toBeCalledTimes(1); // The callback for the "ready" event will inform the user about the trigger. + expect(loggerMock.info).toBeCalledWith(logPrefix + 'Redis connection established. Queued commands: 0.'); // The callback for the "ready" event will inform the user about the trigger. expect(instance._notReadyCommandsQueue).toBe(undefined); // After the DB is ready, it will clean up the offline commands queue so we do not queue commands anymore. // Don't do this at home @@ -219,19 +220,19 @@ describe('STORAGE Redis Adapter', () => { // execute the callback for "ready" once more firstCallArgs[1](); - expect(loggerMock.info.mock.calls.length).toBe(3); // If we had queued commands, it will log the event (1 call) as well as each executed command (n calls). + expect(loggerMock.info).toBeCalledTimes(3); // If we had queued commands, it will log the event (1 call) as well as each executed command (n calls). expect(loggerMock.info.mock.calls).toEqual([ - ['Redis connection established. Queued commands: 2.'], // The callback for the "ready" event will inform the user about the trigger and the amount of queued commands. - ['Executing queued GET command.'], // If we had queued, it will log the event as well as each executed command. - ['Executing queued SET command.'] // If we had queued commands, it will log the event as well as each executed command. + [logPrefix + 'Redis connection established. Queued commands: 2.'], // The callback for the "ready" event will inform the user about the trigger and the amount of queued commands. + [logPrefix + 'Executing queued GET command.'], // If we had queued, it will log the event as well as each executed command. + [logPrefix + 'Executing queued SET command.'] // If we had queued commands, it will log the event as well as each executed command. ]); - expect(queuedGetCommand.command.mock.calls.length).toBe(1); // It will execute each queued command. + expect(queuedGetCommand.command).toBeCalledTimes(1); // It will execute each queued command. setTimeout(() => { // Remember this is tied to a promise. - expect(queuedGetCommand.resolve.mock.calls.length).not.toBe(0); // And depending on what happens with the command promise, it will call the resolve or reject function for the promise wrapper. - expect(queuedGetCommand.reject.mock.calls.length).toBe(0); // And depending on what happens with the command promise, it will call the resolve or reject function for the promise wrapper. - expect(queuedSetCommand.reject.mock.calls.length).not.toBe(0); // And depending on what happens with the command promise, it will call the resolve or reject function for the promise wrapper. - expect(queuedSetCommand.resolve.mock.calls.length).toBe(0); // And depending on what happens with the command promise, it will call the resolve or reject function for the promise wrapper. + expect(queuedGetCommand.resolve).toBeCalled(); // And depending on what happens with the command promise, it will call the resolve or reject function for the promise wrapper. + expect(queuedGetCommand.reject).not.toBeCalled(); // And depending on what happens with the command promise, it will call the resolve or reject function for the promise wrapper. + expect(queuedSetCommand.reject).toBeCalled(); // And depending on what happens with the command promise, it will call the resolve or reject function for the promise wrapper. + expect(queuedSetCommand.resolve).not.toBeCalled(); // And depending on what happens with the command promise, it will call the resolve or reject function for the promise wrapper. done(); }, 5); @@ -245,7 +246,7 @@ describe('STORAGE Redis Adapter', () => { forEach(METHODS_TO_PROMISE_WRAP, methodName => { expect(instance[methodName]).not.toBe(ioredisMock[methodName]); // Method "${methodName}" from ioredis library should be wrapped. - expect(ioredisMock[methodName].mock.calls.length).toBe(0); // Checking that the method was not called yet. + expect(ioredisMock[methodName]).not.toBeCalled(); // Checking that the method was not called yet. const startingQueueLength = instance._notReadyCommandsQueue.length; @@ -267,12 +268,12 @@ describe('STORAGE Redis Adapter', () => { forEach(METHODS_TO_PROMISE_WRAP, (methodName, index) => { // We do NOT have the commands queue on this state, so a call for this methods will execute the command. - expect(ioredisMock[methodName].mock.calls.length).toBe(0); // Control assertion - Original method (${methodName}) was not called yet + expect(ioredisMock[methodName]).not.toBeCalled(); // Control assertion - Original method (${methodName}) was not called yet const previousTimeoutCalls = timeout.mock.calls.length; let previousRunningCommandsSize = instance._runningCommands.size; instance[methodName](methodName).catch(() => { }); // Swallow exception so it's not spread to logs. - expect(ioredisMock[methodName].mock.calls.length).not.toBe(0); // Original method (${methodName}) is called right away (through wrapper) when we are not queueing anymore. + expect(ioredisMock[methodName]).toBeCalled(); // Original method (${methodName}) is called right away (through wrapper) when we are not queueing anymore. expect(instance._runningCommands.size).toBe(previousRunningCommandsSize + 1); // If the result of the operation was a thenable it will add the item to the running commands queue. expect(timeout.mock.calls.length).toBe(previousTimeoutCalls + 1); // The promise returned by the original method should have a timeout wrapper. @@ -286,7 +287,7 @@ describe('STORAGE Redis Adapter', () => { commandTimeoutResolver.rej('test'); setTimeout(() => { // Allow the promises to tick. expect(instance._runningCommands.has(commandTimeoutResolver.originalPromise)).toBe(false); // After a command finishes with error, it's promise is removed from the instance._runningCommands queue. - expect(loggerMock.error.mock.calls[index]).toEqual([`${methodName} operation threw an error or exceeded configured timeout of 5000ms. Message: test`]); // The log error method should be called with the corresponding messages, depending on the method, error and operationTimeout. + expect(loggerMock.error.mock.calls[index]).toEqual([`${logPrefix}${methodName} operation threw an error or exceeded configured timeout of 5000ms. Message: test`]); // The log error method should be called with the corresponding messages, depending on the method, error and operationTimeout. }, 0); }); @@ -310,7 +311,7 @@ describe('STORAGE Redis Adapter', () => { commandTimeoutResolver.res('test'); setTimeout(() => { // Allow the promises to tick. - expect(loggerMock.error.mock.calls.length).toBe(0); // No error should be logged + expect(loggerMock.error).not.toBeCalled(); // No error should be logged expect(instance._runningCommands.has(commandTimeoutResolver.originalPromise)).toBe(false); // After a command finishes successfully, it's promise is removed from the instance._runningCommands queue. }, 0); }); @@ -330,11 +331,11 @@ describe('STORAGE Redis Adapter', () => { // Call the method. // Note that there are no commands on the queue for this first run. instance.disconnect(); - expect(ioredisMock.disconnect.mock.calls.length).toBe(0); // Original method should not be called right away. + expect(ioredisMock.disconnect).not.toBeCalled(); // Original method should not be called right away. setTimeout(() => { // o queued commands timeout wrapper. - expect(loggerMock.debug.mock.calls).toEqual([['No commands pending execution, disconnect.']]); - expect(ioredisMock.disconnect.mock.calls.length).toBe(1); // Original method should have been called once, asynchronously + expect(loggerMock.debug.mock.calls).toEqual([[logPrefix + 'No commands pending execution, disconnect.']]); + expect(ioredisMock.disconnect).toBeCalledTimes(1); // Original method should have been called once, asynchronously loggerMock.debug.mockClear(); ioredisMock.disconnect.mockClear(); @@ -346,12 +347,12 @@ describe('STORAGE Redis Adapter', () => { rejectedPromise.catch(() => { }); // Swallow the unhandled to avoid unhandledRejection warns setTimeout(() => { // queued with rejection timeout wrapper - expect(loggerMock.info.mock.calls).toEqual([['Attempting to disconnect but there are 2 commands still waiting for resolution. Defering disconnection until those finish.']]); + expect(loggerMock.info.mock.calls).toEqual([[logPrefix + 'Attempting to disconnect but there are 2 commands still waiting for resolution. Defering disconnection until those finish.']]); Promise.all(setToArray(instance._runningCommands)).catch(e => { setImmediate(() => { // Allow the callback to execute before checking. - expect(loggerMock.warn.mock.calls[0]).toEqual([`Pending commands finished with error: ${e}. Proceeding with disconnection.`]); // Should warn about the error but tell user that will disconnect anyways. - expect(ioredisMock.disconnect.mock.calls.length).toBe(1); // Original method should have been called once, asynchronously + expect(loggerMock.warn.mock.calls[0]).toEqual([`${logPrefix}Pending commands finished with error: ${e}. Proceeding with disconnection.`]); // Should warn about the error but tell user that will disconnect anyways. + expect(ioredisMock.disconnect).toBeCalledTimes(1); // Original method should have been called once, asynchronously loggerMock.info.mockClear(); loggerMock.warn.mockClear(); @@ -366,12 +367,12 @@ describe('STORAGE Redis Adapter', () => { instance._runningCommands.add(Promise.resolve()); setTimeout(() => { - expect(loggerMock.info.mock.calls).toEqual([['Attempting to disconnect but there are 4 commands still waiting for resolution. Defering disconnection until those finish.']]); + expect(loggerMock.info.mock.calls).toEqual([[logPrefix + 'Attempting to disconnect but there are 4 commands still waiting for resolution. Defering disconnection until those finish.']]); Promise.all(setToArray(instance._runningCommands)).then(() => { // This one will go through success path setImmediate(() => { - expect(loggerMock.debug.mock.calls).toEqual([['Pending commands finished successfully, disconnecting.']]); - expect(ioredisMock.disconnect.mock.calls.length).toBe(1); // Original method should have been called once, asynchronously + expect(loggerMock.debug.mock.calls).toEqual([[logPrefix + 'Pending commands finished successfully, disconnecting.']]); + expect(ioredisMock.disconnect).toBeCalledTimes(1); // Original method should have been called once, asynchronously }); }); }, 10); diff --git a/src/sync/offline/splitsParser/splitsParserFromFile.ts b/src/sync/offline/splitsParser/splitsParserFromFile.ts index 71dcbaec..b56052c6 100644 --- a/src/sync/offline/splitsParser/splitsParserFromFile.ts +++ b/src/sync/offline/splitsParser/splitsParserFromFile.ts @@ -9,7 +9,8 @@ import parseCondition, { IMockSplitEntry } from './parseCondition'; import { ISplitPartial } from '../../../dtos/types'; import { SplitIO } from '../../../types'; import { ILogger } from '../../../logger/types'; -import { DEBUG_34, DEBUG_35, ERROR_6, WARN_3 } from '../../../logger/constants'; + +const logPrefix = 'sync:offline:splits-fetcher: '; type IYamlSplitEntry = Record @@ -59,12 +60,12 @@ function readSplitConfigFile(log: ILogger, filePath: SplitIO.MockedFeaturesFileP let tuple: string | string[] = line.trim(); if (tuple === '' || tuple.charAt(0) === '#') { - log.debug(DEBUG_34, [index]); + log.debug(logPrefix + `Ignoring empty line or comment at #${index}`); } else { tuple = tuple.split(/\s+/); if (tuple.length !== 2) { - log.debug(DEBUG_35, [index]); + log.debug(logPrefix + `Ignoring line since it does not have exactly two columns #${index}`); } else { const splitName = tuple[SPLIT_POSITION]; const condition = parseCondition({ treatment: tuple[TREATMENT_POSITION] }); @@ -101,7 +102,7 @@ function readYAMLConfigFile(log: ILogger, filePath: SplitIO.MockedFeaturesFilePa const splitName = Object.keys(splitEntry)[0]; if (!splitName || !isString(splitEntry[splitName].treatment)) - log.error(ERROR_6); + log.error(logPrefix + 'Ignoring entry on YAML since the format is incorrect.'); const mockData = splitEntry[splitName]; @@ -166,7 +167,7 @@ export default function splitsParserFromFile({ features, log }: { features?: Spl // If we have a filePath, it means the extension is correct, choose the parser. if (endsWith(filePath, '.split')) { - log.warn(WARN_3); + log.warn(logPrefix + '.split mocks will be deprecated soon in favor of YAML files, which provide more targeting power. Take a look in our documentation.'); mockData = readSplitConfigFile(log, filePath); } else { mockData = readYAMLConfigFile(log, filePath); diff --git a/src/sync/offline/syncTasks/fromObjectSyncTask.ts b/src/sync/offline/syncTasks/fromObjectSyncTask.ts index 0ffb2f5a..a3ad3273 100644 --- a/src/sync/offline/syncTasks/fromObjectSyncTask.ts +++ b/src/sync/offline/syncTasks/fromObjectSyncTask.ts @@ -34,8 +34,7 @@ export function fromObjectUpdaterFactory( } if (!loadError && splitsMock) { - log.debug(DEBUG_36); - log.debug(JSON.stringify(splitsMock)); + log.debug(DEBUG_36, [JSON.stringify(splitsMock)]); forOwn(splitsMock, function (val, name) { splits.push([ diff --git a/src/sync/polling/pollingManagerSS.ts b/src/sync/polling/pollingManagerSS.ts index 587e25cc..f6cf4396 100644 --- a/src/sync/polling/pollingManagerSS.ts +++ b/src/sync/polling/pollingManagerSS.ts @@ -6,8 +6,7 @@ import { ISplitApi } from '../../services/types'; import { ISettings } from '../../types'; import { IPollingManager, ISegmentsSyncTask, ISplitsSyncTask } from './types'; import thenable from '../../utils/promise/thenable'; -import { INFO_8, INFO_9 } from '../../logger/constants'; -import { DEBUG_37, DEBUG_38 } from '../../logger/constants'; +import { INFO_8, INFO_9, SYNC_POLLING_LB } from '../../logger/constants'; /** * Expose start / stop mechanism for pulling data from services. @@ -31,8 +30,8 @@ export default function pollingManagerSSFactory( // Start periodic fetching (polling) start() { log.info(INFO_8); - log.debug(DEBUG_37, [settings.scheduler.featuresRefreshRate]); // @TODO remove since we already log it in syncTask debug log? - log.debug(DEBUG_38, [settings.scheduler.segmentsRefreshRate]); // @TODO remove since we already log it in syncTask debug log? + log.debug(SYNC_POLLING_LB + `Splits will be refreshed each ${settings.scheduler.featuresRefreshRate} millis`); + log.debug(SYNC_POLLING_LB + `Segments will be refreshed each ${settings.scheduler.segmentsRefreshRate} millis`); const startingUp = splitsSyncTask.start(); if (thenable(startingUp)) { diff --git a/src/sync/polling/syncTasks/segmentsSyncTask.ts b/src/sync/polling/syncTasks/segmentsSyncTask.ts index 676d67f2..bf131d5c 100644 --- a/src/sync/polling/syncTasks/segmentsSyncTask.ts +++ b/src/sync/polling/syncTasks/segmentsSyncTask.ts @@ -11,7 +11,7 @@ import { IFetchSegmentChanges } from '../../../services/types'; import { ISettings } from '../../../types'; import { SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants'; import { ILogger } from '../../../logger/types'; -import { DEBUG_41, DEBUG_40, DEBUG_39, ERROR_8 } from '../../../logger/constants'; +import { INSTANTIATION_LB, SYNC_SEGMENTS_LB } from '../../../logger/constants'; type ISegmentChangesUpdater = (segmentNames?: string[], noCache?: boolean, fetchOnlyNew?: boolean) => Promise @@ -48,7 +48,7 @@ function segmentChangesUpdaterFactory( * This param is used by SplitUpdateWorker on server-side SDK, to fetch new registered segments on SPLIT_UPDATE notifications. */ return function segmentChangesUpdater(segmentNames?: string[], noCache?: boolean, fetchOnlyNew?: boolean) { - log.debug(DEBUG_41); + log.debug(SYNC_SEGMENTS_LB + 'Started segments update'); // If not a segment name provided, read list of available segments names to be updated. let segments = segmentNames ? segmentNames : segmentsCache.getRegisteredSegments(); @@ -61,7 +61,7 @@ function segmentChangesUpdaterFactory( const segmentName = segments[index]; const since = segmentsCache.getChangeNumber(segmentName); - log.debug(DEBUG_40, [segmentName]); + log.debug(SYNC_SEGMENTS_LB + `Processing segment ${segmentName}`); updaters.push(segmentChangesFetcher(since, segmentName, noCache, _promiseDecorator).then(function (changes) { let changeNumber = -1; @@ -73,7 +73,7 @@ function segmentChangesUpdaterFactory( changeNumber = x.till; } - log.debug(DEBUG_39, [segmentName, x.till, x.added.length, x.removed.length]); + log.debug(SYNC_SEGMENTS_LB + `Processed ${segmentName} with till = ${x.till}. Added: ${x.added.length}. Removed: ${x.removed.length}`); }); return changeNumber; @@ -97,7 +97,7 @@ function segmentChangesUpdaterFactory( if (error.statusCode === 403) { // @TODO although factory status is destroyed, synchronization is not stopped readiness.destroy(); - log.error(ERROR_8); + log.error(INSTANTIATION_LB + ': you passed a client-side type authorizationKey, please grab an Api Key from the Split web console that is of type Server-side.'); } return false; diff --git a/src/sync/syncManagerOnline.ts b/src/sync/syncManagerOnline.ts index 6831e4bd..4a0a0099 100644 --- a/src/sync/syncManagerOnline.ts +++ b/src/sync/syncManagerOnline.ts @@ -9,8 +9,6 @@ import { IPushManagerFactoryParams, IPushManager, IPushManagerCS } from './strea import { IPollingManager, IPollingManagerCS, IPollingManagerFactoryParams } from './polling/types'; import { PUSH_SUBSYSTEM_UP, PUSH_SUBSYSTEM_DOWN } from './streaming/constants'; import { INFO_18, INFO_19, INFO_20 } from '../logger/constants'; -// import { logFactory } from '../logger/sdkLogger'; -// export const log = logFactory('splitio-sync:sync-manager'); /** * Online SyncManager factory. diff --git a/src/utils/inputValidation/__tests__/apiKey.spec.ts b/src/utils/inputValidation/__tests__/apiKey.spec.ts index 23d75893..c1cbb25d 100644 --- a/src/utils/inputValidation/__tests__/apiKey.spec.ts +++ b/src/utils/inputValidation/__tests__/apiKey.spec.ts @@ -1,26 +1,21 @@ +import { ERROR_EMPTY, ERROR_NULL, ERROR_INVALID, WARN_API_KEY } from '../../../logger/constants'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { validateApiKey, validateAndTrackApiKey, releaseApiKey } from '../apiKey'; -const errorMsgs = { - WRONG_TYPE_API_KEY: 'Factory instantiation: you passed an invalid api_key, api_key must be a non-empty string.', - EMPTY_API_KEY: 'Factory instantiation: you passed an empty api_key, api_key must be a non-empty string.', - NULL_API_KEY: 'Factory instantiation: you passed a null or undefined api_key, api_key must be a non-empty string.' -}; - const invalidKeys = [ - { key: '', msg: errorMsgs.EMPTY_API_KEY }, - { key: null, msg: errorMsgs.NULL_API_KEY }, - { key: undefined, msg: errorMsgs.NULL_API_KEY }, - { key: () => { }, msg: errorMsgs.WRONG_TYPE_API_KEY }, - { key: new Promise(r => r()), msg: errorMsgs.WRONG_TYPE_API_KEY }, - { key: Symbol('asd'), msg: errorMsgs.WRONG_TYPE_API_KEY }, - { key: [], msg: errorMsgs.WRONG_TYPE_API_KEY }, - { key: true, msg: errorMsgs.WRONG_TYPE_API_KEY }, - { key: NaN, msg: errorMsgs.WRONG_TYPE_API_KEY }, - { key: Infinity, msg: errorMsgs.WRONG_TYPE_API_KEY }, - { key: -Infinity, msg: errorMsgs.WRONG_TYPE_API_KEY }, - { key: {}, msg: errorMsgs.WRONG_TYPE_API_KEY } + { key: '', msg: ERROR_EMPTY }, + { key: null, msg: ERROR_NULL }, + { key: undefined, msg: ERROR_NULL }, + { key: () => { }, msg: ERROR_INVALID }, + { key: new Promise(r => r()), msg: ERROR_INVALID }, + { key: Symbol('asd'), msg: ERROR_INVALID }, + { key: [], msg: ERROR_INVALID }, + { key: true, msg: ERROR_INVALID }, + { key: NaN, msg: ERROR_INVALID }, + { key: Infinity, msg: ERROR_INVALID }, + { key: -Infinity, msg: ERROR_INVALID }, + { key: {}, msg: ERROR_INVALID } ]; describe('validateApiKey', () => { @@ -31,7 +26,7 @@ describe('validateApiKey', () => { const validApiKey = 'qjok3snti4dgsticade5hfphmlucarsflv14'; expect(validateApiKey(loggerMock, validApiKey)).toBe(validApiKey); // It should return the passed string if it is valid. - expect(loggerMock.error.mock.calls.length).toBe(0); // Should not log any errors. + expect(loggerMock.error).not.toBeCalled(); // Should not log any errors. }); test('Should return false and log error if the api key is invalid', () => { @@ -57,13 +52,11 @@ describe('validateAndTrackApiKey', () => { const validApiKey3 = '84ynbsnti4dgsticade5hfphmlucars92uih'; expect(validateAndTrackApiKey(loggerMock, validApiKey1)).toBe(validApiKey1); - expect(loggerMock.warn.mock.calls.length).toBe(0); // If this is the first api key we are registering, there is no warning. + expect(loggerMock.warn).not.toBeCalled(); // If this is the first api key we are registering, there is no warning. expect(validateAndTrackApiKey(loggerMock, validApiKey2)).toBe(validApiKey2); - expect(loggerMock.warn.mock.calls).toEqual([['Factory instantiation: You already have an instance of the Split factory. Make sure you definitely want this additional instance. We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application.']]); // We register a new api key, we get a warning. - expect(validateAndTrackApiKey(loggerMock, validApiKey3)).toBe(validApiKey3); - expect(loggerMock.warn.mock.calls[0]).toEqual(['Factory instantiation: You already have an instance of the Split factory. Make sure you definitely want this additional instance. We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application.']); // We register a new api key, we get a warning. + expect(loggerMock.warn).toBeCalledWith(WARN_API_KEY, ['an instance of the Split factory']); // we get a warning when we register a new api key. // We will release the used keys and expect no warnings next time. releaseApiKey(validApiKey1); @@ -73,7 +66,7 @@ describe('validateAndTrackApiKey', () => { loggerMock.mockClear(); expect(validateAndTrackApiKey(loggerMock, validApiKey1)).toBe(validApiKey1); - expect(loggerMock.warn.mock.calls.length).toBe(0); // If all the keys were released and we try again, there is no warning. + expect(loggerMock.warn).not.toBeCalled(); // If all the keys were released and we try again, there is no warning. releaseApiKey(validApiKey1); // clean up the cache of api keys for next test }); @@ -82,7 +75,7 @@ describe('validateAndTrackApiKey', () => { const validApiKey = '84ynbsnti4dgsticade5hfphmlucars92uih'; expect(validateAndTrackApiKey(loggerMock, validApiKey)).toBe(validApiKey); - expect(loggerMock.warn.mock.calls.length).toBe(0); // If this is the first api key we are registering, there is no warning. + expect(loggerMock.warn).not.toBeCalled(); // If this is the first api key we are registering, there is no warning. expect(validateAndTrackApiKey(loggerMock, validApiKey)).toBe(validApiKey); @@ -93,10 +86,10 @@ describe('validateAndTrackApiKey', () => { expect(validateAndTrackApiKey(loggerMock, validApiKey)).toBe(validApiKey); expect(loggerMock.warn.mock.calls).toEqual([ - ['Factory instantiation: You already have 1 factory with this API Key. We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application.'], // We register a the same api key again, we get a warning with the number of instances we have. - ['Factory instantiation: You already have 2 factories with this API Key. We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application.'], // We register a the same api key again, we get a warning with the number of instances we have. - ['Factory instantiation: You already have 3 factories with this API Key. We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application.'] // We register a the same api key again, we get a warning with the number of instances we have. - ]); + [WARN_API_KEY, ['1 factory with this API Key']], + [WARN_API_KEY, ['2 factories with this API Key']], + [WARN_API_KEY, ['3 factories with this API Key']] + ]); // We get a warning each time we register the same api key, with the number of instances we have. // We will release the used api key leaving only 1 "use" on the cache. releaseApiKey(validApiKey); @@ -107,7 +100,7 @@ describe('validateAndTrackApiKey', () => { // So we get the warning again. expect(validateAndTrackApiKey(loggerMock, validApiKey)).toBe(validApiKey); - expect(loggerMock.warn.mock.calls).toEqual([['Factory instantiation: You already have 1 factory with this API Key. We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application.']]); // We register a the same api key again, we get a warning with the number of instances we have. + expect(loggerMock.warn).toBeCalledWith(WARN_API_KEY, ['1 factory with this API Key']); // Leave it with 0 releaseApiKey(validApiKey); @@ -116,7 +109,7 @@ describe('validateAndTrackApiKey', () => { loggerMock.mockClear(); expect(validateAndTrackApiKey(loggerMock, validApiKey)).toBe(validApiKey); - expect(loggerMock.warn.mock.calls.length).toBe(0); // s users, there is no warning when we use it again. + expect(loggerMock.warn).not.toBeCalled(); // s users, there is no warning when we use it again. releaseApiKey(validApiKey); // clean up the cache just in case a new test is added }); diff --git a/src/utils/inputValidation/__tests__/attributes.spec.ts b/src/utils/inputValidation/__tests__/attributes.spec.ts index c91310b4..ae7b2c9b 100644 --- a/src/utils/inputValidation/__tests__/attributes.spec.ts +++ b/src/utils/inputValidation/__tests__/attributes.spec.ts @@ -1,3 +1,4 @@ +import { ERROR_NOT_PLAIN_OBJECT } from '../../../logger/constants'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { validateAttributes } from '../attributes'; @@ -40,7 +41,7 @@ describe('INPUT VALIDATION for Attributes', () => { const invalidAttribute = invalidAttributes[i]; expect(validateAttributes(loggerMock, invalidAttribute, 'test_method')).toBe(false); // Invalid attribute objects should return false. - expect(loggerMock.error.mock.calls[0][0]).toEqual('test_method: attributes must be a plain object.'); // The error should be logged for the invalid attributes map. + expect(loggerMock.error).lastCalledWith(ERROR_NOT_PLAIN_OBJECT, ['test_method', 'attributes']); // The error should be logged for the invalid attributes map. loggerMock.error.mockClear(); } diff --git a/src/utils/inputValidation/__tests__/event.spec.ts b/src/utils/inputValidation/__tests__/event.spec.ts index 9abfdff1..2e54136b 100644 --- a/src/utils/inputValidation/__tests__/event.spec.ts +++ b/src/utils/inputValidation/__tests__/event.spec.ts @@ -1,34 +1,28 @@ +import { ERROR_INVALID, ERROR_EMPTY, ERROR_NULL, ERROR_EVENT_TYPE_FORMAT } from '../../../logger/constants'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { validateEvent } from '../event'; -const errorMsgs = { - NULL_EVENT: () => 'you passed a null or undefined event_type, event_type must be a non-empty string.', - WRONG_TYPE_EVENT: () => 'you passed an invalid event_type, event_type must be a non-empty string.', - EMPTY_EVENT: () => 'you passed an empty event_type, event_type must be a non-empty string.', - WRONG_FORMAT_EVENT: (invalidEvent: any) => `you passed "${invalidEvent}", event_type must adhere to the regular expression /^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$/g. This means an event_type must be alphanumeric, cannot be more than 80 characters long, and can only include a dash, underscore, period, or colon as separators of alphanumeric characters.` -}; - const invalidEvents = [ - { event: [], msg: errorMsgs.WRONG_TYPE_EVENT }, - { event: () => { }, msg: errorMsgs.WRONG_TYPE_EVENT }, - { event: false, msg: errorMsgs.WRONG_TYPE_EVENT }, - { event: true, msg: errorMsgs.WRONG_TYPE_EVENT }, - { event: {}, msg: errorMsgs.WRONG_TYPE_EVENT }, - { event: Object.create({}), msg: errorMsgs.WRONG_TYPE_EVENT }, - { event: 'something+withInvalidchars', msg: errorMsgs.WRONG_FORMAT_EVENT }, - { event: 'with spaces', msg: errorMsgs.WRONG_FORMAT_EVENT }, - { event: ' asd', msg: errorMsgs.WRONG_FORMAT_EVENT }, - { event: 'asd ', msg: errorMsgs.WRONG_FORMAT_EVENT }, - { event: '?', msg: errorMsgs.WRONG_FORMAT_EVENT }, - { event: '', msg: errorMsgs.EMPTY_EVENT }, - { event: NaN, msg: errorMsgs.WRONG_TYPE_EVENT }, - { event: -Infinity, msg: errorMsgs.WRONG_TYPE_EVENT }, - { event: Infinity, msg: errorMsgs.WRONG_TYPE_EVENT }, - { event: new Promise(res => res), msg: errorMsgs.WRONG_TYPE_EVENT }, - { event: Symbol('asd'), msg: errorMsgs.WRONG_TYPE_EVENT }, - { event: null, msg: errorMsgs.NULL_EVENT }, - { event: undefined, msg: errorMsgs.NULL_EVENT } + { event: [], msg: ERROR_INVALID }, + { event: () => { }, msg: ERROR_INVALID }, + { event: false, msg: ERROR_INVALID }, + { event: true, msg: ERROR_INVALID }, + { event: {}, msg: ERROR_INVALID }, + { event: Object.create({}), msg: ERROR_INVALID }, + { event: 'something+withInvalidchars', msg: ERROR_EVENT_TYPE_FORMAT }, + { event: 'with spaces', msg: ERROR_EVENT_TYPE_FORMAT }, + { event: ' asd', msg: ERROR_EVENT_TYPE_FORMAT }, + { event: 'asd ', msg: ERROR_EVENT_TYPE_FORMAT }, + { event: '?', msg: ERROR_EVENT_TYPE_FORMAT }, + { event: '', msg: ERROR_EMPTY }, + { event: NaN, msg: ERROR_INVALID }, + { event: -Infinity, msg: ERROR_INVALID }, + { event: Infinity, msg: ERROR_INVALID }, + { event: new Promise(res => res), msg: ERROR_INVALID }, + { event: Symbol('asd'), msg: ERROR_INVALID }, + { event: null, msg: ERROR_NULL }, + { event: undefined, msg: ERROR_NULL } ]; describe('INPUT VALIDATION for Event types', () => { @@ -38,26 +32,26 @@ describe('INPUT VALIDATION for Event types', () => { test('Should return the provided event type if it is a valid string without logging any errors', () => { expect(validateEvent(loggerMock, 'valid:Too', 'some_method_eventType')).toBe('valid:Too'); // It should return the provided string if it is valid. - expect(loggerMock.error.mock.calls.length).toBe(0); // Should not log any errors. + expect(loggerMock.error).not.toBeCalled(); // Should not log any errors. expect(validateEvent(loggerMock, 'I.am.valid-string_ValUe', 'some_method_eventType')).toBe('I.am.valid-string_ValUe'); // It should return the provided string if it is valid. - expect(loggerMock.error.mock.calls.length).toBe(0); // Should not log any errors. + expect(loggerMock.error).not.toBeCalled(); // Should not log any errors. expect(validateEvent(loggerMock, 'a', 'some_method_eventType')).toBe('a'); // It should return the provided string if it is valid. - expect(loggerMock.error.mock.calls.length).toBe(0); // Should not log any errors. + expect(loggerMock.error).not.toBeCalled(); // Should not log any errors. - expect(loggerMock.warn.mock.calls.length).toBe(0); // It should have not logged any warnings. + expect(loggerMock.warn).not.toBeCalled(); // It should have not logged any warnings. }); test('Should return false and log error if event type is not a valid string', () => { for (let i = 0; i < invalidEvents.length; i++) { const invalidValue = invalidEvents[i]['event']; - const expectedLog = invalidEvents[i]['msg'](invalidValue); + const expectedLog = invalidEvents[i]['msg']; expect(validateEvent(loggerMock, invalidValue, 'test_method')).toBe(false); // Invalid event types should always return false. - expect(loggerMock.error.mock.calls[0][0]).toEqual(`test_method: ${expectedLog}`); // Should log the error for the invalid event type. + expect(loggerMock.error).toBeCalledWith(expectedLog, expectedLog === ERROR_EVENT_TYPE_FORMAT ? ['test_method', invalidValue] : ['test_method']); // Should log the error for the invalid event type. loggerMock.error.mockClear(); } - expect(loggerMock.warn.mock.calls.length).toBe(0); // It should have not logged any warnings. + expect(loggerMock.warn).not.toBeCalled(); // It should have not logged any warnings. }); }); diff --git a/src/utils/inputValidation/__tests__/eventProperties.spec.ts b/src/utils/inputValidation/__tests__/eventProperties.spec.ts index 3f2caa9b..d17f08bd 100644 --- a/src/utils/inputValidation/__tests__/eventProperties.spec.ts +++ b/src/utils/inputValidation/__tests__/eventProperties.spec.ts @@ -1,3 +1,4 @@ +import { ERROR_NOT_PLAIN_OBJECT, ERROR_SIZE_EXCEEDED, WARN_SETTING_NULL, WARN_TRIMMING_PROPERTIES } from '../../../logger/constants'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { validateEventProperties } from '../eventProperties'; @@ -48,8 +49,8 @@ describe('INPUT VALIDATION for Event Properties', () => { }); // It should return the passed null since it is valid with default event size.'); - expect(loggerMock.error.mock.calls.length).toBe(0); // Should not log any errors. - expect(loggerMock.warn.mock.calls.length).toBe(0); // It should have not logged any warnings. + expect(loggerMock.error).not.toBeCalled(); // Should not log any errors. + expect(loggerMock.warn).not.toBeCalled(); // It should have not logged any warnings. }); test('When setting a value for properties, only objects are acceptable', () => { @@ -59,12 +60,12 @@ describe('INPUT VALIDATION for Event Properties', () => { properties: false, size: 1024 }); // It should return default size and properties false if the properties value is not an object or null/undefined.'); - expect(loggerMock.error.mock.calls).toEqual([['some_method_eventProps: properties must be a plain object.']]); // Should log an error. + expect(loggerMock.error).toBeCalledWith(ERROR_NOT_PLAIN_OBJECT, ['some_method_eventProps', 'properties']); // Should log an error. loggerMock.error.mockClear(); }); - expect(loggerMock.warn.mock.calls.length).toBe(0); // It should have not logged any warnings. + expect(loggerMock.warn).not.toBeCalled(); // It should have not logged any warnings. }); test('It should return the properties object when valid and also the correct event size', () => { @@ -87,8 +88,8 @@ describe('INPUT VALIDATION for Event Properties', () => { expect(validProperties).not.toBe(output.properties); // Returned properties should be a clone. - expect(loggerMock.error.mock.calls.length).toBe(0); // Should not log any errors. - expect(loggerMock.warn.mock.calls.length).toBe(0); // It should have not logged any warnings. + expect(loggerMock.error).not.toBeCalled(); // Should not log any errors. + expect(loggerMock.warn).not.toBeCalled(); // It should have not logged any warnings. }); test('It should return the properties object when valid and also the correct event size, nulling any invalid prop', () => { @@ -117,11 +118,11 @@ describe('INPUT VALIDATION for Event Properties', () => { expect(providedProperties).not.toBe(output.properties); // Returned properties should be a clone. - expect(loggerMock.error.mock.calls.length).toBe(0); // Should not log any errors. - expect(loggerMock.warn.mock.calls.length).toBe(4); // It should have logged one warning per each property of the invalid type. + expect(loggerMock.error).not.toBeCalled(); // Should not log any errors. + expect(loggerMock.warn).toBeCalledTimes(4); // It should have logged one warning per each property of the invalid type. ['willBeNulled1', 'willBeNulled2', 'willBeNulled3', 'willBeNulled4'].forEach((key, index) => { - expect(loggerMock.warn.mock.calls[index][0]).toBe(`some_method_eventProps: Property ${key} is of invalid type. Setting value to null.`); + expect(loggerMock.warn.mock.calls[index]).toEqual([WARN_SETTING_NULL, ['some_method_eventProps', key]]); }); }); @@ -139,8 +140,8 @@ describe('INPUT VALIDATION for Event Properties', () => { }); // It should return the properties and the event size.'); - expect(loggerMock.error.mock.calls.length).toBe(0); // Should not log any errors. - expect(loggerMock.warn.mock.calls.length).toBe(0); // It should have not logged any warnings. + expect(loggerMock.error).not.toBeCalled(); // Should not log any errors. + expect(loggerMock.warn).not.toBeCalled(); // It should have not logged any warnings. // @ts-ignore validProperties.a = null; // Adding one prop to exceed the limit. @@ -152,8 +153,8 @@ describe('INPUT VALIDATION for Event Properties', () => { }); // It should return the properties and the event size.'); - expect(loggerMock.error.mock.calls.length).toBe(0); // Should not log any errors. - expect(loggerMock.warn.mock.calls).toEqual([['some_method_eventProps: Event has more than 300 properties. Some of them will be trimmed when processed.']]); // It should have logged a warning. + expect(loggerMock.error).not.toBeCalled(); // Should not log any errors. + expect(loggerMock.warn).toBeCalledWith(WARN_TRIMMING_PROPERTIES, ['some_method_eventProps']); // It should have logged a warning. }); const fiveHundredChars = 'JKHSAKFJASHFJKASHSFKHAKJSGJKASGH1234567890JASHGJHASGJKAHSJKGHAJKSHGJKAHGJKASHajksghkjahsgjkhsakjghjkashgjkhagjkhajksghjkahsgjksahgjkahsgjkhasgjkhsagjkabsgjhaenjkrnjkwnqrkjnqwekjrnkjweqntkjnjkenasdjkngjksdajkghkjdasgkjnadsjgn asdkjgnkjsadngkjnasdjkngjknasdkjgnasdlgnsdakgnlkasndugbuoewqoitnwlkgadsgjdnsagubadisugboisdngklasdgndsgbjasdbgjkasbdgubuiqwetoiqhweiojtioweqhtiohqweiohtiowqehtoihewqiobtgoiqwengiowqnegionwqeogiqwneoignqiowegnioqewgnwqoiegnoiqwengiowqnegoinqwgionqwegionwqeoignqwegoinoiadnfaosignoiansgk'; @@ -174,8 +175,8 @@ describe('INPUT VALIDATION for Event Properties', () => { // It should return the properties and the event size.'); expect(validProperties).not.toBe(output.properties); // Returned properties should be a clone. - expect(loggerMock.error.mock.calls.length).toBe(0); // Should not log any errors. - expect(loggerMock.warn.mock.calls.length).toBe(0); // Should not log any warnings. + expect(loggerMock.error).not.toBeCalled(); // Should not log any errors. + expect(loggerMock.warn).not.toBeCalled(); // Should not log any warnings. // @ts-ignore validProperties.a = null; // exceed by two bytes (1 char string key which is two bytes, null value which we count as 0 to match other SDKs) @@ -188,7 +189,7 @@ describe('INPUT VALIDATION for Event Properties', () => { }); // It should return false instead of the properties and the event size.'); - expect(loggerMock.warn.mock.calls.length).toBe(0); // Should not log any warnings. - expect(loggerMock.error.mock.calls).toEqual([['some_method_eventProps: The maximum size allowed for the properties is 32768 bytes, which was exceeded. Event not queued.']]); // Should log an error. + expect(loggerMock.warn).not.toBeCalled(); // Should not log any warnings. + expect(loggerMock.error).toBeCalledWith(ERROR_SIZE_EXCEEDED, ['some_method_eventProps']); // Should log an error. }); }); diff --git a/src/utils/inputValidation/__tests__/eventValue.spec.ts b/src/utils/inputValidation/__tests__/eventValue.spec.ts index b98ca3c0..dc7d0f33 100644 --- a/src/utils/inputValidation/__tests__/eventValue.spec.ts +++ b/src/utils/inputValidation/__tests__/eventValue.spec.ts @@ -1,3 +1,4 @@ +import { ERROR_NOT_FINITE } from '../../../logger/constants'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { validateEventValue } from '../eventValue'; @@ -23,20 +24,20 @@ describe('INPUT VALIDATION for Event Values', () => { test('Should return the passed value if it is a valid finite number without logging any errors', () => { expect(validateEventValue(loggerMock, 50, 'some_method_eventValue')).toBe(50); // It should return the passed number if it is valid. - expect(loggerMock.error.mock.calls.length).toBe(0); // Should not log any errors. + expect(loggerMock.error).not.toBeCalled(); // Should not log any errors. expect(validateEventValue(loggerMock, -50, 'some_method_eventValue')).toBe(-50); // It should return the passed number if it is valid. - expect(loggerMock.error.mock.calls.length).toBe(0); // Should not log any errors. + expect(loggerMock.error).not.toBeCalled(); // Should not log any errors. - expect(loggerMock.warn.mock.calls.length).toBe(0); // It should have not logged any warnings. + expect(loggerMock.warn).not.toBeCalled(); // It should have not logged any warnings. }); test('Should return the passed value if it is a null or undefined (since it is optional) without logging any errors', () => { expect(validateEventValue(loggerMock, null, 'some_method_eventValue')).toBe(null); // It should return the passed number if it is valid. - expect(loggerMock.error.mock.calls.length).toBe(0); // Should not log any errors. + expect(loggerMock.error).not.toBeCalled(); // Should not log any errors. expect(validateEventValue(loggerMock, undefined, 'some_method_eventValue')).toBe(undefined); // It should return the passed number if it is valid. - expect(loggerMock.error.mock.calls.length).toBe(0); // Should not log any errors. + expect(loggerMock.error).not.toBeCalled(); // Should not log any errors. - expect(loggerMock.warn.mock.calls.length).toBe(0); // It should have not logged any warnings. + expect(loggerMock.warn).not.toBeCalled(); // It should have not logged any warnings. }); test('Should return false and log error if event value is not a valid finite number', () => { @@ -44,11 +45,11 @@ describe('INPUT VALIDATION for Event Values', () => { const invalidValue = invalidValues[i]; expect(validateEventValue(loggerMock, invalidValue, 'test_method')).toBe(false); // Invalid event values should always return false. - expect(loggerMock.error.mock.calls[0][0]).toEqual('test_method: value must be a finite number.'); // Should log the error for the invalid event value. + expect(loggerMock.error).toBeCalledWith(ERROR_NOT_FINITE, ['test_method']); // Should log the error for the invalid event value. loggerMock.error.mockClear(); } - expect(loggerMock.warn.mock.calls.length).toBe(0); // It should have not logged any warnings. + expect(loggerMock.warn).not.toBeCalled(); // It should have not logged any warnings. }); }); diff --git a/src/utils/inputValidation/__tests__/isOperational.spec.ts b/src/utils/inputValidation/__tests__/isOperational.spec.ts index e5ab45b8..0d32547a 100644 --- a/src/utils/inputValidation/__tests__/isOperational.spec.ts +++ b/src/utils/inputValidation/__tests__/isOperational.spec.ts @@ -1,3 +1,4 @@ +import { WARN_CLIENT_NOT_READY, ERROR_CLIENT_DESTROYED } from '../../../logger/constants'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { validateIfNotDestroyed, validateIfOperational } from '../isOperational'; @@ -11,19 +12,19 @@ describe('validateIfNotDestroyed', () => { // @ts-ignore expect(validateIfNotDestroyed(loggerMock, readinessManagerMock)).toBe(true); // It should return true if the client is operational (it is NOT destroyed). - expect(readinessManagerMock.isDestroyed.mock.calls.length).toBe(1); // It checks for destroyed status using the context. - expect(loggerMock.error.mock.calls.length).toBe(0); // Should not log any errors. - expect(loggerMock.warn.mock.calls.length).toBe(0); // Should not log any warnings. + expect(readinessManagerMock.isDestroyed).toBeCalledTimes(1); // It checks for destroyed status using the context. + expect(loggerMock.error).not.toBeCalled(); // Should not log any errors. + expect(loggerMock.warn).not.toBeCalled(); // Should not log any warnings. }); test('Should return false and log error if attributes map is invalid', () => { const readinessManagerMock = { isDestroyed: jest.fn(() => true) }; // @ts-ignore - expect(validateIfNotDestroyed(loggerMock, readinessManagerMock)).toBe(false); // It should return false if the client is NOT operational (it is destroyed). - expect(readinessManagerMock.isDestroyed.mock.calls.length).toBe(1); // It checks for destroyed status using the context. - expect(loggerMock.error.mock.calls).toEqual([['Client has already been destroyed - no calls possible.']]); // Should log an error. - expect(loggerMock.warn.mock.calls.length).toBe(0); // But it should not log any warnings. + expect(validateIfNotDestroyed(loggerMock, readinessManagerMock, 'test_method')).toBe(false); // It should return false if the client is NOT operational (it is destroyed). + expect(readinessManagerMock.isDestroyed).toBeCalledTimes(1); // It checks for destroyed status using the context. + expect(loggerMock.error).toBeCalledWith(ERROR_CLIENT_DESTROYED, ['test_method']); // Should log an error. + expect(loggerMock.warn).not.toBeCalled(); // But it should not log any warnings. }); }); @@ -34,9 +35,9 @@ describe('validateIfOperational', () => { // @ts-ignore expect(validateIfOperational(loggerMock, readinessManagerMock, 'test_method')).toBe(true); // It should return true if SDK was ready. - expect(readinessManagerMock.isReady.mock.calls.length).toBe(1); // It checks for readiness status using the context. - expect(loggerMock.warn.mock.calls.length).toBe(0); // But it should not log any warnings. - expect(loggerMock.error.mock.calls.length).toBe(0); // But it should not log any errors. + expect(readinessManagerMock.isReady).toBeCalledTimes(1); // It checks for readiness status using the context. + expect(loggerMock.warn).not.toBeCalled(); // But it should not log any warnings. + expect(loggerMock.error).not.toBeCalled(); // But it should not log any errors. }); test('Should return true and log nothing if the SDK was ready from cache.', () => { @@ -44,10 +45,10 @@ describe('validateIfOperational', () => { // @ts-ignore expect(validateIfOperational(loggerMock, readinessManagerMock, 'test_method')).toBe(true); // It should return true if SDK was ready. - expect(readinessManagerMock.isReady.mock.calls.length).toBe(1); // It checks for SDK_READY status. - expect(readinessManagerMock.isReadyFromCache.mock.calls.length).toBe(1); // It checks for SDK_READY_FROM_CACHE status. - expect(loggerMock.warn.mock.calls.length).toBe(0); // But it should not log any warnings. - expect(loggerMock.error.mock.calls.length).toBe(0); // But it should not log any errors. + expect(readinessManagerMock.isReady).toBeCalledTimes(1); // It checks for SDK_READY status. + expect(readinessManagerMock.isReadyFromCache).toBeCalledTimes(1); // It checks for SDK_READY_FROM_CACHE status. + expect(loggerMock.warn).not.toBeCalled(); // But it should not log any warnings. + expect(loggerMock.error).not.toBeCalled(); // But it should not log any errors. }); test('Should return false and log a warning if the SDK was not ready.', () => { @@ -55,9 +56,9 @@ describe('validateIfOperational', () => { // @ts-ignore expect(validateIfOperational(loggerMock, readinessManagerMock, 'test_method')).toBe(false); // It should return true if SDK was ready. - expect(readinessManagerMock.isReady.mock.calls.length).toBe(1); // It checks for SDK_READY status. - expect(readinessManagerMock.isReadyFromCache.mock.calls.length).toBe(1); // It checks for SDK_READY_FROM_CACHE status. - expect(loggerMock.warn.mock.calls).toEqual([['test_method: the SDK is not ready, results may be incorrect. Make sure to wait for SDK readiness before using this method.']]); // It should log the expected warning. - expect(loggerMock.error.mock.calls.length).toBe(0); // But it should not log any errors. + expect(readinessManagerMock.isReady).toBeCalledTimes(1); // It checks for SDK_READY status. + expect(readinessManagerMock.isReadyFromCache).toBeCalledTimes(1); // It checks for SDK_READY_FROM_CACHE status. + expect(loggerMock.warn).toBeCalledWith(WARN_CLIENT_NOT_READY, ['test_method']); // It should log the expected warning. + expect(loggerMock.error).not.toBeCalled(); // But it should not log any errors. }); }); diff --git a/src/utils/inputValidation/__tests__/key.spec.ts b/src/utils/inputValidation/__tests__/key.spec.ts index 62600870..74bdb62e 100644 --- a/src/utils/inputValidation/__tests__/key.spec.ts +++ b/src/utils/inputValidation/__tests__/key.spec.ts @@ -1,35 +1,27 @@ +import { ERROR_EMPTY, ERROR_INVALID, ERROR_INVALID_KEY_OBJECT, ERROR_NULL, ERROR_TOO_LONG, WARN_CONVERTING } from '../../../logger/constants'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { validateKey } from '../key'; -const errorMsgs = { - EMPTY_KEY: (keyType: string) => `you passed an empty string, ${keyType} must be a non-empty string.`, - LONG_KEY: (keyType: string) => `${keyType} too long, ${keyType} must be 250 characters or less.`, - NULL_KEY: (keyType: string) => `you passed a null or undefined ${keyType}, ${keyType} must be a non-empty string.`, - WRONG_TYPE_KEY: (keyType: string) => `you passed an invalid ${keyType} type, ${keyType} must be a non-empty string.`, - NUMERIC_KEY: (keyType: string, value: any) => `${keyType} "${value}" is not of type string, converting.`, - WRONG_KEY_PROPS: 'Key must be an object with bucketingKey and matchingKey with valid string properties.' -}; - const invalidKeys = [ - { key: '', msg: errorMsgs.EMPTY_KEY }, - { key: 'a'.repeat(251), msg: errorMsgs.LONG_KEY }, - { key: null, msg: errorMsgs.NULL_KEY }, - { key: undefined, msg: errorMsgs.NULL_KEY }, - { key: () => { }, msg: errorMsgs.WRONG_TYPE_KEY }, - { key: new Promise(r => r()), msg: errorMsgs.WRONG_TYPE_KEY }, - { key: Symbol('asd'), msg: errorMsgs.WRONG_TYPE_KEY }, - { key: [], msg: errorMsgs.WRONG_TYPE_KEY }, - { key: true, msg: errorMsgs.WRONG_TYPE_KEY }, - { key: NaN, msg: errorMsgs.WRONG_TYPE_KEY }, - { key: Infinity, msg: errorMsgs.WRONG_TYPE_KEY }, - { key: -Infinity, msg: errorMsgs.WRONG_TYPE_KEY }, + { key: '', msg: ERROR_EMPTY }, + { key: 'a'.repeat(251), msg: ERROR_TOO_LONG }, + { key: null, msg: ERROR_NULL }, + { key: undefined, msg: ERROR_NULL }, + { key: () => { }, msg: ERROR_INVALID }, + { key: new Promise(r => r()), msg: ERROR_INVALID }, + { key: Symbol('asd'), msg: ERROR_INVALID }, + { key: [], msg: ERROR_INVALID }, + { key: true, msg: ERROR_INVALID }, + { key: NaN, msg: ERROR_INVALID }, + { key: Infinity, msg: ERROR_INVALID }, + { key: -Infinity, msg: ERROR_INVALID }, ]; const stringifyableKeys = [ - { key: 200, msg: errorMsgs.NUMERIC_KEY }, - { key: 1235891238571295, msg: errorMsgs.NUMERIC_KEY }, - { key: 0, msg: errorMsgs.NUMERIC_KEY } + { key: 200, msg: WARN_CONVERTING }, + { key: 1235891238571295, msg: WARN_CONVERTING }, + { key: 0, msg: WARN_CONVERTING } ]; const invalidKeyObjects = [ @@ -51,52 +43,52 @@ describe('INPUT VALIDATION for Key', () => { }; expect(validateKey(loggerMock, validKey, 'some_method_keys')).toEqual(validKey); // It will return the valid key. - expect(loggerMock.error.mock.calls.length).toBe(0); // No errors should be logged. + expect(loggerMock.error).not.toBeCalled(); // No errors should be logged. expect(validateKey(loggerMock, validObjKey, 'some_method_keys')).toEqual(validObjKey); // It will return the valid key. - expect(loggerMock.error.mock.calls.length).toBe(0); // No errors should be logged. + expect(loggerMock.error).not.toBeCalled(); // No errors should be logged. - expect(loggerMock.warn.mock.calls.length).toBe(0); // It should have not logged any warnings. + expect(loggerMock.warn).not.toBeCalled(); // It should have not logged any warnings. }); test('String key / Should return false and log error if key is invalid', () => { for (let i = 0; i < invalidKeys.length; i++) { const invalidKey = invalidKeys[i]['key']; - const expectedLog = invalidKeys[i]['msg']('key'); + const expectedLog = invalidKeys[i]['msg']; expect(validateKey(loggerMock, invalidKey, 'test_method')).toBe(false); // Invalid keys should return false. - expect(loggerMock.error.mock.calls[0][0]).toEqual(`test_method: ${expectedLog}`); // The error should be logged for the invalid key. + expect(loggerMock.error).toBeCalledWith(expectedLog, ['test_method', 'key']); // The error should be logged for the invalid key. loggerMock.error.mockClear(); } - expect(loggerMock.warn.mock.calls.length).toBe(0); // It should have not logged any warnings. + expect(loggerMock.warn).not.toBeCalled(); // It should have not logged any warnings. }); test('String key / Should return stringified version of the key if it is convertible to one and log a warning.', () => { for (let i = 0; i < stringifyableKeys.length; i++) { const invalidKey = stringifyableKeys[i]['key']; - const expectedLog = stringifyableKeys[i]['msg']('key', invalidKey); + const expectedLog = stringifyableKeys[i]['msg']; validateKey(loggerMock, invalidKey, 'test_method'); - expect(loggerMock.warn.mock.calls[0][0]).toEqual(`test_method: ${expectedLog}`); // But if the logger allows for warnings, it should be logged. + expect(loggerMock.warn).toBeCalledWith(expectedLog, ['test_method', 'key', invalidKey]); // But if the logger allows for warnings, it should be logged. loggerMock.warn.mockClear(); } - expect(loggerMock.error.mock.calls.length).toBe(0); // It should have not logged any errors. + expect(loggerMock.error).not.toBeCalled(); // It should have not logged any errors. }); test('Object key / Should return false and log error if a part of the key is invalid', () => { // Test invalid object format for (let i = 0; i < invalidKeyObjects.length; i++) { expect(validateKey(loggerMock, invalidKeyObjects[i], 'test_method')).toBe(false); // Invalid key objects should return false. - expect(loggerMock.error.mock.calls[loggerMock.error.mock.calls.length - 1][0]).toEqual(`test_method: ${errorMsgs.WRONG_KEY_PROPS}`); // The error should be logged for the invalid key. + expect(loggerMock.error).lastCalledWith(ERROR_INVALID_KEY_OBJECT, ['test_method']); // The error should be logged for the invalid key. loggerMock.error.mockClear(); } - expect(loggerMock.warn.mock.calls.length).toBe(0); // It should have not logged any warnings. + expect(loggerMock.warn).not.toBeCalled(); // It should have not logged any warnings. loggerMock.mockClear(); // Test invalid matchingKey @@ -105,15 +97,15 @@ describe('INPUT VALIDATION for Key', () => { matchingKey: invalidKeys[i]['key'], bucketingKey: 'thisIsValid' }; - const expectedLog = invalidKeys[i]['msg']('matchingKey'); + const expectedLog = invalidKeys[i]['msg']; expect(validateKey(loggerMock, invalidKey, 'test_method')).toBe(false); // Invalid keys should return false. - expect(loggerMock.error.mock.calls[0][0]).toEqual(`test_method: ${expectedLog}`); // The error should be logged for the invalid key. + expect(loggerMock.error).toBeCalledWith(expectedLog, ['test_method', 'matchingKey']); // The error should be logged for the invalid key. loggerMock.error.mockClear(); } - expect(loggerMock.warn.mock.calls.length).toBe(0); // It should have not logged any warnings. + expect(loggerMock.warn).not.toBeCalled(); // It should have not logged any warnings. loggerMock.mockClear(); @@ -123,15 +115,15 @@ describe('INPUT VALIDATION for Key', () => { matchingKey: 'thisIsValidToo', bucketingKey: invalidKeys[i]['key'] }; - const expectedLog = invalidKeys[i]['msg']('bucketingKey'); + const expectedLog = invalidKeys[i]['msg']; expect(validateKey(loggerMock, invalidKey, 'test_method')).toBe(false); // Invalid keys should return false. - expect(loggerMock.error.mock.calls[0][0]).toEqual(`test_method: ${expectedLog}`); // The error should be logged for the invalid key. + expect(loggerMock.error).toBeCalledWith(expectedLog, ['test_method', 'bucketingKey']); // The error should be logged for the invalid key. loggerMock.error.mockClear(); } - expect(loggerMock.warn.mock.calls.length).toBe(0); // It should have not logged any warnings. + expect(loggerMock.warn).not.toBeCalled(); // It should have not logged any warnings. loggerMock.mockClear(); @@ -140,14 +132,14 @@ describe('INPUT VALIDATION for Key', () => { matchingKey: invalidKeys[0]['key'], bucketingKey: invalidKeys[1]['key'] }; - let expectedLogMK = invalidKeys[0]['msg']('matchingKey'); - let expectedLogBK = invalidKeys[1]['msg']('bucketingKey'); + let expectedLogMK = invalidKeys[0]['msg']; + let expectedLogBK = invalidKeys[1]['msg']; expect(validateKey(loggerMock, invalidKey, 'test_method')).toBe(false); // Invalid keys should return false. - expect(loggerMock.error.mock.calls[0][0]).toEqual(`test_method: ${expectedLogMK}`); // The error should be logged for the invalid key property. - expect(loggerMock.error.mock.calls[1][0]).toEqual(`test_method: ${expectedLogBK}`); // The error should be logged for the invalid key property. + expect(loggerMock.error).nthCalledWith(1, expectedLogMK, ['test_method', 'matchingKey']); // The error should be logged for the invalid key property. + expect(loggerMock.error).nthCalledWith(2, expectedLogBK, ['test_method', 'bucketingKey']); // The error should be logged for the invalid key property. - expect(loggerMock.warn.mock.calls.length).toBe(0); // It should have not logged any warnings. + expect(loggerMock.warn).not.toBeCalled(); // It should have not logged any warnings. }); test('Object key / Should return stringified version of the key props if those are convertible and log the corresponding warnings', () => { @@ -159,13 +151,13 @@ describe('INPUT VALIDATION for Key', () => { matchingKey: String(invalidKey.matchingKey), bucketingKey: String(invalidKey.bucketingKey), }; - let expectedLogMK = stringifyableKeys[0]['msg']('matchingKey', invalidKey.matchingKey); - let expectedLogBK = stringifyableKeys[1]['msg']('bucketingKey', invalidKey.bucketingKey); + let expectedLogMK = stringifyableKeys[0]['msg']; + let expectedLogBK = stringifyableKeys[1]['msg']; expect(validateKey(loggerMock, invalidKey, 'test_method')).toEqual(expectedKey); // If a key object had stringifyable values, those will be stringified and Key returned. - expect(loggerMock.warn.mock.calls[0][0]).toEqual(`test_method: ${expectedLogMK}`); // The warning should be logged for the stringified prop if warnings are enabled. - expect(loggerMock.warn.mock.calls[1][0]).toEqual(`test_method: ${expectedLogBK}`); // The warning should be logged for the stringified prop if warnings are enabled. + expect(loggerMock.warn).nthCalledWith(1, expectedLogMK, ['test_method', 'matchingKey', invalidKey.matchingKey]); // The warning should be logged for the stringified prop if warnings are enabled. + expect(loggerMock.warn).nthCalledWith(2, expectedLogBK, ['test_method', 'bucketingKey', invalidKey.bucketingKey]); // The warning should be logged for the stringified prop if warnings are enabled. - expect(loggerMock.error.mock.calls.length).toBe(0); // It should have not logged any errors. + expect(loggerMock.error).not.toBeCalled(); // It should have not logged any errors. }); }); diff --git a/src/utils/inputValidation/__tests__/preloadedData.spec.ts b/src/utils/inputValidation/__tests__/preloadedData.spec.ts index 248aaf12..381cba4a 100644 --- a/src/utils/inputValidation/__tests__/preloadedData.spec.ts +++ b/src/utils/inputValidation/__tests__/preloadedData.spec.ts @@ -144,14 +144,14 @@ test('INPUT VALIDATION for preloadedData', () => { expect(loggerMock.error.mock.calls[0]).toEqual([testCase.error]); // Should log the error for the invalid preloadedData. loggerMock.error.mockClear(); } else { - expect(loggerMock.error.mock.calls.length === 0).toBe(true); // Should not log any error. + expect(loggerMock.error).not.toBeCalled(); // Should not log any error. } if (testCase.warn) { expect(loggerMock.warn.mock.calls[0]).toEqual([testCase.warn]); // Should log the warning for the given preloadedData. loggerMock.warn.mockClear(); } else { - expect(loggerMock.warn.mock.calls.length === 0).toBe(true); // Should not log any warning. + expect(loggerMock.warn).not.toBeCalled(); // Should not log any warning. } } }); diff --git a/src/utils/inputValidation/__tests__/split.spec.ts b/src/utils/inputValidation/__tests__/split.spec.ts index 1a6417b9..13ccfaa3 100644 --- a/src/utils/inputValidation/__tests__/split.spec.ts +++ b/src/utils/inputValidation/__tests__/split.spec.ts @@ -1,30 +1,24 @@ +import { ERROR_INVALID, ERROR_NULL, ERROR_EMPTY, WARN_TRIMMING } from '../../../logger/constants'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { validateSplit } from '../split'; -const errorMsgs = { - NULL_SPLIT: () => 'you passed a null or undefined split name, split name must be a non-empty string.', - WRONG_TYPE_SPLIT: () => 'you passed an invalid split name, split name must be a non-empty string.', - EMPTY_SPLIT: () => 'you passed an empty split name, split name must be a non-empty string.', - TRIMMABLE_SPLIT: (splitName: string) => `split name "${splitName}" has extra whitespace, trimming.` -}; - const invalidSplits = [ - { split: [], msg: errorMsgs.WRONG_TYPE_SPLIT }, - { split: () => { }, msg: errorMsgs.WRONG_TYPE_SPLIT }, - { split: Object.create({}), msg: errorMsgs.WRONG_TYPE_SPLIT }, - { split: {}, msg: errorMsgs.WRONG_TYPE_SPLIT }, - { split: true, msg: errorMsgs.WRONG_TYPE_SPLIT }, - { split: false, msg: errorMsgs.WRONG_TYPE_SPLIT }, - { split: 10, msg: errorMsgs.WRONG_TYPE_SPLIT }, - { split: 0, msg: errorMsgs.WRONG_TYPE_SPLIT }, - { split: NaN, msg: errorMsgs.WRONG_TYPE_SPLIT }, - { split: Infinity, msg: errorMsgs.WRONG_TYPE_SPLIT }, - { split: null, msg: errorMsgs.NULL_SPLIT }, - { split: undefined, msg: errorMsgs.NULL_SPLIT }, - { split: new Promise(res => res), msg: errorMsgs.WRONG_TYPE_SPLIT }, - { split: Symbol('asd'), msg: errorMsgs.WRONG_TYPE_SPLIT }, - { split: '', msg: errorMsgs.EMPTY_SPLIT } + { split: [], msg: ERROR_INVALID }, + { split: () => { }, msg: ERROR_INVALID }, + { split: Object.create({}), msg: ERROR_INVALID }, + { split: {}, msg: ERROR_INVALID }, + { split: true, msg: ERROR_INVALID }, + { split: false, msg: ERROR_INVALID }, + { split: 10, msg: ERROR_INVALID }, + { split: 0, msg: ERROR_INVALID }, + { split: NaN, msg: ERROR_INVALID }, + { split: Infinity, msg: ERROR_INVALID }, + { split: null, msg: ERROR_NULL }, + { split: undefined, msg: ERROR_NULL }, + { split: new Promise(res => res), msg: ERROR_INVALID }, + { split: Symbol('asd'), msg: ERROR_INVALID }, + { split: '', msg: ERROR_EMPTY } ]; const trimmableSplits = [ @@ -52,7 +46,7 @@ describe('INPUT VALIDATION for Split name', () => { for (let i = 0; i < trimmableSplits.length; i++) { const trimmableSplit = trimmableSplits[i]; expect(validateSplit(loggerMock, trimmableSplit, 'some_method_splitName')).toBe(trimmableSplit.trim()); // It should return the trimmed version of the split name received. - expect(loggerMock.warn.mock.calls[0][0]).toEqual(`some_method_splitName: ${errorMsgs.TRIMMABLE_SPLIT(trimmableSplit)}`); // Should log a warning if those are enabled. + expect(loggerMock.warn).toBeCalledWith(WARN_TRIMMING, ['some_method_splitName', 'split name', trimmableSplit]); // Should log a warning if those are enabled. loggerMock.warn.mockClear(); } @@ -64,10 +58,10 @@ describe('INPUT VALIDATION for Split name', () => { for (let i = 0; i < invalidSplits.length; i++) { const invalidValue = invalidSplits[i]['split']; // @ts-ignore - const expectedLog = invalidSplits[i]['msg'](invalidValue); + const expectedLog = invalidSplits[i]['msg']; expect(validateSplit(loggerMock, invalidValue, 'test_method')).toBe(false); // Invalid event types should always return false. - expect(loggerMock.error.mock.calls[0][0]).toEqual(`test_method: ${expectedLog}`); // Should log the error for the invalid event type. + expect(loggerMock.error).toBeCalledWith(expectedLog, ['test_method', 'split name']); // Should log the error for the invalid event type. loggerMock.error.mockClear(); } diff --git a/src/utils/inputValidation/__tests__/splitExistance.spec.ts b/src/utils/inputValidation/__tests__/splitExistance.spec.ts index ae1cfd5f..6b72ad87 100644 --- a/src/utils/inputValidation/__tests__/splitExistance.spec.ts +++ b/src/utils/inputValidation/__tests__/splitExistance.spec.ts @@ -5,10 +5,7 @@ import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { validateSplitExistance } from '../splitExistance'; import { IReadinessManager } from '../../../readiness/types'; - -const errorMsgs = { - NOT_EXISTENT_SPLIT: (splitName: string) => `you passed "${splitName}" that does not exist in this environment, please double check what Splits exist in the web console.` -}; +import { WARN_NOT_EXISTENT_SPLIT } from '../../../logger/constants'; describe('Split existance (special case)', () => { @@ -43,7 +40,7 @@ describe('Split existance (special case)', () => { expect(validateSplitExistance(loggerMock, readinessManagerMock, 'other_split', LabelConstants.SPLIT_NOT_FOUND, 'other_method')).toBe(false); // Should return false if it receives a label but it is the split not found one. expect(loggerMock.warn.mock.calls.length).toBe(3); // It should have logged 3 warnings, one per each time we called it - loggerMock.warn.mock.calls.forEach(call => expect(call[0]).toBe(`other_method: ${errorMsgs.NOT_EXISTENT_SPLIT('other_split')}`)); // Warning logs should have the correct message. + loggerMock.warn.mock.calls.forEach(call => expect(call).toEqual([WARN_NOT_EXISTENT_SPLIT, ['other_method', 'other_split']])); // Warning logs should have the correct message. expect(loggerMock.error.mock.calls.length).toBe(0); // We log warnings, not errors. }); diff --git a/src/utils/inputValidation/__tests__/splits.spec.ts b/src/utils/inputValidation/__tests__/splits.spec.ts index 1bbe14d0..a4d51b71 100644 --- a/src/utils/inputValidation/__tests__/splits.spec.ts +++ b/src/utils/inputValidation/__tests__/splits.spec.ts @@ -3,6 +3,7 @@ import startsWith from 'lodash/startsWith'; // mocks sdkLogger import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; +import { ERROR_EMPTY_ARRAY } from '../../../logger/constants'; // mocks validateSplit jest.mock('../split'); @@ -61,7 +62,7 @@ describe('INPUT VALIDATION for Split names', () => { test('Should return false and log an error for the array if it is invalid', () => { for (let i = 0; i < invalidSplits.length; i++) { expect(validateSplits(loggerMock, invalidSplits[i], 'test_method')).toBe(false); // It will return false as the array is of an incorrect type. - expect(loggerMock.error.mock.calls).toEqual([['test_method: split_names must be a non-empty array.']]); // Should log the error for the collection. + expect(loggerMock.error).toBeCalledWith(ERROR_EMPTY_ARRAY, ['test_method', 'split_names']); // Should log the error for the collection. expect(validateSplitMock.mock.calls.length).toBe(0); // Should not try to validate any inner value if there is no valid array. loggerMock.error.mockClear(); diff --git a/src/utils/inputValidation/__tests__/trafficType.spec.ts b/src/utils/inputValidation/__tests__/trafficType.spec.ts index 2b5af848..43355676 100644 --- a/src/utils/inputValidation/__tests__/trafficType.spec.ts +++ b/src/utils/inputValidation/__tests__/trafficType.spec.ts @@ -1,30 +1,24 @@ +import { ERROR_NULL, ERROR_INVALID, ERROR_EMPTY, WARN_LOWERCASE_TRAFFIC_TYPE } from '../../../logger/constants'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { validateTrafficType } from '../trafficType'; -const errorMsgs = { - NULL_TRAFFIC_TYPE: 'you passed a null or undefined traffic_type_name, traffic_type_name must be a non-empty string.', - WRONG_TYPE_TRAFFIC_TYPE: 'you passed an invalid traffic_type_name, traffic_type_name must be a non-empty string.', - EMPTY_TRAFFIC_TYPE: 'you passed an empty traffic_type_name, traffic_type_name must be a non-empty string.', - LOWERCASE_TRAFFIC_TYPE: 'traffic_type_name should be all lowercase - converting string to lowercase.' -}; - const invalidTrafficTypes = [ - { tt: [], msg: errorMsgs.WRONG_TYPE_TRAFFIC_TYPE }, - { tt: () => { }, msg: errorMsgs.WRONG_TYPE_TRAFFIC_TYPE }, - { tt: Object.create({}), msg: errorMsgs.WRONG_TYPE_TRAFFIC_TYPE }, - { tt: {}, msg: errorMsgs.WRONG_TYPE_TRAFFIC_TYPE }, - { tt: true, msg: errorMsgs.WRONG_TYPE_TRAFFIC_TYPE }, - { tt: false, msg: errorMsgs.WRONG_TYPE_TRAFFIC_TYPE }, - { tt: 10, msg: errorMsgs.WRONG_TYPE_TRAFFIC_TYPE }, - { tt: 0, msg: errorMsgs.WRONG_TYPE_TRAFFIC_TYPE }, - { tt: NaN, msg: errorMsgs.WRONG_TYPE_TRAFFIC_TYPE }, - { tt: Infinity, msg: errorMsgs.WRONG_TYPE_TRAFFIC_TYPE }, - { tt: null, msg: errorMsgs.NULL_TRAFFIC_TYPE }, - { tt: undefined, msg: errorMsgs.NULL_TRAFFIC_TYPE }, - { tt: new Promise(res => res), msg: errorMsgs.WRONG_TYPE_TRAFFIC_TYPE }, - { tt: Symbol('asd'), msg: errorMsgs.WRONG_TYPE_TRAFFIC_TYPE }, - { tt: '', msg: errorMsgs.EMPTY_TRAFFIC_TYPE } + { tt: [], msg: ERROR_INVALID }, + { tt: () => { }, msg: ERROR_INVALID }, + { tt: Object.create({}), msg: ERROR_INVALID }, + { tt: {}, msg: ERROR_INVALID }, + { tt: true, msg: ERROR_INVALID }, + { tt: false, msg: ERROR_INVALID }, + { tt: 10, msg: ERROR_INVALID }, + { tt: 0, msg: ERROR_INVALID }, + { tt: NaN, msg: ERROR_INVALID }, + { tt: Infinity, msg: ERROR_INVALID }, + { tt: null, msg: ERROR_NULL }, + { tt: undefined, msg: ERROR_NULL }, + { tt: new Promise(res => res), msg: ERROR_INVALID }, + { tt: Symbol('asd'), msg: ERROR_INVALID }, + { tt: '', msg: ERROR_EMPTY } ]; const convertibleTrafficTypes = [ @@ -53,7 +47,7 @@ describe('INPUT VALIDATION for Traffic Types', () => { const convertibleTrafficType = convertibleTrafficTypes[i]; expect(validateTrafficType(loggerMock, convertibleTrafficType, 'some_method_trafficType')).toBe(convertibleTrafficType.toLowerCase()); // It should return the lowercase version of the traffic type received. - expect(loggerMock.warn.mock.calls[i][0]).toEqual(`some_method_trafficType: ${errorMsgs.LOWERCASE_TRAFFIC_TYPE}`); // Should log a warning. + expect(loggerMock.warn.mock.calls[i]).toEqual([WARN_LOWERCASE_TRAFFIC_TYPE, ['some_method_trafficType']]); // Should log a warning. } expect(loggerMock.error.mock.calls.length).toBe(0); // It should have not logged any errors. @@ -65,7 +59,7 @@ describe('INPUT VALIDATION for Traffic Types', () => { const expectedLog = invalidTrafficTypes[i]['msg']; expect(validateTrafficType(loggerMock, invalidValue, 'test_method')).toBe(false); // Invalid traffic types should always return false. - expect(loggerMock.error.mock.calls[i][0]).toEqual(`test_method: ${expectedLog}`); // Should log the error for the invalid traffic type. + expect(loggerMock.error.mock.calls[i]).toEqual([expectedLog, ['test_method']]); // Should log the error for the invalid traffic type. } expect(loggerMock.warn.mock.calls.length).toBe(0); // It should have not logged any warnings. diff --git a/src/utils/inputValidation/__tests__/trafficTypeExistance.spec.ts b/src/utils/inputValidation/__tests__/trafficTypeExistance.spec.ts index f8e30bd7..ba2f1666 100644 --- a/src/utils/inputValidation/__tests__/trafficTypeExistance.spec.ts +++ b/src/utils/inputValidation/__tests__/trafficTypeExistance.spec.ts @@ -2,6 +2,7 @@ import { IReadinessManager } from '../../../readiness/types'; import { ISplitsCacheBase } from '../../../storages/types'; import { LOCALHOST_MODE, STANDALONE_MODE } from '../../constants'; import thenable from '../../promise/thenable'; +import { WARN_NOT_EXISTENT_TT } from '../../../logger/constants'; /** Mocks */ import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; @@ -29,10 +30,6 @@ const splitsCacheMock = { /** Test target */ import { validateTrafficTypeExistance } from '../trafficTypeExistance'; -const errorMsgs = { - NOT_EXISTENT_TT: (ttName: string) => `Traffic Type ${ttName} does not have any corresponding Splits in this environment, make sure you're tracking your events to a valid traffic type defined in the Split console.` -}; - describe('validateTrafficTypeExistance', () => { afterEach(() => { @@ -43,16 +40,16 @@ describe('validateTrafficTypeExistance', () => { test('Should return true without going to the storage and log nothing if the SDK is not ready or in localhost mode', () => { // Not ready and not localstorage expect(validateTrafficTypeExistance(loggerMock, readinessManagerMock, splitsCacheMock, STANDALONE_MODE, 'test_tt', 'test_method')).toBe(true); // If the SDK is not ready yet, it will return true. - expect(splitsCacheMock.trafficTypeExists.mock.calls.length).toBe(0); // If the SDK is not ready yet, it does not try to go to the storage. - expect(loggerMock.error.mock.calls.length).toBe(0); // If the SDK is not ready yet, it will not log any errors. - expect(loggerMock.error.mock.calls.length).toBe(0); // If the SDK is not ready yet, it will not log any errors. + expect(splitsCacheMock.trafficTypeExists).not.toBeCalled(); // If the SDK is not ready yet, it does not try to go to the storage. + expect(loggerMock.error).not.toBeCalled(); // If the SDK is not ready yet, it will not log any errors. + expect(loggerMock.error).not.toBeCalled(); // If the SDK is not ready yet, it will not log any errors. // Ready and in localstorage mode. readinessManagerMock.isReady.mockImplementation(() => true); expect(validateTrafficTypeExistance(loggerMock, readinessManagerMock, splitsCacheMock, LOCALHOST_MODE, 'test_tt', 'test_method')).toBe(true); // If the SDK is in localhost mode, it will return true. - expect(splitsCacheMock.trafficTypeExists.mock.calls.length).toBe(0); // If the SDK is in localhost mode, it does not try to go to the storage. - expect(loggerMock.warn.mock.calls.length).toBe(0); // If the SDK is in localhost mode, it will not log any warnings. - expect(loggerMock.error.mock.calls.length).toBe(0); // If the SDK is in localhost mode, it will not log any errors. + expect(splitsCacheMock.trafficTypeExists).not.toBeCalled(); // If the SDK is in localhost mode, it does not try to go to the storage. + expect(loggerMock.warn).not.toBeCalled(); // If the SDK is in localhost mode, it will not log any warnings. + expect(loggerMock.error).not.toBeCalled(); // If the SDK is in localhost mode, it will not log any errors. }); test('Should return true and log nothing if SDK Ready, not localhost mode and the traffic type exists in the storage', () => { @@ -61,16 +58,16 @@ describe('validateTrafficTypeExistance', () => { expect(validateTrafficTypeExistance(loggerMock, readinessManagerMock, splitsCacheMock, STANDALONE_MODE, TEST_EXISTENT_TT, 'test_method')).toBe(true); // If the SDK is in condition to validate but the TT exists, it will return true. expect(splitsCacheMock.trafficTypeExists.mock.calls).toEqual([[TEST_EXISTENT_TT]]); // If the SDK is in condition to validate, it checks that TT existance with the storage. - expect(loggerMock.warn.mock.calls.length).toBe(0); // If the SDK is in condition to validate but the TT exists, it will not log any warnings. - expect(loggerMock.error.mock.calls.length).toBe(0); // If the SDK is in condition to validate but the TT exists, it will not log any errors. + expect(loggerMock.warn).not.toBeCalled(); // If the SDK is in condition to validate but the TT exists, it will not log any warnings. + expect(loggerMock.error).not.toBeCalled(); // If the SDK is in condition to validate but the TT exists, it will not log any errors. }); test('Should return false and log warning if SDK Ready, not localhost mode and the traffic type does NOT exist in the storage', () => { // Ready, standalone, and the TT not exists in the storage. expect(validateTrafficTypeExistance(loggerMock, readinessManagerMock, splitsCacheMock, STANDALONE_MODE, TEST_NOT_EXISTENT_TT, 'test_method_y')).toBe(false); // If the SDK is in condition to validate but the TT does not exist in the storage, it will return false. expect(splitsCacheMock.trafficTypeExists.mock.calls).toEqual([[TEST_NOT_EXISTENT_TT]]); // If the SDK is in condition to validate, it checks that TT existance with the storage. - expect(loggerMock.warn.mock.calls).toEqual([[`test_method_y: ${errorMsgs.NOT_EXISTENT_TT(TEST_NOT_EXISTENT_TT)}`]]); // If the SDK is in condition to validate but the TT does not exist in the storage, it will log the expected warning. - expect(loggerMock.error.mock.calls.length).toBe(0); // It logged a warning so no errors should be logged. + expect(loggerMock.warn).toBeCalledWith(WARN_NOT_EXISTENT_TT, ['test_method_y', TEST_NOT_EXISTENT_TT]); // If the SDK is in condition to validate but the TT does not exist in the storage, it will log the expected warning. + expect(loggerMock.error).not.toBeCalled(); // It logged a warning so no errors should be logged. }); test('validateTrafficTypeExistance w/async storage - If the storage is async but the SDK is in condition to validate, it will validate that the TT exists on the storage', async () => { @@ -79,14 +76,14 @@ describe('validateTrafficTypeExistance', () => { const validationPromise = validateTrafficTypeExistance(loggerMock, readinessManagerMock, splitsCacheMock, STANDALONE_MODE, TEST_EXISTENT_ASYNC_TT, 'test_method_z'); expect(thenable(validationPromise)).toBe(true); // If the storage is async, it should also return a promise. expect(splitsCacheMock.trafficTypeExists.mock.calls).toEqual([[TEST_EXISTENT_ASYNC_TT]]); // If the SDK is in condition to validate, it checks that TT existance with the async storage. - expect(loggerMock.warn.mock.calls.length).toBe(0); // We are still fetching the data from the storage, no logs yet. - expect(loggerMock.error.mock.calls.length).toBe(0); // We are still fetching the data from the storage, no logs yet. + expect(loggerMock.warn).not.toBeCalled(); // We are still fetching the data from the storage, no logs yet. + expect(loggerMock.error).not.toBeCalled(); // We are still fetching the data from the storage, no logs yet. const isValid = await validationPromise; expect(isValid).toBe(true); // As the split existed, it will resolve to true. - expect(loggerMock.warn.mock.calls.length).toBe(0); // It was valid so no logs. - expect(loggerMock.error.mock.calls.length).toBe(0); // It was valid so no logs. + expect(loggerMock.warn).not.toBeCalled(); // It was valid so no logs. + expect(loggerMock.error).not.toBeCalled(); // It was valid so no logs. // Second round, a TT that does not exist on the asnyc storage splitsCacheMock.trafficTypeExists.mockClear(); @@ -94,12 +91,12 @@ describe('validateTrafficTypeExistance', () => { const validationPromise2 = validateTrafficTypeExistance(loggerMock, readinessManagerMock, splitsCacheMock, STANDALONE_MODE, TEST_NOT_EXISTENT_ASYNC_TT, 'test_method_z'); expect(thenable(validationPromise2)).toBe(true); // If the storage is async, it should also return a promise. expect(splitsCacheMock.trafficTypeExists.mock.calls).toEqual([[TEST_NOT_EXISTENT_ASYNC_TT]]); // If the SDK is in condition to validate, it checks that TT existance with the async storage. - expect(loggerMock.warn.mock.calls.length).toBe(0); // We are still fetching the data from the storage, no logs yet. - expect(loggerMock.error.mock.calls.length).toBe(0); // We are still fetching the data from the storage, no logs yet. + expect(loggerMock.warn).not.toBeCalled(); // We are still fetching the data from the storage, no logs yet. + expect(loggerMock.error).not.toBeCalled(); // We are still fetching the data from the storage, no logs yet. const isValid2 = await validationPromise2; expect(isValid2).toBe(false); // As the split is not on the storage, it will resolve to false, failing the validation.. - expect(loggerMock.warn.mock.calls).toEqual([[`test_method_z: ${errorMsgs.NOT_EXISTENT_TT(TEST_NOT_EXISTENT_ASYNC_TT)}`]]); // If the SDK is in condition to validate but the TT does not exist in the storage, it will log the expected warning. - expect(loggerMock.error.mock.calls.length).toBe(0); // It logged a warning so no errors should be logged. + expect(loggerMock.warn).toBeCalledWith(WARN_NOT_EXISTENT_TT, ['test_method_z', TEST_NOT_EXISTENT_ASYNC_TT]); // If the SDK is in condition to validate but the TT does not exist in the storage, it will log the expected warning. + expect(loggerMock.error).not.toBeCalled(); // It logged a warning so no errors should be logged. }); }); diff --git a/src/utils/inputValidation/apiKey.ts b/src/utils/inputValidation/apiKey.ts index da2bdbbb..a629815a 100644 --- a/src/utils/inputValidation/apiKey.ts +++ b/src/utils/inputValidation/apiKey.ts @@ -1,19 +1,21 @@ -import { ERROR_API_KEY, WARN_API_KEY } from '../../logger/constants'; +import { ERROR_NULL, ERROR_EMPTY, ERROR_INVALID, WARN_API_KEY, INSTANTIATION_LB } from '../../logger/constants'; import { ILogger } from '../../logger/types'; import { isString } from '../lang'; +const item = 'api_key'; + /** validates the given api key */ export function validateApiKey(log: ILogger, maybeApiKey: any): string | false { let apiKey: string | false = false; if (maybeApiKey == undefined) { // eslint-disable-line eqeqeq - log.error(ERROR_API_KEY, ['you passed a null or undefined api_key']); + log.error(ERROR_NULL, [INSTANTIATION_LB, item]); } else if (isString(maybeApiKey)) { if (maybeApiKey.length > 0) apiKey = maybeApiKey; else - log.error(ERROR_API_KEY, ['you passed an empty api_key']); + log.error(ERROR_EMPTY, [INSTANTIATION_LB, item]); } else { - log.error(ERROR_API_KEY, ['you passed an invalid api_key']); + log.error(ERROR_INVALID, [INSTANTIATION_LB, item]); } return apiKey; @@ -31,10 +33,10 @@ export function validateAndTrackApiKey(log: ILogger, maybeApiKey: any): string | // If this key is not present, only warning scenarios is that we have factories for other keys. usedKeysMap[apiKey] = 1; if (Object.keys(usedKeysMap).length > 1) { - log.warn(WARN_API_KEY, ['You already have an instance of the Split factory. Make sure you definitely want this additional instance']); + log.warn(WARN_API_KEY, ['an instance of the Split factory']); } } else { - log.warn(WARN_API_KEY, [`You already have ${usedKeysMap[apiKey]} ${usedKeysMap[apiKey] === 1 ? 'factory' : 'factories'} with this API Key`]); + log.warn(WARN_API_KEY, [`${usedKeysMap[apiKey]} ${usedKeysMap[apiKey] === 1 ? 'factory' : 'factories'} with this API Key`]); usedKeysMap[apiKey]++; } } diff --git a/src/utils/inputValidation/attributes.ts b/src/utils/inputValidation/attributes.ts index d40f21db..e45f67c0 100644 --- a/src/utils/inputValidation/attributes.ts +++ b/src/utils/inputValidation/attributes.ts @@ -1,13 +1,13 @@ import { isObject } from '../lang'; import { SplitIO } from '../../types'; import { ILogger } from '../../logger/types'; -import { ERROR_13 } from '../../logger/constants'; +import { ERROR_NOT_PLAIN_OBJECT } from '../../logger/constants'; export function validateAttributes(log: ILogger, maybeAttrs: any, method: string): SplitIO.Attributes | undefined | false { // Attributes are optional if (isObject(maybeAttrs) || maybeAttrs == undefined) // eslint-disable-line eqeqeq return maybeAttrs; - log.error(ERROR_13, [method]); + log.error(ERROR_NOT_PLAIN_OBJECT, [method, 'attributes']); return false; } diff --git a/src/utils/inputValidation/event.ts b/src/utils/inputValidation/event.ts index f067b23e..03e79c9e 100644 --- a/src/utils/inputValidation/event.ts +++ b/src/utils/inputValidation/event.ts @@ -1,4 +1,4 @@ -import { ERROR_14, ERROR_15, ERROR_16, ERROR_17 } from '../../logger/constants'; +import { ERROR_EVENT_TYPE_FORMAT, ERROR_NULL, ERROR_INVALID, ERROR_EMPTY } from '../../logger/constants'; import { ILogger } from '../../logger/types'; import { isString } from '../lang'; @@ -6,14 +6,14 @@ const EVENT_TYPE_REGEX = /^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$/; export function validateEvent(log: ILogger, maybeEvent: any, method: string): string | false { if (maybeEvent == undefined) { // eslint-disable-line eqeqeq - log.error(ERROR_15, [method]); + log.error(ERROR_NULL, [method]); } else if (!isString(maybeEvent)) { - log.error(ERROR_16, [method]); + log.error(ERROR_INVALID, [method]); } else { // It is a string. if (maybeEvent.length === 0) { - log.error(ERROR_17, [method]); + log.error(ERROR_EMPTY, [method]); } else if (!EVENT_TYPE_REGEX.test(maybeEvent)) { - log.error(ERROR_14, [method, maybeEvent]); + log.error(ERROR_EVENT_TYPE_FORMAT, [method, maybeEvent]); } else { return maybeEvent; } diff --git a/src/utils/inputValidation/eventProperties.ts b/src/utils/inputValidation/eventProperties.ts index 6f195e09..8a599a97 100644 --- a/src/utils/inputValidation/eventProperties.ts +++ b/src/utils/inputValidation/eventProperties.ts @@ -1,7 +1,7 @@ import { isObject, shallowClone, isString, isFiniteNumber, isBoolean } from '../lang'; import { SplitIO } from '../../types'; import { ILogger } from '../../logger/types'; -import { ERROR_18, ERROR_19, WARN_12, WARN_13 } from '../../logger/constants'; +import { ERROR_NOT_PLAIN_OBJECT, ERROR_SIZE_EXCEEDED, WARN_SETTING_NULL, WARN_TRIMMING_PROPERTIES } from '../../logger/constants'; const ECMA_SIZES = { NULL: 0, // While on the JSON it's going to occupy more space, we'll take it as 0 for the approximation. @@ -17,7 +17,7 @@ export function validateEventProperties(log: ILogger, maybeProperties: any, meth if (maybeProperties == undefined) return { properties: null, size: BASE_EVENT_SIZE }; // eslint-disable-line eqeqeq if (!isObject(maybeProperties)) { - log.error(ERROR_18, [method]); + log.error(ERROR_NOT_PLAIN_OBJECT, [method, 'properties']); return { properties: false, size: BASE_EVENT_SIZE }; } @@ -30,7 +30,7 @@ export function validateEventProperties(log: ILogger, maybeProperties: any, meth }; if (keys.length > MAX_PROPERTIES_AMOUNT) { - log.warn(WARN_13, [method]); + log.warn(WARN_TRIMMING_PROPERTIES, [method]); } for (let i = 0; i < keys.length; i++) { @@ -47,7 +47,7 @@ export function validateEventProperties(log: ILogger, maybeProperties: any, meth clone[keys[i]] = null; val = null; isNullVal = true; - log.warn(WARN_12, [method, keys[i]]); + log.warn(WARN_SETTING_NULL, [method, keys[i]]); } if (isNullVal) output.size += ECMA_SIZES.NULL; @@ -56,7 +56,7 @@ export function validateEventProperties(log: ILogger, maybeProperties: any, meth else if (isStringVal) output.size += val.length * ECMA_SIZES.STRING; if (output.size > MAX_PROPERTIES_SIZE) { - log.error(ERROR_19, [method]); + log.error(ERROR_SIZE_EXCEEDED, [method]); output.properties = false; break; } diff --git a/src/utils/inputValidation/eventValue.ts b/src/utils/inputValidation/eventValue.ts index ef9f5c36..9b7bdd16 100644 --- a/src/utils/inputValidation/eventValue.ts +++ b/src/utils/inputValidation/eventValue.ts @@ -1,4 +1,4 @@ -import { ERROR_20 } from '../../logger/constants'; +import { ERROR_NOT_FINITE } from '../../logger/constants'; import { ILogger } from '../../logger/types'; import { isFiniteNumber } from '../lang'; @@ -6,6 +6,6 @@ export function validateEventValue(log: ILogger, maybeValue: any, method: string if (isFiniteNumber(maybeValue) || maybeValue == undefined) // eslint-disable-line eqeqeq return maybeValue; - log.error(ERROR_20, [method]); + log.error(ERROR_NOT_FINITE, [method]); return false; } diff --git a/src/utils/inputValidation/isOperational.ts b/src/utils/inputValidation/isOperational.ts index 34ef4464..666b9d33 100644 --- a/src/utils/inputValidation/isOperational.ts +++ b/src/utils/inputValidation/isOperational.ts @@ -1,17 +1,17 @@ -import { ERROR_21, WARN_14 } from '../../logger/constants'; +import { ERROR_CLIENT_DESTROYED, WARN_CLIENT_NOT_READY } from '../../logger/constants'; import { ILogger } from '../../logger/types'; import { IReadinessManager } from '../../readiness/types'; -export function validateIfNotDestroyed(log: ILogger, readinessManager: IReadinessManager): boolean { +export function validateIfNotDestroyed(log: ILogger, readinessManager: IReadinessManager, method: string): boolean { if (!readinessManager.isDestroyed()) return true; - log.error(ERROR_21); + log.error(ERROR_CLIENT_DESTROYED, [method]); return false; } export function validateIfOperational(log: ILogger, readinessManager: IReadinessManager, method: string) { if (readinessManager.isReady() || readinessManager.isReadyFromCache()) return true; - log.warn(WARN_14, [method]); + log.warn(WARN_CLIENT_NOT_READY, [method]); return false; } diff --git a/src/utils/inputValidation/key.ts b/src/utils/inputValidation/key.ts index f7ec064f..9068bed6 100644 --- a/src/utils/inputValidation/key.ts +++ b/src/utils/inputValidation/key.ts @@ -1,17 +1,17 @@ import { isObject, isString, isFiniteNumber, toString } from '../lang'; import { SplitIO } from '../../types'; import { ILogger } from '../../logger/types'; -import { ERROR_22, WARN_15, ERROR_25, ERROR_23, ERROR_24, ERROR_26 } from '../../logger/constants'; +import { ERROR_NULL, WARN_CONVERTING, ERROR_EMPTY, ERROR_TOO_LONG, ERROR_INVALID, ERROR_INVALID_KEY_OBJECT } from '../../logger/constants'; const KEY_MAX_LENGTH = 250; function validateKeyValue(log: ILogger, maybeKey: any, method: string, type: string): string | false { if (maybeKey == undefined) { // eslint-disable-line eqeqeq - log.error(ERROR_22, [method, type, type]); + log.error(ERROR_NULL, [method, type]); return false; } if (isFiniteNumber(maybeKey)) { - log.warn(WARN_15, [method, type, maybeKey]); + log.warn(WARN_CONVERTING, [method, type, maybeKey]); return toString(maybeKey); } if (isString(maybeKey)) { @@ -22,12 +22,12 @@ function validateKeyValue(log: ILogger, maybeKey: any, method: string, type: str if (maybeKey.length > 0 && maybeKey.length <= KEY_MAX_LENGTH) return maybeKey; if (maybeKey.length === 0) { - log.error(ERROR_25, [method, type]); + log.error(ERROR_EMPTY, [method, type]); } else if (maybeKey.length > KEY_MAX_LENGTH) { - log.error(ERROR_23, [method, type, type]); + log.error(ERROR_TOO_LONG, [method, type]); } } else { - log.error(ERROR_24, [method, type, type]); + log.error(ERROR_INVALID, [method, type]); } return false; @@ -43,7 +43,7 @@ export function validateKey(log: ILogger, maybeKey: any, method: string): SplitI matchingKey, bucketingKey }; - log.error(ERROR_26, [method]); + log.error(ERROR_INVALID_KEY_OBJECT, [method]); return false; } else { return validateKeyValue(log, maybeKey, method, 'key'); diff --git a/src/utils/inputValidation/split.ts b/src/utils/inputValidation/split.ts index 12064144..b7261604 100644 --- a/src/utils/inputValidation/split.ts +++ b/src/utils/inputValidation/split.ts @@ -1,4 +1,4 @@ -import { ERROR_22, ERROR_32, WARN_17, ERROR_33 } from '../../logger/constants'; +import { ERROR_NULL, ERROR_INVALID, WARN_TRIMMING, ERROR_EMPTY } from '../../logger/constants'; import { ILogger } from '../../logger/types'; import { isString } from '../lang'; @@ -7,19 +7,19 @@ const TRIMMABLE_SPACES_REGEX = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/; export function validateSplit(log: ILogger, maybeSplit: any, method: string, item = 'split name'): string | false { if (maybeSplit == undefined) { // eslint-disable-line eqeqeq - log.error(ERROR_22, [method, item, item]); + log.error(ERROR_NULL, [method, item]); } else if (!isString(maybeSplit)) { - log.error(ERROR_32, [method, item, item]); + log.error(ERROR_INVALID, [method, item]); } else { if (TRIMMABLE_SPACES_REGEX.test(maybeSplit)) { - log.warn(WARN_17, [method, item, maybeSplit]); + log.warn(WARN_TRIMMING, [method, item, maybeSplit]); maybeSplit = maybeSplit.trim(); } if (maybeSplit.length > 0) { return maybeSplit; } else { - log.error(ERROR_33, [method, item, item]); + log.error(ERROR_EMPTY, [method, item]); } } diff --git a/src/utils/inputValidation/splitExistance.ts b/src/utils/inputValidation/splitExistance.ts index b8798f84..ddafa767 100644 --- a/src/utils/inputValidation/splitExistance.ts +++ b/src/utils/inputValidation/splitExistance.ts @@ -1,7 +1,7 @@ import { SPLIT_NOT_FOUND } from '../labels'; import { IReadinessManager } from '../../readiness/types'; import { ILogger } from '../../logger/types'; -import { WARN_18 } from '../../logger/constants'; +import { WARN_NOT_EXISTENT_SPLIT } from '../../logger/constants'; /** * This is defined here and in this format mostly because of the logger and the fact that it's considered a validation at product level. @@ -10,7 +10,7 @@ import { WARN_18 } from '../../logger/constants'; export function validateSplitExistance(log: ILogger, readinessManager: IReadinessManager, splitName: string, labelOrSplitObj: any, method: string): boolean { if (readinessManager.isReady()) { // Only if it's ready we validate this, otherwise it may just be that the SDK is not ready yet. if (labelOrSplitObj === SPLIT_NOT_FOUND || labelOrSplitObj == null) { - log.warn(WARN_18, [method, splitName]); + log.warn(WARN_NOT_EXISTENT_SPLIT, [method, splitName]); return false; } } diff --git a/src/utils/inputValidation/splits.ts b/src/utils/inputValidation/splits.ts index 6807d7da..ee381210 100644 --- a/src/utils/inputValidation/splits.ts +++ b/src/utils/inputValidation/splits.ts @@ -1,4 +1,4 @@ -import { ERROR_34 } from '../../logger/constants'; +import { ERROR_EMPTY_ARRAY } from '../../logger/constants'; import { ILogger } from '../../logger/types'; import { uniq } from '../lang'; import { validateSplit } from './split'; @@ -16,6 +16,6 @@ export function validateSplits(log: ILogger, maybeSplits: any, method: string, l if (validatedArray.length) return uniq(validatedArray); } - log.error(ERROR_34, [method, listName]); + log.error(ERROR_EMPTY_ARRAY, [method, listName]); return false; } diff --git a/src/utils/inputValidation/trafficType.ts b/src/utils/inputValidation/trafficType.ts index 189560e8..79f1eb02 100644 --- a/src/utils/inputValidation/trafficType.ts +++ b/src/utils/inputValidation/trafficType.ts @@ -1,4 +1,4 @@ -import { ERROR_35, ERROR_36, ERROR_37, WARN_19 } from '../../logger/constants'; +import { ERROR_NULL, ERROR_INVALID, ERROR_EMPTY, WARN_LOWERCASE_TRAFFIC_TYPE } from '../../logger/constants'; import { ILogger } from '../../logger/types'; import { isString } from '../lang'; @@ -6,15 +6,15 @@ const CAPITAL_LETTERS_REGEX = /[A-Z]/; export function validateTrafficType(log: ILogger, maybeTT: any, method: string): string | false { if (maybeTT == undefined) { // eslint-disable-line eqeqeq - log.error(ERROR_35, [method]); + log.error(ERROR_NULL, [method]); } else if (!isString(maybeTT)) { - log.error(ERROR_36, [method]); + log.error(ERROR_INVALID, [method]); } else { if (maybeTT.length === 0) { - log.error(ERROR_37, [method]); + log.error(ERROR_EMPTY, [method]); } else { if (CAPITAL_LETTERS_REGEX.test(maybeTT)) { - log.warn(WARN_19, [method]); + log.warn(WARN_LOWERCASE_TRAFFIC_TYPE, [method]); maybeTT = maybeTT.toLowerCase(); } diff --git a/src/utils/inputValidation/trafficTypeExistance.ts b/src/utils/inputValidation/trafficTypeExistance.ts index 79244801..eb64c0d3 100644 --- a/src/utils/inputValidation/trafficTypeExistance.ts +++ b/src/utils/inputValidation/trafficTypeExistance.ts @@ -5,10 +5,10 @@ import { IReadinessManager } from '../../readiness/types'; import { SDKMode } from '../../types'; import { MaybeThenable } from '../../dtos/types'; import { ILogger } from '../../logger/types'; -import { WARN_20 } from '../../logger/constants'; +import { WARN_NOT_EXISTENT_TT } from '../../logger/constants'; function logTTExistanceWarning(log: ILogger, maybeTT: string, method: string) { - log.warn(WARN_20, [method, maybeTT]); + log.warn(WARN_NOT_EXISTENT_TT, [method, maybeTT]); } /** diff --git a/src/utils/settingsValidation/__tests__/splitFilters.spec.ts b/src/utils/settingsValidation/__tests__/splitFilters.spec.ts index d46117e5..6024ec2e 100644 --- a/src/utils/settingsValidation/__tests__/splitFilters.spec.ts +++ b/src/utils/settingsValidation/__tests__/splitFilters.spec.ts @@ -7,6 +7,7 @@ import { splitFilters, queryStrings, groupedFilters } from '../../../__tests__/m // Test target import { validateSplitFilters } from '../splitFilters'; +import { DEBUG_SPLITS_FILTER, ERROR_INVALID, ERROR_EMPTY_ARRAY, WARN_SPLITS_FILTER_IGNORED, WARN_SPLITS_FILTER_INVALID, WARN_SPLITS_FILTER_EMPTY } from '../../../logger/constants'; describe('validateSplitFilters', () => { @@ -22,25 +23,17 @@ describe('validateSplitFilters', () => { expect(validateSplitFilters(loggerMock, undefined, STANDALONE_MODE)).toEqual(defaultOutput); // splitFilters ignored if not a non-empty array expect(validateSplitFilters(loggerMock, null, STANDALONE_MODE)).toEqual(defaultOutput); // splitFilters ignored if not a non-empty array - expect(loggerMock.warn.mock.calls.length === 0).toBe(true); + expect(loggerMock.warn).not.toBeCalled(); expect(validateSplitFilters(loggerMock, true, STANDALONE_MODE)).toEqual(defaultOutput); // splitFilters ignored if not a non-empty array - expect(loggerMock.warn.mock.calls[0]).toEqual(['Factory instantiation: splitFilters configuration must be a non-empty array of filter objects.']); - expect(validateSplitFilters(loggerMock, 15, STANDALONE_MODE)).toEqual(defaultOutput); // splitFilters ignored if not a non-empty array - expect(loggerMock.warn.mock.calls[1]).toEqual(['Factory instantiation: splitFilters configuration must be a non-empty array of filter objects.']); - expect(validateSplitFilters(loggerMock, 'string', STANDALONE_MODE)).toEqual(defaultOutput); // splitFilters ignored if not a non-empty array - expect(loggerMock.warn.mock.calls[2]).toEqual(['Factory instantiation: splitFilters configuration must be a non-empty array of filter objects.']); - expect(validateSplitFilters(loggerMock, [], STANDALONE_MODE)).toEqual(defaultOutput); // splitFilters ignored if not a non-empty array - expect(loggerMock.warn.mock.calls[3]).toEqual(['Factory instantiation: splitFilters configuration must be a non-empty array of filter objects.']); - - expect(validateSplitFilters(loggerMock, [{ type: 'byName', values: ['split_1'] }], CONSUMER_MODE)).toEqual(defaultOutput); - expect(loggerMock.warn.mock.calls[4]).toEqual(["Factory instantiation: split filters have been configured but will have no effect if mode is not 'standalone', since synchronization is being deferred to an external tool."]); + expect(validateSplitFilters(loggerMock, [{ type: 'byName', values: ['split_1'] }], CONSUMER_MODE)).toEqual(defaultOutput); // splitFilters ignored if not in 'standalone' mode + expect(loggerMock.warn.mock.calls).toEqual([[WARN_SPLITS_FILTER_EMPTY], [WARN_SPLITS_FILTER_EMPTY], [WARN_SPLITS_FILTER_EMPTY], [WARN_SPLITS_FILTER_EMPTY], [WARN_SPLITS_FILTER_IGNORED, ['standalone']]]); - expect(loggerMock.debug.mock.calls.length === 0).toBe(true); - expect(loggerMock.error.mock.calls.length === 0).toBe(true); + expect(loggerMock.debug).not.toBeCalled(); + expect(loggerMock.error).not.toBeCalled(); }); test('Returns object with null queryString, if `splitFilters` contains invalid filters or contains filters with no values or invalid values', () => { @@ -55,7 +48,7 @@ describe('validateSplitFilters', () => { groupedFilters: { byName: [], byPrefix: [] } }; expect(validateSplitFilters(loggerMock, splitFilters, STANDALONE_MODE)).toEqual(output); // filters without values - expect(loggerMock.debug.mock.calls[0]).toEqual(["Factory instantiation: splits filtering criteria is 'null'."]); + expect(loggerMock.debug).toBeCalledWith(DEBUG_SPLITS_FILTER, [null]); loggerMock.debug.mockClear(); splitFilters.push( @@ -65,16 +58,16 @@ describe('validateSplitFilters', () => { { type: 'byName', values: [13] }); output.validFilters.push({ type: 'byName', values: [13] }); expect(validateSplitFilters(loggerMock, splitFilters, STANDALONE_MODE)).toEqual(output); // some filters are invalid - expect(loggerMock.debug.mock.calls).toEqual([["Factory instantiation: splits filtering criteria is 'null'."]]); + expect(loggerMock.debug.mock.calls).toEqual([[DEBUG_SPLITS_FILTER, [null]]]); expect(loggerMock.warn.mock.calls).toEqual([ - ["Factory instantiation: split filter at position '3' is invalid. It must be an object with a valid filter type ('byName' or 'byPrefix') and a list of 'values'."], // invalid value of `type` property - ["Factory instantiation: split filter at position '4' is invalid. It must be an object with a valid filter type ('byName' or 'byPrefix') and a list of 'values'."], // invalid type of `values` property - ["Factory instantiation: split filter at position '5' is invalid. It must be an object with a valid filter type ('byName' or 'byPrefix') and a list of 'values'."] // invalid type of `type` property + [WARN_SPLITS_FILTER_INVALID, [3]], // invalid value of `type` property + [WARN_SPLITS_FILTER_INVALID, [4]], // invalid type of `values` property + [WARN_SPLITS_FILTER_INVALID, [5]] // invalid type of `type` property ]); expect(loggerMock.error.mock.calls).toEqual([ - ['Factory instantiation: you passed an invalid byName filter value, byName filter value must be a non-empty string.'], - ['Factory instantiation: byName filter must be a non-empty array.'] + [ERROR_INVALID, ['settings', 'byName filter value']], + [ERROR_EMPTY_ARRAY, ['settings', 'byName filter']] ]); }); @@ -89,7 +82,7 @@ describe('validateSplitFilters', () => { groupedFilters: groupedFilters[i] }; expect(validateSplitFilters(loggerMock, splitFilters[i], STANDALONE_MODE)).toEqual(output); // splitFilters #${i} - expect(loggerMock.debug.mock.calls[loggerMock.debug.mock.calls.length - 1]).toEqual([`Factory instantiation: splits filtering criteria is '${queryStrings[i]}'.`]); + expect(loggerMock.debug).lastCalledWith(DEBUG_SPLITS_FILTER, [queryStrings[i]]); } else { // tests where validateSplitFilters throws an exception expect(() => validateSplitFilters(loggerMock, splitFilters[i], STANDALONE_MODE)).toThrow(queryStrings[i]); diff --git a/src/utils/settingsValidation/impressionsMode.ts b/src/utils/settingsValidation/impressionsMode.ts index c81f5460..84ae2f14 100644 --- a/src/utils/settingsValidation/impressionsMode.ts +++ b/src/utils/settingsValidation/impressionsMode.ts @@ -1,4 +1,4 @@ -import { ERROR_38 } from '../../logger/constants'; +import { ERROR_INVALID_IMPRESSIONS_MODE } from '../../logger/constants'; import { ILogger } from '../../logger/types'; import { SplitIO } from '../../types'; import { DEBUG, OPTIMIZED } from '../constants'; @@ -6,7 +6,7 @@ import { DEBUG, OPTIMIZED } from '../constants'; export default function validImpressionsMode(log: ILogger, impressionsMode: string): SplitIO.ImpressionsMode { impressionsMode = impressionsMode.toUpperCase(); if ([DEBUG, OPTIMIZED].indexOf(impressionsMode) === -1) { - log.error(ERROR_38, [DEBUG, OPTIMIZED, OPTIMIZED]); + log.error(ERROR_INVALID_IMPRESSIONS_MODE, [[DEBUG, OPTIMIZED], OPTIMIZED]); impressionsMode = OPTIMIZED; } diff --git a/src/utils/settingsValidation/integrations/common.ts b/src/utils/settingsValidation/integrations/common.ts index 61ea19c0..95fa1cd4 100644 --- a/src/utils/settingsValidation/integrations/common.ts +++ b/src/utils/settingsValidation/integrations/common.ts @@ -1,4 +1,4 @@ -import { WARN_21 } from '../../../logger/constants'; +import { WARN_INTEGRATION_INVALID } from '../../../logger/constants'; import { ILogger } from '../../../logger/types'; /** @@ -21,7 +21,7 @@ export function validateIntegrations(settings: { log: ILogger, integrations?: an // Log a warning if at least one item is invalid const invalids = integrations.length - validIntegrations.length; - if (invalids) log.warn(WARN_21, [invalids, invalids === 1 ? 'item' : 'items', invalids === 1 ? 'is' : 'are', extraWarning || '']); + if (invalids) log.warn(WARN_INTEGRATION_INVALID, [invalids, invalids === 1 ? 'item' : 'items', invalids === 1 ? 'is' : 'are', extraWarning || '']); return validIntegrations; } diff --git a/src/utils/settingsValidation/logger/builtinLogger.ts b/src/utils/settingsValidation/logger/builtinLogger.ts index 51a993e3..2ef50b97 100644 --- a/src/utils/settingsValidation/logger/builtinLogger.ts +++ b/src/utils/settingsValidation/logger/builtinLogger.ts @@ -3,6 +3,13 @@ import { ILogger } from '../../../logger/types'; import { LogLevel } from '../../../types'; import { isLocalStorageAvailable } from '../../env/isLocalStorageAvailable'; import { isNode } from '../../env/isNode'; +import { codesError } from '../../../logger/messages/error'; +import { codesWarn } from '../../../logger/messages/warn'; +import { codesInfo } from '../../../logger/messages/info'; +import { codesDebug } from '../../../logger/messages/debug'; +import { _Map } from '../../lang/maps'; + +const allCodes = new _Map(codesError.concat(codesWarn, codesInfo, codesDebug)); // @TODO when integrating with other packages, find the best way to handle initial state per environment const LS_KEY = 'splitio_debug'; @@ -52,16 +59,14 @@ export function getLogLevel(debugValue: unknown): LogLevel | undefined { * @param settings user config object * @returns a logger instance with the log level at `settings.debug`. If `settings.debug` is invalid or not provided, `initialLogLevel` is used. */ -export function validateLogger(settings: { debug: unknown} ): ILogger { +export function validateLogger(settings: { debug: unknown }): ILogger { const settingLogLevel = settings.debug ? getLogLevel(settings.debug) : initialLogLevel; - const log = new Logger('splitio', { logLevel: settingLogLevel || initialLogLevel }); + const log = new Logger('splitio', { logLevel: settingLogLevel || initialLogLevel }, allCodes); // logs error if the provided settings debug value is invalid if (!settingLogLevel) log.error('Invalid Log Level - No changes to the logs will be applied.'); return log; } - - diff --git a/src/utils/settingsValidation/logger/pluggableLogger.ts b/src/utils/settingsValidation/logger/pluggableLogger.ts index 8e01d90c..2a644618 100644 --- a/src/utils/settingsValidation/logger/pluggableLogger.ts +++ b/src/utils/settingsValidation/logger/pluggableLogger.ts @@ -1,31 +1,27 @@ +import { Logger } from '../../../logger'; import { ILogger } from '../../../logger/types'; -const noopLogger: ILogger = { - setLogLevel() { }, - debug() { }, - info() { }, - warn() { }, - error() { } -}; - function isLogger(log: any): log is ILogger { return log && typeof log.debug === 'function' && typeof log.info === 'function' && typeof log.warn === 'function' && typeof log.error === 'function' && typeof log.setLogLevel === 'function'; } /** - * Validates the `log` (logger) property at config. + * Validates the `debug` (logger) property at config. * * @param settings user config object - * @returns the provided logger or a no-op logger if no one is provided - * @throws throws an error if a logger was provided but is invalid + * @returns the provided logger at `settings.debug` or a new one with NONE log level if the provided one is invalid */ export function validateLogger(settings: { debug: unknown }): ILogger { const { debug } = settings; + const log = new Logger('splitio', { logLevel: 'NONE' }); - if (!debug) return noopLogger; + // @TODO support boolean and string values? + if (!debug) return log; if (isLogger(debug)) return debug; - // @TODO log error instead of throwing one - throw new Error('The provided `debug` value at config is not valid'); + // logs error, for consistency with builtin logger validator + log.error('The provided `debug` value at config is invalid.'); + + return log; } diff --git a/src/utils/settingsValidation/splitFilters.ts b/src/utils/settingsValidation/splitFilters.ts index 25e7cf90..8542a44b 100644 --- a/src/utils/settingsValidation/splitFilters.ts +++ b/src/utils/settingsValidation/splitFilters.ts @@ -3,7 +3,7 @@ import { validateSplits } from '../inputValidation/splits'; import { ISplitFiltersValidation } from '../../dtos/types'; import { SplitIO } from '../../types'; import { ILogger } from '../../logger/types'; -import { WARN_22, WARN_24, WARN_23, DEBUG_51 } from '../../logger/constants'; +import { WARN_SPLITS_FILTER_IGNORED, WARN_SPLITS_FILTER_EMPTY, WARN_SPLITS_FILTER_INVALID, DEBUG_SPLITS_FILTER, SETTINGS_LB } from '../../logger/constants'; // Split filters metadata. // Ordered according to their precedency when forming the filter query string: `&names=&prefixes=` @@ -39,7 +39,7 @@ function validateFilterType(maybeFilterType: any): maybeFilterType is SplitIO.Sp */ function validateSplitFilter(log: ILogger, type: SplitIO.SplitFilterType, values: string[], maxLength: number) { // validate and remove invalid and duplicated values - let result = validateSplits(log, values, 'Factory instantiation', `${type} filter`, `${type} filter value`); + let result = validateSplits(log, values, SETTINGS_LB, `${type} filter`, `${type} filter value`); if (result) { // check max length @@ -97,12 +97,12 @@ export function validateSplitFilters(log: ILogger, maybeSplitFilters: any, mode: if (!maybeSplitFilters) return res; // Warn depending on the mode if (mode !== STANDALONE_MODE) { - log.warn(WARN_22, [STANDALONE_MODE]); + log.warn(WARN_SPLITS_FILTER_IGNORED, [STANDALONE_MODE]); return res; } // Check collection type if (!Array.isArray(maybeSplitFilters) || maybeSplitFilters.length === 0) { - log.warn(WARN_24); + log.warn(WARN_SPLITS_FILTER_EMPTY); return res; } @@ -113,7 +113,7 @@ export function validateSplitFilters(log: ILogger, maybeSplitFilters: any, mode: res.groupedFilters[filter.type as SplitIO.SplitFilterType] = res.groupedFilters[filter.type as SplitIO.SplitFilterType].concat(filter.values); return true; } else { - log.warn(WARN_23, [index]); + log.warn(WARN_SPLITS_FILTER_INVALID, [index]); } return false; }); @@ -125,7 +125,7 @@ export function validateSplitFilters(log: ILogger, maybeSplitFilters: any, mode: // build query string res.queryString = queryStringBuilder(res.groupedFilters); - log.debug(DEBUG_51, [res.queryString]); + log.debug(DEBUG_SPLITS_FILTER, [res.queryString]); return res; } diff --git a/src/utils/settingsValidation/storage/storageCS.ts b/src/utils/settingsValidation/storage/storageCS.ts index 0c5f4171..d8d1406c 100644 --- a/src/utils/settingsValidation/storage/storageCS.ts +++ b/src/utils/settingsValidation/storage/storageCS.ts @@ -1,7 +1,7 @@ import { InMemoryStorageCSFactory } from '../../../storages/inMemory/InMemoryStorageCS'; import { ISettings } from '../../../types'; import { ILogger } from '../../../logger/types'; -import { WARN_25 } from '../../../logger/constants'; +import { WARN_STORAGE_INVALID } from '../../../logger/constants'; /** * This function validates `settings.storage` object @@ -17,7 +17,7 @@ export function validateStorageCS(settings: { log: ILogger, storage?: any }): IS // @TODO validate its API (Splits cache, MySegments cache, etc) when supporting custom storages if (storage) { if (typeof storage === 'function') return storage; - log.warn(WARN_25); + log.warn(WARN_STORAGE_INVALID); } // return default InMemory storage if provided one is not valid