Skip to content

Commit

Permalink
Add Init Request Retries Into StatsigOptions (#470)
Browse files Browse the repository at this point in the history
Added `initRequestRetries` into StatsigOptions so that customers can
define how many times they wanna retry themselves.

Additionally, observing that other logs in the codebase start with
`[Statsig]`, modifications were made accordingly to align with this
pattern.
  • Loading branch information
weihao-statsig authored Apr 2, 2024
1 parent cb9ee74 commit d440265
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 20 deletions.
16 changes: 8 additions & 8 deletions src/StatsigClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -771,13 +771,13 @@ export default class StatsigClient implements IHasStatsigInternal, IStatsig {
}
if (typeof eventName !== 'string' || eventName.length === 0) {
OutputLogger.error(
'statsigSDK> Event not logged. No valid eventName passed.',
'Event not logged. No valid eventName passed.',
);
return;
}
if (this.shouldTrimParam(eventName, MAX_VALUE_SIZE)) {
OutputLogger.info(
'statsigSDK> eventName is too long, trimming to ' +
'eventName is too long, trimming to ' +
MAX_VALUE_SIZE +
' characters.',
);
Expand All @@ -788,12 +788,12 @@ export default class StatsigClient implements IHasStatsigInternal, IStatsig {
this.shouldTrimParam(value, MAX_VALUE_SIZE)
) {
OutputLogger.info(
'statsigSDK> value is too long, trimming to ' + MAX_VALUE_SIZE + '.',
'value is too long, trimming to ' + MAX_VALUE_SIZE + '.',
);
value = value.substring(0, MAX_VALUE_SIZE);
}
if (this.shouldTrimParam(metadata, MAX_OBJ_SIZE)) {
OutputLogger.info('statsigSDK> metadata is too big. Dropping the metadata.');
OutputLogger.info('metadata is too big. Dropping the metadata.');
metadata = { error: 'not logged due to size too large' };
}
const event = new LogEvent(eventName);
Expand Down Expand Up @@ -1322,20 +1322,20 @@ export default class StatsigClient implements IHasStatsigInternal, IStatsig {
}
if (this.shouldTrimParam(user.userID ?? null, MAX_VALUE_SIZE)) {
OutputLogger.info(
'statsigSDK> User ID is too large, trimming to ' + MAX_VALUE_SIZE + 'characters',
'User ID is too large, trimming to ' + MAX_VALUE_SIZE + 'characters',
);
user.userID = user.userID?.toString().substring(0, MAX_VALUE_SIZE);
}
if (this.shouldTrimParam(user, MAX_OBJ_SIZE)) {
user.custom = {};
if (this.shouldTrimParam(user, MAX_OBJ_SIZE)) {
OutputLogger.info(
'statsigSDK> User object is too large, only keeping the user ID.',
'User object is too large, only keeping the user ID.',
);
user = { userID: user.userID };
} else {
OutputLogger.info(
'statsigSDK> User object is too large, dropping the custom property.',
'User object is too large, dropping the custom property.',
);
}
}
Expand Down Expand Up @@ -1366,7 +1366,7 @@ export default class StatsigClient implements IHasStatsigInternal, IStatsig {
const prefetchUsers = args.prefetchUsers ?? [];
const timeout = args.timeout ?? this.options.getInitTimeoutMs();
if (prefetchUsers.length > 5) {
OutputLogger.info('statsigSDK> Cannot prefetch more than 5 users.');
OutputLogger.info('Cannot prefetch more than 5 users.');
}

const keyedPrefetchUsers = this.normalizePrefetchUsers(prefetchUsers)
Expand Down
4 changes: 2 additions & 2 deletions src/StatsigLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ export default class StatsigLogger {
): void {
this.logGenericEvent(DEFAULT_VALUE_WARNING, user, message, metadata);
this.loggedErrors.add(message);
OutputLogger.error(`statsigSDK> ${message}`);
OutputLogger.error(message);
}

public logAppError(
Expand Down Expand Up @@ -567,7 +567,7 @@ export default class StatsigLogger {
}
}
} catch (e) {
OutputLogger.error("statsigSDK> sendSavedRequests ", e as Error);
OutputLogger.error("sendSavedRequests ", e as Error);
this.sdkInternal.getErrorBoundary().logError('sendSavedRequests', e);
} finally {
this.clearLocalStorageRequests();
Expand Down
8 changes: 4 additions & 4 deletions src/StatsigNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export default class StatsigNetwork {

return this.postWithTimeout(StatsigEndpoint.Initialize, input, {
timeout,
retries: 3,
retries: this.sdkInternal.getOptions().getInitRequestRetries(),
diagnostics: Diagnostics.mark.initialize.networkRequest,
});
}
Expand Down Expand Up @@ -161,7 +161,7 @@ export default class StatsigNetwork {
res = localRes;
if (!res.ok) {
const errorMessage = `Request to ${endpointName} failed with status ${res.status}`;
OutputLogger.error(`statsigSDK> ${errorMessage}`);
OutputLogger.error(errorMessage);
return Promise.reject(
new Error(
errorMessage,
Expand All @@ -171,7 +171,7 @@ export default class StatsigNetwork {

if (typeof res.data !== 'object') {
const errorMessage = `Request to ${endpointName} received invalid response type. Expected 'object' but got '${typeof res.data}'`;
OutputLogger.error(`statsigSDK> ${errorMessage}`);
OutputLogger.error(errorMessage);
const error = new Error(
errorMessage,
);
Expand Down Expand Up @@ -370,7 +370,7 @@ export default class StatsigNetwork {
})
.catch((e) => {
diagnostics?.end(this.getDiagnosticsData(res, attempt, e));
const errorMessage = `statsigSDK> Error occurred while posting to endpoint: ${e.message}\n` +
const errorMessage = `Error occurred while posting to endpoint: ${e.message}\n` +
`Error Details: ${JSON.stringify(e)}\n` +
`Endpoint: ${endpointName}\n` +
`Attempt: ${attempt}\n` +
Expand Down
8 changes: 8 additions & 0 deletions src/StatsigSDKOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { StatsigUser } from './StatsigUser';

const DEFAULT_FEATURE_GATE_API = 'https://featuregates.org/v1/';
const DEFAULT_EVENT_LOGGING_API = 'https://events.statsigapi.net/v1/';
const DEFAULT_INIT_NETWORK_RETRIES = 3;

export const INIT_TIMEOUT_DEFAULT_MS = 3000;

Expand Down Expand Up @@ -56,6 +57,7 @@ export type StatsigOptions = {
disableLocalOverrides?: boolean;
disableLocalStorage?: boolean;
disableNetworkKeepalive?: boolean;
initRequestRetries?: number;
environment?: StatsigEnvironment;
eventLoggingApi?: string;
fetchMode?: FetchMode;
Expand Down Expand Up @@ -111,6 +113,7 @@ export default class StatsigSDKOptions {
private disableLocalOverrides: boolean;
private disableLocalStorage: boolean;
private disableNetworkKeepalive: boolean;
private initRequestRetries: number;
private environment: StatsigEnvironment | null;
private eventLoggingApi: string;
private fetchMode: FetchMode;
Expand Down Expand Up @@ -159,6 +162,7 @@ export default class StatsigSDKOptions {
);

this.disableNetworkKeepalive = options.disableNetworkKeepalive ?? false;
this.initRequestRetries = options.initRequestRetries ?? DEFAULT_INIT_NETWORK_RETRIES;
this.overrideStableID = options.overrideStableID ?? null;
this.localMode = options.localMode ?? false;
this.initTimeoutMs =
Expand Down Expand Up @@ -340,6 +344,10 @@ export default class StatsigSDKOptions {
return this.disableHashing;
}

getInitRequestRetries(): number {
return this.initRequestRetries;
}

isAllLoggingDisabled(): boolean {
return this.disableAllLogging;
}
Expand Down
54 changes: 54 additions & 0 deletions src/__tests__/InitRequestRetry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import Statsig from '../index';

describe('Statsig Initialization Retry Logic', () => {
const DEFAULT_INIT_NETWORK_RETRIES = 3;
let initCalledTimes = 0;
let backoff = 1000;
jest.setTimeout(1000000);

beforeEach(async () => {
initCalledTimes = 0;
global.window = {} as any;
// @ts-ignore
global.fetch = jest.fn((url, params: any) => {
if (url.toString().includes('/initialize')) {
initCalledTimes++;
}
return Promise.resolve({
ok: false,
status: 500, // retryable code
text: () => Promise.resolve('error!'),
})
});
});

afterEach(() => {
delete (global as any).window;
Statsig.shutdown();
})

test('Should retry on failure with retryable status code when retries option is not customized', async () => {
await Statsig.initialize('client-key', { userID: 'whd' }, { initTimeoutMs: 9999 });
jest.advanceTimersByTime(backoff * 2); // Simulate backoff period
expect(initCalledTimes).toEqual(DEFAULT_INIT_NETWORK_RETRIES); // default retry is 3
});

test('Should not retry on failure when retries are setted to be 0', async () => {
// Initialize Statsig with retries customized
await Statsig.initialize('client-key', { userID: 'whd' }, { initRequestRetries: 0 });
expect(initCalledTimes).toEqual(1);
});

test('Should not retry on failure when retries option is customized', async () => {
// Initialize Statsig with retries customized
await Statsig.initialize('client-key', { userID: 'whd' }, { initRequestRetries: 4, initTimeoutMs: 9999 });
jest.advanceTimersByTime(backoff * 2); // Simulate backoff period
expect(initCalledTimes).toEqual(4);
});

test('should only retry once if negative retries be passed in', async () => {
await Statsig.initialize('client-key', { userID: 'whd' }, { initRequestRetries: -4, initTimeoutMs: 9999 });
jest.advanceTimersByTime(backoff * 2);
expect(initCalledTimes).toEqual(1);
});
});
4 changes: 2 additions & 2 deletions src/__tests__/OutputLogger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ describe('Output logger Interface', () => {
client.logEvent('');
if (level === LogLevel.ERROR) {
expect(errors).toContainEqual(
'statsigSDK> Event not logged. No valid eventName passed.',
'[Statsig] Event not logged. No valid eventName passed.',
);
}

client.logEvent('a'.repeat(100));
if (level == LogLevel.INFO) {
expect(infos).toContainEqual(
'statsigSDK> eventName is too long, trimming to ' + MAX_VALUE_SIZE + ' characters.',
'[Statsig] eventName is too long, trimming to ' + MAX_VALUE_SIZE + ' characters.',
);
}
}
Expand Down
10 changes: 6 additions & 4 deletions src/utils/OutputLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,34 @@ import { LoggerInterface, LogLevel } from '../StatsigSDKOptions';
let _logger: LoggerInterface = console;
let _logLevel: LogLevel = LogLevel.WARN;
export default abstract class OutputLogger {
private static readonly LOG_PREFIX = "[Statsig]";

static getLogger(): LoggerInterface {
return _logger;

}

static debug(message?: any, ...optionalParams: any[]) {
if (_logLevel !== LogLevel.NONE) {
_logger.debug && _logger.debug(message, ...optionalParams);
_logger.debug && _logger.debug(`${this.LOG_PREFIX} ${message}`, ...optionalParams);
}
}

static info(message?: any, ...optionalParams: any[]) {
if (_logLevel === LogLevel.INFO) {
_logger.info(message, ...optionalParams);
_logger.info(`${this.LOG_PREFIX} ${message}`, ...optionalParams);
}
}

static warn(message?: any, ...optionalParams: any[]) {
if (_logLevel === LogLevel.WARN) {
_logger.warn(message, ...optionalParams);
_logger.warn(`${this.LOG_PREFIX} ${message}`, ...optionalParams);
}
}

static error(message?: any, ...optionalParams: any[]) {
if (_logLevel === LogLevel.ERROR) {
_logger.error(message, ...optionalParams);
_logger.error(`${this.LOG_PREFIX} ${message}`, ...optionalParams);
}
}

Expand Down

0 comments on commit d440265

Please sign in to comment.