@splitsoftware/splitio-commons 2.6.0 → 2.6.1-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGES.txt +3 -0
  2. package/cjs/listeners/browser.js +1 -0
  3. package/cjs/logger/constants.js +4 -3
  4. package/cjs/logger/index.js +18 -0
  5. package/cjs/logger/messages/info.js +1 -0
  6. package/cjs/logger/sdkLogger.js +7 -0
  7. package/cjs/storages/inMemory/EventsCacheInMemory.js +1 -0
  8. package/cjs/storages/inMemory/ImpressionCountsCacheInMemory.js +1 -0
  9. package/cjs/storages/inMemory/ImpressionsCacheInMemory.js +1 -0
  10. package/cjs/storages/inMemory/TelemetryCacheInMemory.js +1 -0
  11. package/cjs/storages/inMemory/UniqueKeysCacheInMemory.js +1 -0
  12. package/cjs/storages/inMemory/UniqueKeysCacheInMemoryCS.js +1 -0
  13. package/cjs/sync/submitters/eventsSubmitter.js +2 -3
  14. package/cjs/sync/submitters/impressionCountsSubmitter.js +1 -1
  15. package/cjs/sync/submitters/impressionsSubmitter.js +2 -4
  16. package/cjs/sync/submitters/submitter.js +2 -1
  17. package/cjs/sync/submitters/telemetrySubmitter.js +3 -2
  18. package/cjs/sync/submitters/uniqueKeysSubmitter.js +2 -3
  19. package/cjs/utils/settingsValidation/logger/builtinLogger.js +2 -1
  20. package/cjs/utils/settingsValidation/logger/commons.js +12 -15
  21. package/cjs/utils/settingsValidation/logger/pluggableLogger.js +7 -4
  22. package/esm/listeners/browser.js +2 -1
  23. package/esm/logger/constants.js +1 -0
  24. package/esm/logger/index.js +18 -0
  25. package/esm/logger/messages/info.js +1 -0
  26. package/esm/logger/sdkLogger.js +7 -0
  27. package/esm/storages/inMemory/EventsCacheInMemory.js +1 -0
  28. package/esm/storages/inMemory/ImpressionCountsCacheInMemory.js +1 -0
  29. package/esm/storages/inMemory/ImpressionsCacheInMemory.js +1 -0
  30. package/esm/storages/inMemory/TelemetryCacheInMemory.js +1 -0
  31. package/esm/storages/inMemory/UniqueKeysCacheInMemory.js +1 -0
  32. package/esm/storages/inMemory/UniqueKeysCacheInMemoryCS.js +1 -0
  33. package/esm/sync/submitters/eventsSubmitter.js +2 -3
  34. package/esm/sync/submitters/impressionCountsSubmitter.js +1 -1
  35. package/esm/sync/submitters/impressionsSubmitter.js +2 -4
  36. package/esm/sync/submitters/submitter.js +2 -1
  37. package/esm/sync/submitters/telemetrySubmitter.js +3 -2
  38. package/esm/sync/submitters/uniqueKeysSubmitter.js +2 -3
  39. package/esm/utils/settingsValidation/logger/builtinLogger.js +2 -1
  40. package/esm/utils/settingsValidation/logger/commons.js +10 -14
  41. package/esm/utils/settingsValidation/logger/pluggableLogger.js +8 -5
  42. package/package.json +1 -1
  43. package/src/listeners/browser.ts +2 -1
  44. package/src/logger/constants.ts +1 -0
  45. package/src/logger/index.ts +19 -0
  46. package/src/logger/messages/info.ts +1 -0
  47. package/src/logger/sdkLogger.ts +7 -0
  48. package/src/logger/types.ts +5 -3
  49. package/src/storages/inMemory/EventsCacheInMemory.ts +1 -0
  50. package/src/storages/inMemory/ImpressionCountsCacheInMemory.ts +2 -0
  51. package/src/storages/inMemory/ImpressionsCacheInMemory.ts +1 -0
  52. package/src/storages/inMemory/TelemetryCacheInMemory.ts +2 -0
  53. package/src/storages/inMemory/UniqueKeysCacheInMemory.ts +3 -3
  54. package/src/storages/inMemory/UniqueKeysCacheInMemoryCS.ts +3 -3
  55. package/src/storages/types.ts +1 -0
  56. package/src/sync/submitters/eventsSubmitter.ts +2 -4
  57. package/src/sync/submitters/impressionCountsSubmitter.ts +1 -1
  58. package/src/sync/submitters/impressionsSubmitter.ts +2 -5
  59. package/src/sync/submitters/submitter.ts +1 -1
  60. package/src/sync/submitters/telemetrySubmitter.ts +3 -2
  61. package/src/sync/submitters/uniqueKeysSubmitter.ts +2 -3
  62. package/src/utils/settingsValidation/logger/builtinLogger.ts +3 -2
  63. package/src/utils/settingsValidation/logger/commons.ts +11 -11
  64. package/src/utils/settingsValidation/logger/pluggableLogger.ts +10 -6
  65. package/types/splitio.d.ts +22 -0
@@ -53,6 +53,7 @@ export function getTelemetryConfigStats(mode, storageType) {
53
53
  */
54
54
  export function telemetryCacheConfigAdapter(telemetry, settings) {
55
55
  return {
56
+ name: 'telemetry config',
56
57
  isEmpty: function () { return false; },
57
58
  clear: function () { },
58
59
  pop: function () {
@@ -102,7 +103,7 @@ export function telemetrySubmitterFactory(params) {
102
103
  return; // No submitter created if telemetry cache is not defined
103
104
  var settings = params.settings, _a = params.settings, log = _a.log, telemetryRefreshRate = _a.scheduler.telemetryRefreshRate, splitApi = params.splitApi, readiness = params.readiness, sdkReadinessManager = params.sdkReadinessManager;
104
105
  var startTime = timer(now);
105
- var submitter = firstPushWindowDecorator(submitterFactory(log, splitApi.postMetricsUsage, telemetry, telemetryRefreshRate, 'telemetry stats', undefined, 0, true), telemetryRefreshRate);
106
+ var submitter = firstPushWindowDecorator(submitterFactory(log, splitApi.postMetricsUsage, telemetry, telemetryRefreshRate, undefined, 0, true), telemetryRefreshRate);
106
107
  readiness.gate.once(SDK_READY_FROM_CACHE, function () {
107
108
  telemetry.recordTimeUntilReadyFromCache(startTime());
108
109
  });
@@ -111,7 +112,7 @@ export function telemetrySubmitterFactory(params) {
111
112
  telemetry.recordTimeUntilReady(startTime());
112
113
  // Post config data when the SDK is ready and if the telemetry submitter was started
113
114
  if (submitter.isRunning()) {
114
- var postMetricsConfigTask = submitterFactory(log, splitApi.postMetricsConfig, telemetryCacheConfigAdapter(telemetry, settings), 0, 'telemetry config', undefined, 0, true);
115
+ var postMetricsConfigTask = submitterFactory(log, splitApi.postMetricsConfig, telemetryCacheConfigAdapter(telemetry, settings), 0, undefined, 0, true);
115
116
  postMetricsConfigTask.execute();
116
117
  }
117
118
  });
@@ -1,6 +1,5 @@
1
1
  import { SUBMITTERS_PUSH_FULL_QUEUE } from '../../logger/constants';
2
2
  import { submitterFactory } from './submitter';
3
- var DATA_NAME = 'unique keys';
4
3
  var UNIQUE_KEYS_RATE = 900000; // 15 minutes
5
4
  /**
6
5
  * Submitter that periodically posts impression counts
@@ -9,11 +8,11 @@ export function uniqueKeysSubmitterFactory(params) {
9
8
  var _a = params.settings, log = _a.log, key = _a.core.key, _b = params.splitApi, postUniqueKeysBulkCs = _b.postUniqueKeysBulkCs, postUniqueKeysBulkSs = _b.postUniqueKeysBulkSs, uniqueKeys = params.storage.uniqueKeys;
10
9
  var isClientSide = key !== undefined;
11
10
  var postUniqueKeysBulk = isClientSide ? postUniqueKeysBulkCs : postUniqueKeysBulkSs;
12
- var syncTask = submitterFactory(log, postUniqueKeysBulk, uniqueKeys, UNIQUE_KEYS_RATE, DATA_NAME);
11
+ var syncTask = submitterFactory(log, postUniqueKeysBulk, uniqueKeys, UNIQUE_KEYS_RATE);
13
12
  // register unique keys submitter to be executed when uniqueKeys cache is full
14
13
  uniqueKeys.setOnFullQueueCb(function () {
15
14
  if (syncTask.isRunning()) {
16
- log.info(SUBMITTERS_PUSH_FULL_QUEUE, [DATA_NAME]);
15
+ log.info(SUBMITTERS_PUSH_FULL_QUEUE, [uniqueKeys.name]);
17
16
  syncTask.execute();
18
17
  }
19
18
  // If submitter is stopped (e.g., user consent declined or unknown, or app state offline), we don't send the data.
@@ -32,9 +32,10 @@ else if (isLogLevelString(initialState)) {
32
32
  * @returns a logger instance with the log level at `settings.debug`. If `settings.debug` is invalid or not provided, `initialLogLevel` is used.
33
33
  */
34
34
  export function validateLogger(settings) {
35
- var debug = settings.debug;
35
+ var debug = settings.debug, logger = settings.logger;
36
36
  var logLevel = debug !== undefined ? getLogLevel(debug) : initialLogLevel;
37
37
  var log = new Logger({ logLevel: logLevel || initialLogLevel }, allCodes);
38
+ log.setLogger(logger);
38
39
  // @ts-ignore // if logLevel is undefined at this point, it means that settings `debug` value is invalid
39
40
  if (!logLevel)
40
41
  log._log(LogLevels.ERROR, 'Invalid Log Level - No changes to the logs will be applied.');
@@ -7,18 +7,14 @@ import { LogLevels, isLogLevelString } from '../../../logger';
7
7
  * @returns LogLevel of the given debugValue or undefined if the provided value is invalid
8
8
  */
9
9
  export function getLogLevel(debugValue) {
10
- if (typeof debugValue === 'boolean') {
11
- if (debugValue) {
12
- return LogLevels.DEBUG;
13
- }
14
- else {
15
- return LogLevels.NONE;
16
- }
17
- }
18
- else if (typeof debugValue === 'string' && isLogLevelString(debugValue)) {
19
- return debugValue;
20
- }
21
- else {
22
- return undefined;
23
- }
10
+ return typeof debugValue === 'boolean' ?
11
+ debugValue ?
12
+ LogLevels.DEBUG :
13
+ LogLevels.NONE :
14
+ typeof debugValue === 'string' && isLogLevelString(debugValue) ?
15
+ debugValue :
16
+ undefined;
17
+ }
18
+ export function isLogger(log) {
19
+ return log !== null && typeof log === 'object' && typeof log.debug === 'function' && typeof log.info === 'function' && typeof log.warn === 'function' && typeof log.error === 'function';
24
20
  }
@@ -1,7 +1,7 @@
1
1
  import { Logger, LogLevels } from '../../../logger';
2
- import { getLogLevel } from './commons';
3
- function isLogger(log) {
4
- return log !== null && typeof log === 'object' && typeof log.debug === 'function' && typeof log.info === 'function' && typeof log.warn === 'function' && typeof log.error === 'function' && typeof log.setLogLevel === 'function';
2
+ import { getLogLevel, isLogger } from './commons';
3
+ function isILogger(log) {
4
+ return isLogger(log) && typeof log.setLogLevel === 'function';
5
5
  }
6
6
  // By default it starts disabled.
7
7
  var initialLogLevel = LogLevels.NONE;
@@ -13,14 +13,17 @@ var initialLogLevel = LogLevels.NONE;
13
13
  * or one with NONE log level if `debug` is not defined or invalid.
14
14
  */
15
15
  export function validateLogger(settings) {
16
- var debug = settings.debug;
16
+ var debug = settings.debug, logger = settings.logger;
17
17
  var logLevel = initialLogLevel;
18
18
  if (debug !== undefined) {
19
- if (isLogger(debug))
19
+ if (isILogger(debug)) {
20
+ debug.setLogger(logger);
20
21
  return debug;
22
+ }
21
23
  logLevel = getLogLevel(settings.debug);
22
24
  }
23
25
  var log = new Logger({ logLevel: logLevel || initialLogLevel });
26
+ log.setLogger(logger);
24
27
  // @ts-ignore // `debug` value is invalid if logLevel is undefined at this point
25
28
  if (!logLevel)
26
29
  log._log(LogLevels.ERROR, 'Invalid `debug` value at config. Logs will be disabled.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "2.6.0",
3
+ "version": "2.6.1-rc.0",
4
4
  "description": "Split JavaScript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -9,7 +9,7 @@ import { ISettings } from '../types';
9
9
  import SplitIO from '../../types/splitio';
10
10
  import { ImpressionsPayload } from '../sync/submitters/types';
11
11
  import { objectAssign } from '../utils/lang/objectAssign';
12
- import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants';
12
+ import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING, SUBMITTERS_PUSH_PAGE_HIDDEN } from '../logger/constants';
13
13
  import { ISyncManager } from '../sync/types';
14
14
  import { isConsentGranted } from '../consent';
15
15
 
@@ -104,6 +104,7 @@ export class BrowserSignalListener implements ISignalListener {
104
104
  if (!this._sendBeacon(url, dataPayload, extraMetadata)) {
105
105
  postService(JSON.stringify(dataPayload)).catch(() => { }); // no-op to handle possible promise rejection
106
106
  }
107
+ this.settings.log.debug(SUBMITTERS_PUSH_PAGE_HIDDEN, [cache.name]);
107
108
  }
108
109
  }
109
110
 
@@ -55,6 +55,7 @@ export const IMPRESSIONS_TRACKER_SUCCESS = 121;
55
55
  export const USER_CONSENT_UPDATED = 122;
56
56
  export const USER_CONSENT_NOT_UPDATED = 123;
57
57
  export const USER_CONSENT_INITIAL = 124;
58
+ export const SUBMITTERS_PUSH_PAGE_HIDDEN = 125;
58
59
 
59
60
  export const ENGINE_VALUE_INVALID = 200;
60
61
  export const ENGINE_VALUE_NO_ATTRIBUTES = 201;
@@ -2,6 +2,7 @@ import { objectAssign } from '../utils/lang/objectAssign';
2
2
  import { ILoggerOptions, ILogger } from './types';
3
3
  import { find, isObject } from '../utils/lang';
4
4
  import SplitIO from '../../types/splitio';
5
+ import { isLogger } from '../utils/settingsValidation/logger/commons';
5
6
 
6
7
  export const LogLevels: SplitIO.ILoggerAPI['LogLevel'] = {
7
8
  DEBUG: 'DEBUG',
@@ -48,6 +49,7 @@ export class Logger implements ILogger {
48
49
  private options: Required<ILoggerOptions>;
49
50
  private codes: Map<number, string>;
50
51
  private logLevel: number;
52
+ private logger?: SplitIO.Logger;
51
53
 
52
54
  constructor(options?: ILoggerOptions, codes?: Map<number, string>) {
53
55
  this.options = objectAssign({}, defaultOptions, options);
@@ -60,6 +62,15 @@ export class Logger implements ILogger {
60
62
  this.logLevel = LogLevelIndexes[logLevel];
61
63
  }
62
64
 
65
+ setLogger(logger?: SplitIO.Logger) {
66
+ if (!logger || isLogger(logger)) {
67
+ this.logger = logger;
68
+ } else {
69
+ this._log(LogLevels.ERROR, 'Invalid `logger` instance. It must be an object with `debug`, `info`, `warn` and `error` methods. Defaulting to `console.log`');
70
+ this.logger = undefined;
71
+ }
72
+ }
73
+
63
74
  debug(msg: string | number, args?: any[]) {
64
75
  if (this._shouldLog(LogLevelIndexes.DEBUG)) this._log(LogLevels.DEBUG, msg, args);
65
76
  }
@@ -86,6 +97,14 @@ export class Logger implements ILogger {
86
97
 
87
98
  const formattedText = this._generateLogMessage(level, msg);
88
99
 
100
+ // Do not break on custom logger errors
101
+ if (this.logger) {
102
+ try { // @ts-expect-error
103
+ this.logger[level.toLowerCase()](formattedText);
104
+ return;
105
+ } catch (e) { /* empty */ }
106
+ }
107
+
89
108
  console.log(formattedText);
90
109
  }
91
110
 
@@ -25,6 +25,7 @@ export const codesInfo: [number, string][] = codesWarn.concat([
25
25
  [c.SYNC_SPLITS_FETCH_RETRY, c.LOG_PREFIX_SYNC_SPLITS + 'Retrying download of feature flags #%s. Reason: %s'],
26
26
  [c.SUBMITTERS_PUSH_FULL_QUEUE, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Flushing full %s queue and resetting timer.'],
27
27
  [c.SUBMITTERS_PUSH, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Pushing %s.'],
28
+ [c.SUBMITTERS_PUSH_PAGE_HIDDEN, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Flushing %s because page became hidden.'],
28
29
  [c.STREAMING_REFRESH_TOKEN, c.LOG_PREFIX_SYNC_STREAMING + 'Refreshing streaming token in %s seconds, and connecting streaming in %s seconds.'],
29
30
  [c.STREAMING_RECONNECT, c.LOG_PREFIX_SYNC_STREAMING + 'Attempting to reconnect streaming in %s seconds.'],
30
31
  [c.STREAMING_CONNECTING, c.LOG_PREFIX_SYNC_STREAMING + 'Connecting streaming.'],
@@ -30,6 +30,13 @@ export function createLoggerAPI(log: ILogger): SplitIO.ILoggerAPI {
30
30
  * @param logLevel - Custom LogLevel value.
31
31
  */
32
32
  setLogLevel,
33
+ /**
34
+ * Sets a custom logger for the SDK logs.
35
+ * @param logger - Custom logger.
36
+ */
37
+ setLogger(logger?: ILogger) {
38
+ log.setLogger(logger);
39
+ },
33
40
  /**
34
41
  * Disables all the log levels.
35
42
  */
@@ -1,12 +1,14 @@
1
1
  import SplitIO from '../../types/splitio';
2
2
 
3
3
  export interface ILoggerOptions {
4
- prefix?: string,
5
- logLevel?: SplitIO.LogLevel,
6
- showLevel?: boolean, // @TODO remove this param eventually since it is not being set `false` anymore
4
+ prefix?: string;
5
+ logLevel?: SplitIO.LogLevel;
6
+ showLevel?: boolean; // @TODO remove this param eventually since it is not being set `false` anymore
7
7
  }
8
8
 
9
9
  export interface ILogger extends SplitIO.ILogger {
10
+ setLogger(logger?: SplitIO.Logger): void;
11
+
10
12
  debug(msg: any): void;
11
13
  debug(msg: string | number, args?: any[]): void;
12
14
 
@@ -5,6 +5,7 @@ const MAX_QUEUE_BYTE_SIZE = 5 * 1024 * 1024; // 5M
5
5
 
6
6
  export class EventsCacheInMemory implements IEventsCacheSync {
7
7
 
8
+ public name = 'events';
8
9
  private onFullQueue?: () => void;
9
10
  private readonly maxQueue: number;
10
11
  private queue: SplitIO.EventData[];
@@ -3,6 +3,8 @@ import { DEFAULT_CACHE_SIZE } from '../inRedis/constants';
3
3
  import { IImpressionCountsCacheSync } from '../types';
4
4
 
5
5
  export class ImpressionCountsCacheInMemory implements IImpressionCountsCacheSync {
6
+
7
+ public name = 'impression counts';
6
8
  protected cache: Record<string, number> = {};
7
9
  private readonly maxStorage: number;
8
10
  protected onFullQueue?: () => void;
@@ -3,6 +3,7 @@ import SplitIO from '../../../types/splitio';
3
3
 
4
4
  export class ImpressionsCacheInMemory implements IImpressionsCacheSync {
5
5
 
6
+ public name = 'impressions';
6
7
  private onFullQueue?: () => void;
7
8
  private readonly maxQueue: number;
8
9
  private queue: SplitIO.ImpressionDTO[];
@@ -25,6 +25,8 @@ export function shouldRecordTelemetry({ settings }: IStorageFactoryParams) {
25
25
 
26
26
  export class TelemetryCacheInMemory implements ITelemetryCacheSync {
27
27
 
28
+ public name = 'telemetry stats';
29
+
28
30
  constructor(private splits?: ISplitsCacheSync, private segments?: ISegmentsCacheSync, private largeSegments?: ISegmentsCacheSync) { }
29
31
 
30
32
  // isEmpty flag
@@ -1,4 +1,4 @@
1
- import { IUniqueKeysCacheBase } from '../types';
1
+ import { IUniqueKeysCacheSync } from '../types';
2
2
  import { UniqueKeysPayloadSs } from '../../sync/submitters/types';
3
3
  import { DEFAULT_CACHE_SIZE } from '../inRedis/constants';
4
4
  import { setToArray } from '../../utils/lang/sets';
@@ -22,8 +22,8 @@ export function fromUniqueKeysCollector(uniqueKeys: { [featureName: string]: Set
22
22
  return { keys: payload };
23
23
  }
24
24
 
25
- export class UniqueKeysCacheInMemory implements IUniqueKeysCacheBase {
26
-
25
+ export class UniqueKeysCacheInMemory implements IUniqueKeysCacheSync {
26
+ public name = 'unique keys';
27
27
  protected onFullQueue?: () => void;
28
28
  private readonly maxStorage: number;
29
29
  private uniqueTrackerSize = 0;
@@ -1,10 +1,10 @@
1
- import { IUniqueKeysCacheBase } from '../types';
1
+ import { IUniqueKeysCacheSync } from '../types';
2
2
  import { UniqueKeysPayloadCs } from '../../sync/submitters/types';
3
3
  import { DEFAULT_CACHE_SIZE } from '../inRedis/constants';
4
4
  import { setToArray } from '../../utils/lang/sets';
5
5
 
6
- export class UniqueKeysCacheInMemoryCS implements IUniqueKeysCacheBase {
7
-
6
+ export class UniqueKeysCacheInMemoryCS implements IUniqueKeysCacheSync {
7
+ public name = 'unique keys';
8
8
  private onFullQueue?: () => void;
9
9
  private readonly maxStorage: number;
10
10
  private uniqueTrackerSize = 0;
@@ -325,6 +325,7 @@ export interface IUniqueKeysCacheBase {
325
325
 
326
326
  // API methods for sync recorder storages, used by submitters in standalone mode to pop data and post it to Split BE.
327
327
  export interface IRecorderCacheSync<T> {
328
+ name: string,
328
329
  // @TODO names are inconsistent with spec
329
330
  /* Checks if cache is empty. Returns true if the cache was just created or cleared */
330
331
  isEmpty(): boolean
@@ -2,8 +2,6 @@ import { submitterFactory, firstPushWindowDecorator } from './submitter';
2
2
  import { SUBMITTERS_PUSH_FULL_QUEUE } from '../../logger/constants';
3
3
  import { ISdkFactoryContextSync } from '../../sdkFactory/types';
4
4
 
5
- const DATA_NAME = 'events';
6
-
7
5
  /**
8
6
  * Submitter that periodically posts tracked events
9
7
  */
@@ -16,7 +14,7 @@ export function eventsSubmitterFactory(params: ISdkFactoryContextSync) {
16
14
  } = params;
17
15
 
18
16
  // don't retry events.
19
- let submitter = submitterFactory(log, postEventsBulk, events, eventsPushRate, DATA_NAME);
17
+ let submitter = submitterFactory(log, postEventsBulk, events, eventsPushRate);
20
18
 
21
19
  // Set a timer for the first push window of events.
22
20
  if (eventsFirstPushWindow > 0) submitter = firstPushWindowDecorator(submitter, eventsFirstPushWindow);
@@ -24,7 +22,7 @@ export function eventsSubmitterFactory(params: ISdkFactoryContextSync) {
24
22
  // register events submitter to be executed when events cache is full
25
23
  events.setOnFullQueueCb(() => {
26
24
  if (submitter.isRunning()) {
27
- log.info(SUBMITTERS_PUSH_FULL_QUEUE, [DATA_NAME]);
25
+ log.info(SUBMITTERS_PUSH_FULL_QUEUE, [events.name]);
28
26
  submitter.execute();
29
27
  }
30
28
  // If submitter is stopped (e.g., user consent declined or unknown, or app state offline), we don't send the data.
@@ -40,5 +40,5 @@ export function impressionCountsSubmitterFactory(params: ISdkFactoryContextSync)
40
40
  } = params;
41
41
 
42
42
  // retry impressions counts only once.
43
- return submitterFactory(log, postTestImpressionsCount, impressionCounts, IMPRESSIONS_COUNT_RATE, 'impression counts', fromImpressionCountsCollector, 1);
43
+ return submitterFactory(log, postTestImpressionsCount, impressionCounts, IMPRESSIONS_COUNT_RATE, fromImpressionCountsCollector, 1);
44
44
  }
@@ -5,8 +5,6 @@ import { ImpressionsPayload } from './types';
5
5
  import { SUBMITTERS_PUSH_FULL_QUEUE } from '../../logger/constants';
6
6
  import { ISdkFactoryContextSync } from '../../sdkFactory/types';
7
7
 
8
- const DATA_NAME = 'impressions';
9
-
10
8
  /**
11
9
  * Converts `impressions` data from cache into request payload.
12
10
  */
@@ -14,7 +12,6 @@ export function fromImpressionsCollector(sendLabels: boolean, data: SplitIO.Impr
14
12
  let groupedByFeature = groupBy(data, 'feature');
15
13
  let dto: ImpressionsPayload = [];
16
14
 
17
- // using forOwn instead of for...in since the last also iterates over prototype enumerables
18
15
  forOwn(groupedByFeature, (value, name) => {
19
16
  dto.push({
20
17
  f: name, // Test Name
@@ -50,12 +47,12 @@ export function impressionsSubmitterFactory(params: ISdkFactoryContextSync) {
50
47
  } = params;
51
48
 
52
49
  // retry impressions only once.
53
- const syncTask = submitterFactory(log, postTestImpressionsBulk, impressions, impressionsRefreshRate, DATA_NAME, fromImpressionsCollector.bind(undefined, labelsEnabled), 1);
50
+ const syncTask = submitterFactory(log, postTestImpressionsBulk, impressions, impressionsRefreshRate, fromImpressionsCollector.bind(undefined, labelsEnabled), 1);
54
51
 
55
52
  // register impressions submitter to be executed when impressions cache is full
56
53
  impressions.setOnFullQueueCb(() => {
57
54
  if (syncTask.isRunning()) {
58
- log.info(SUBMITTERS_PUSH_FULL_QUEUE, [DATA_NAME]);
55
+ log.info(SUBMITTERS_PUSH_FULL_QUEUE, [impressions.name]);
59
56
  syncTask.execute();
60
57
  }
61
58
  // If submitter is stopped (e.g., user consent declined or unknown, or app state offline), we don't send the data.
@@ -13,12 +13,12 @@ export function submitterFactory<T>(
13
13
  postClient: (body: string) => Promise<IResponse>,
14
14
  sourceCache: IRecorderCacheSync<T>,
15
15
  postRate: number,
16
- dataName: string,
17
16
  fromCacheToPayload?: (cacheData: T) => any,
18
17
  maxRetries: number = 0,
19
18
  debugLogs?: boolean // true for telemetry submitters
20
19
  ): ISyncTask<[], void> {
21
20
 
21
+ const dataName = sourceCache.name;
22
22
  let retries = 0;
23
23
  let data: any;
24
24
 
@@ -66,6 +66,7 @@ export function getTelemetryConfigStats(mode: SplitIO.SDKMode, storageType: stri
66
66
  */
67
67
  export function telemetryCacheConfigAdapter(telemetry: ITelemetryCacheSync, settings: ISettings) {
68
68
  return {
69
+ name: 'telemetry config',
69
70
  isEmpty() { return false; },
70
71
  clear() { },
71
72
 
@@ -124,7 +125,7 @@ export function telemetrySubmitterFactory(params: ISdkFactoryContextSync) {
124
125
  submitterFactory(
125
126
  log, splitApi.postMetricsUsage,
126
127
  telemetry,
127
- telemetryRefreshRate, 'telemetry stats', undefined, 0, true
128
+ telemetryRefreshRate, undefined, 0, true
128
129
  ),
129
130
  telemetryRefreshRate
130
131
  );
@@ -139,7 +140,7 @@ export function telemetrySubmitterFactory(params: ISdkFactoryContextSync) {
139
140
 
140
141
  // Post config data when the SDK is ready and if the telemetry submitter was started
141
142
  if (submitter.isRunning()) {
142
- const postMetricsConfigTask = submitterFactory(log, splitApi.postMetricsConfig, telemetryCacheConfigAdapter(telemetry, settings), 0, 'telemetry config', undefined, 0, true);
143
+ const postMetricsConfigTask = submitterFactory(log, splitApi.postMetricsConfig, telemetryCacheConfigAdapter(telemetry, settings), 0, undefined, 0, true);
143
144
  postMetricsConfigTask.execute();
144
145
  }
145
146
  });
@@ -2,7 +2,6 @@ import { SUBMITTERS_PUSH_FULL_QUEUE } from '../../logger/constants';
2
2
  import { ISdkFactoryContextSync } from '../../sdkFactory/types';
3
3
  import { submitterFactory } from './submitter';
4
4
 
5
- const DATA_NAME = 'unique keys';
6
5
  const UNIQUE_KEYS_RATE = 900000; // 15 minutes
7
6
 
8
7
  /**
@@ -19,12 +18,12 @@ export function uniqueKeysSubmitterFactory(params: ISdkFactoryContextSync) {
19
18
  const isClientSide = key !== undefined;
20
19
  const postUniqueKeysBulk = isClientSide ? postUniqueKeysBulkCs : postUniqueKeysBulkSs;
21
20
 
22
- const syncTask = submitterFactory(log, postUniqueKeysBulk, uniqueKeys, UNIQUE_KEYS_RATE, DATA_NAME);
21
+ const syncTask = submitterFactory(log, postUniqueKeysBulk, uniqueKeys, UNIQUE_KEYS_RATE);
23
22
 
24
23
  // register unique keys submitter to be executed when uniqueKeys cache is full
25
24
  uniqueKeys.setOnFullQueueCb(() => {
26
25
  if (syncTask.isRunning()) {
27
- log.info(SUBMITTERS_PUSH_FULL_QUEUE, [DATA_NAME]);
26
+ log.info(SUBMITTERS_PUSH_FULL_QUEUE, [uniqueKeys.name]);
28
27
  syncTask.execute();
29
28
  }
30
29
  // If submitter is stopped (e.g., user consent declined or unknown, or app state offline), we don't send the data.
@@ -40,12 +40,13 @@ if (/^(enabled?|on)/i.test(initialState)) {
40
40
  * @param settings - user config object, with an optional `debug` property of type boolean or string log level.
41
41
  * @returns a logger instance with the log level at `settings.debug`. If `settings.debug` is invalid or not provided, `initialLogLevel` is used.
42
42
  */
43
- export function validateLogger(settings: { debug: unknown }): ILogger {
44
- const { debug } = settings;
43
+ export function validateLogger(settings: { debug: unknown, logger?: SplitIO.Logger }): ILogger {
44
+ const { debug, logger } = settings;
45
45
 
46
46
  const logLevel: SplitIO.LogLevel | undefined = debug !== undefined ? getLogLevel(debug) : initialLogLevel;
47
47
 
48
48
  const log = new Logger({ logLevel: logLevel || initialLogLevel }, allCodes);
49
+ log.setLogger(logger);
49
50
 
50
51
  // @ts-ignore // if logLevel is undefined at this point, it means that settings `debug` value is invalid
51
52
  if (!logLevel) log._log(LogLevels.ERROR, 'Invalid Log Level - No changes to the logs will be applied.');
@@ -10,15 +10,15 @@ import SplitIO from '../../../../types/splitio';
10
10
  * @returns LogLevel of the given debugValue or undefined if the provided value is invalid
11
11
  */
12
12
  export function getLogLevel(debugValue: unknown): SplitIO.LogLevel | undefined {
13
- if (typeof debugValue === 'boolean') {
14
- if (debugValue) {
15
- return LogLevels.DEBUG;
16
- } else {
17
- return LogLevels.NONE;
18
- }
19
- } else if (typeof debugValue === 'string' && isLogLevelString(debugValue)) {
20
- return debugValue;
21
- } else {
22
- return undefined;
23
- }
13
+ return typeof debugValue === 'boolean' ?
14
+ debugValue ?
15
+ LogLevels.DEBUG :
16
+ LogLevels.NONE :
17
+ typeof debugValue === 'string' && isLogLevelString(debugValue) ?
18
+ debugValue :
19
+ undefined;
20
+ }
21
+
22
+ export function isLogger(log: any): log is SplitIO.Logger {
23
+ return log !== null && typeof log === 'object' && typeof log.debug === 'function' && typeof log.info === 'function' && typeof log.warn === 'function' && typeof log.error === 'function';
24
24
  }
@@ -1,10 +1,10 @@
1
1
  import { Logger, LogLevels } from '../../../logger';
2
2
  import { ILogger } from '../../../logger/types';
3
3
  import SplitIO from '../../../../types/splitio';
4
- import { getLogLevel } from './commons';
4
+ import { getLogLevel, isLogger } from './commons';
5
5
 
6
- function isLogger(log: any): log is ILogger {
7
- return log !== null && typeof log === 'object' && typeof log.debug === 'function' && typeof log.info === 'function' && typeof log.warn === 'function' && typeof log.error === 'function' && typeof log.setLogLevel === 'function';
6
+ function isILogger(log: any): log is ILogger {
7
+ return isLogger(log) && typeof (log as any).setLogLevel === 'function';
8
8
  }
9
9
 
10
10
  // By default it starts disabled.
@@ -17,16 +17,20 @@ let initialLogLevel = LogLevels.NONE;
17
17
  * @returns a logger instance, that might be: the provided logger at `settings.debug`, or one with the given `debug` log level,
18
18
  * or one with NONE log level if `debug` is not defined or invalid.
19
19
  */
20
- export function validateLogger(settings: { debug: unknown }): ILogger {
21
- const { debug } = settings;
20
+ export function validateLogger(settings: { debug: unknown, logger?: SplitIO.Logger }): ILogger {
21
+ const { debug, logger } = settings;
22
22
  let logLevel: SplitIO.LogLevel | undefined = initialLogLevel;
23
23
 
24
24
  if (debug !== undefined) {
25
- if (isLogger(debug)) return debug;
25
+ if (isILogger(debug)) {
26
+ debug.setLogger(logger);
27
+ return debug;
28
+ }
26
29
  logLevel = getLogLevel(settings.debug);
27
30
  }
28
31
 
29
32
  const log = new Logger({ logLevel: logLevel || initialLogLevel });
33
+ log.setLogger(logger);
30
34
 
31
35
  // @ts-ignore // `debug` value is invalid if logLevel is undefined at this point
32
36
  if (!logLevel) log._log(LogLevels.ERROR, 'Invalid `debug` value at config. Logs will be disabled.');
@@ -91,6 +91,10 @@ interface ISharedSettings {
91
91
  * Do not change these settings unless you're working an advanced use case, like connecting to the Split proxy.
92
92
  */
93
93
  urls?: SplitIO.UrlSettings;
94
+ /**
95
+ * Custom logger object. If not provided, the SDK will use the default `console.log` method for all log levels.
96
+ */
97
+ logger?: SplitIO.Logger;
94
98
  }
95
99
  /**
96
100
  * Common settings properties for SDKs with synchronous API (standalone and localhost modes).
@@ -587,6 +591,7 @@ declare namespace SplitIO {
587
591
  telemetry: string;
588
592
  };
589
593
  readonly integrations?: IntegrationFactory[];
594
+ readonly logger?: Logger;
590
595
  readonly debug: boolean | LogLevel | ILogger;
591
596
  readonly version: string;
592
597
  /**
@@ -617,6 +622,15 @@ declare namespace SplitIO {
617
622
  * Log levels.
618
623
  */
619
624
  type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'NONE';
625
+ /**
626
+ * Custom logger interface.
627
+ */
628
+ interface Logger {
629
+ debug(message: string): any;
630
+ info(message: string): any;
631
+ warn(message: string): any;
632
+ error(message: string): any;
633
+ }
620
634
  /**
621
635
  * Logger API
622
636
  */
@@ -631,8 +645,16 @@ declare namespace SplitIO {
631
645
  disable(): void;
632
646
  /**
633
647
  * Sets a log level for the SDK logs.
648
+ *
649
+ * @param logLevel - The log level to set.
634
650
  */
635
651
  setLogLevel(logLevel: LogLevel): void;
652
+ /**
653
+ * Sets a custom logger for the SDK logs.
654
+ *
655
+ * @param logger - The custom logger to set, or `undefined` to remove the custom logger and fall back to the default `console.log` method.
656
+ */
657
+ setLogger(logger?: Logger): void;
636
658
  /**
637
659
  * Log level constants. Use this to pass them to setLogLevel function.
638
660
  */