Skip to content

Commit 0bc57bf

Browse files
committed
Merge branch 'main' into web/subdomain-redirect-poc
2 parents 0a45d29 + 8286c5b commit 0bc57bf

8 files changed

Lines changed: 332 additions & 18 deletions

File tree

packages/experiment-browser/src/config.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { FetchHttpClient } from './transport/http';
22
import { ExperimentAnalyticsProvider } from './types/analytics';
33
import { ExposureTrackingProvider } from './types/exposure';
4+
import { Logger, LogLevel } from './types/logger';
45
import { ExperimentUserProvider } from './types/provider';
56
import { Source } from './types/source';
67
import { HttpClient } from './types/transport';
@@ -13,9 +14,23 @@ export interface ExperimentConfig {
1314
/**
1415
* Debug all assignment requests in the UI Debugger and log additional
1516
* information to the console. This should be false for production builds.
17+
* @deprecated Use logLevel instead. When debug is true, it sets logLevel to Debug.
1618
*/
1719
debug?: boolean;
1820

21+
/**
22+
* The minimum log level to output. Messages below this level will be ignored.
23+
* Supported levels: Disable, Error (default), Warn, Info, Debug, Verbose.
24+
* If the deprecated debug flag is set to true, this will default to Debug.
25+
*/
26+
logLevel?: LogLevel;
27+
28+
/**
29+
* Custom logger implementation. If not provided, a default ConsoleLogger will be used.
30+
* The logger must implement the Logger interface with methods for error, warn, info, debug, and verbose.
31+
*/
32+
loggerProvider?: Logger;
33+
1934
/**
2035
* The name of the instance being initialized. Used for initializing separate
2136
* instances of experiment or linking the experiment SDK to a specific
@@ -165,6 +180,8 @@ export interface ExperimentConfig {
165180
| **Option** | **Default** |
166181
|------------------|-----------------------------------|
167182
| **debug** | `false` |
183+
| **logLevel** | `LogLevel.Error` |
184+
| **logger** | `null` (ConsoleLogger will be used) |
168185
| **instanceName** | `$default_instance` |
169186
| **fallbackVariant** | `null` |
170187
| **initialVariants** | `null` |
@@ -189,6 +206,8 @@ export interface ExperimentConfig {
189206
*/
190207
export const Defaults: ExperimentConfig = {
191208
debug: false,
209+
logLevel: LogLevel.Error,
210+
loggerProvider: null,
192211
instanceName: '$default_instance',
193212
fallbackVariant: {},
194213
initialVariants: {},

packages/experiment-browser/src/experimentClient.ts

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import { version as PACKAGE_VERSION } from '../package.json';
2121

2222
import { Defaults, ExperimentConfig } from './config';
2323
import { IntegrationManager } from './integration/manager';
24+
import { AmpLogger } from './logger/ampLogger';
25+
import { ConsoleLogger } from './logger/consoleLogger';
2426
import {
2527
getFlagStorage,
2628
getVariantStorage,
@@ -33,6 +35,7 @@ import { FetchHttpClient, WrapperClient } from './transport/http';
3335
import { exposureEvent } from './types/analytics';
3436
import { Client, FetchOptions } from './types/client';
3537
import { Exposure, ExposureTrackingProvider } from './types/exposure';
38+
import { LogLevel } from './types/logger';
3639
import { ExperimentPlugin, IntegrationPlugin } from './types/plugin';
3740
import { ExperimentUserProvider } from './types/provider';
3841
import { isFallback, Source, VariantSource } from './types/source';
@@ -75,6 +78,7 @@ const euFlagsServerUrl = 'https://flag.lab.eu.amplitude.com';
7578
export class ExperimentClient implements Client {
7679
private readonly apiKey: string;
7780
private readonly config: ExperimentConfig;
81+
private readonly logger: AmpLogger;
7882
private readonly variants: LoadStoreCache<Variant>;
7983
private readonly flags: LoadStoreCache<EvaluationFlag>;
8084
private readonly flagApi: FlagApi;
@@ -128,6 +132,10 @@ export class ExperimentClient implements Client {
128132
: config.flagConfigPollingIntervalMillis ??
129133
Defaults.flagConfigPollingIntervalMillis,
130134
};
135+
this.logger = new AmpLogger(
136+
this.config.loggerProvider || new ConsoleLogger(),
137+
ExperimentClient.getLogLevel(config),
138+
);
131139
const internalInstanceName = this.config?.['internalInstanceNameSuffix'];
132140
this.isWebExperiment = internalInstanceName === 'web';
133141
this.poller = new Poller(
@@ -297,12 +305,10 @@ export class ExperimentClient implements Client {
297305
}
298306

299307
// Otherwise, handle errors silently as before
300-
if (this.config.debug) {
301-
if (e instanceof TimeoutError) {
302-
console.debug(e);
303-
} else {
304-
console.error(e);
305-
}
308+
if (e instanceof TimeoutError) {
309+
this.logger.debug(e);
310+
} else {
311+
this.logger.error(e);
306312
}
307313
}
308314
return this;
@@ -332,7 +338,7 @@ export class ExperimentClient implements Client {
332338
if (this.config.automaticExposureTracking) {
333339
this.exposureInternal(key, sourceVariant);
334340
}
335-
this.debug(
341+
this.logger.debug(
336342
`[Experiment] variant for ${key} is ${
337343
sourceVariant.variant?.key || sourceVariant.variant?.value
338344
}`,
@@ -697,7 +703,7 @@ export class ExperimentClient implements Client {
697703
throw Error('Experiment API key is empty');
698704
}
699705

700-
this.debug(`[Experiment] Fetch all: retry=${retry}`);
706+
this.logger.debug(`[Experiment] Fetch all: retry=${retry}`);
701707

702708
// Proactively cancel retries if active in order to avoid unnecessary API
703709
// requests. A new failure will restart the retries.
@@ -730,7 +736,7 @@ export class ExperimentClient implements Client {
730736
): Promise<Variants> {
731737
user = await this.addContextOrWait(user);
732738
user = this.cleanUserPropsForFetch(user);
733-
this.debug('[Experiment] Fetch variants for user: ', user);
739+
this.logger.debug('[Experiment] Fetch variants for user: ', user);
734740
const results = await this.evaluationApi.getVariants(user, {
735741
timeoutMillis: timeoutMillis,
736742
...options,
@@ -739,7 +745,7 @@ export class ExperimentClient implements Client {
739745
for (const key of Object.keys(results)) {
740746
variants[key] = convertEvaluationVariantToVariant(results[key]);
741747
}
742-
this.debug('[Experiment] Received variants: ', variants);
748+
this.logger.debug('[Experiment] Received variants: ', variants);
743749
return variants;
744750
}
745751

@@ -763,7 +769,7 @@ export class ExperimentClient implements Client {
763769
this.flags.putAll(flags);
764770
} catch (e) {
765771
if (e instanceof TimeoutError) {
766-
this.config.debug && console.debug(e);
772+
this.logger.debug(e);
767773
// If throwOnError is configured to true, rethrow timeout errors
768774
if (this.config.throwOnError) {
769775
throw e;
@@ -802,14 +808,14 @@ export class ExperimentClient implements Client {
802808
} catch (e) {
803809
// catch localStorage undefined error
804810
}
805-
this.debug('[Experiment] Stored variants: ', variants);
811+
this.logger.debug('[Experiment] Stored variants: ', variants);
806812
}
807813

808814
private async startRetries(
809815
user: ExperimentUser,
810816
options: FetchOptions,
811817
): Promise<void> {
812-
this.debug('[Experiment] Retry fetch');
818+
this.logger.debug('[Experiment] Retry fetch');
813819
this.retriesBackoff = new Backoff(
814820
fetchBackoffAttempts,
815821
fetchBackoffMinMillis,
@@ -938,11 +944,13 @@ export class ExperimentClient implements Client {
938944
}
939945
}
940946

941-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
942-
private debug(message?: any, ...optionalParams: any[]): void {
943-
if (this.config.debug) {
944-
console.debug(message, ...optionalParams);
947+
private static getLogLevel(config: ExperimentConfig): LogLevel {
948+
// Backwards compatibility: if debug flag is set to true, use Debug level
949+
if (config.debug === true) {
950+
return LogLevel.Debug;
945951
}
952+
// Otherwise use the configured logLevel or default to Error
953+
return config.logLevel ?? LogLevel.Error;
946954
}
947955

948956
private shouldRetryFetch(e: Error): boolean {

packages/experiment-browser/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,5 @@ export {
3434
ExperimentPluginType,
3535
ExperimentEvent,
3636
} from './types/plugin';
37+
export { Logger, LogLevel } from './types/logger';
38+
export { ConsoleLogger } from './logger/consoleLogger';
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any*/
2+
import { Logger, LogLevel } from '../types/logger';
3+
4+
/**
5+
* Internal logger class that wraps a Logger implementation and handles log level filtering.
6+
* This class provides a centralized logging mechanism for the Experiment client.
7+
* @category Logging
8+
*/
9+
export class AmpLogger implements Logger {
10+
private logger: Logger;
11+
private logLevel: LogLevel;
12+
13+
/**
14+
* Creates a new AmpLogger instance
15+
* @param logger The underlying logger implementation to use
16+
* @param logLevel The minimum log level to output. Messages below this level will be ignored.
17+
*/
18+
constructor(logger: Logger, logLevel: LogLevel = LogLevel.Error) {
19+
this.logger = logger;
20+
this.logLevel = logLevel;
21+
}
22+
23+
/**
24+
* Log an error message
25+
* @param message The message to log
26+
* @param optionalParams Additional parameters to log
27+
*/
28+
error(message?: any, ...optionalParams: any[]): void {
29+
if (this.logLevel >= LogLevel.Error) {
30+
this.logger.error(message, ...optionalParams);
31+
}
32+
}
33+
34+
/**
35+
* Log a warning message
36+
* @param message The message to log
37+
* @param optionalParams Additional parameters to log
38+
*/
39+
warn(message?: any, ...optionalParams: any[]): void {
40+
if (this.logLevel >= LogLevel.Warn) {
41+
this.logger.warn(message, ...optionalParams);
42+
}
43+
}
44+
45+
/**
46+
* Log an informational message
47+
* @param message The message to log
48+
* @param optionalParams Additional parameters to log
49+
*/
50+
info(message?: any, ...optionalParams: any[]): void {
51+
if (this.logLevel >= LogLevel.Info) {
52+
this.logger.info(message, ...optionalParams);
53+
}
54+
}
55+
56+
/**
57+
* Log a debug message
58+
* @param message The message to log
59+
* @param optionalParams Additional parameters to log
60+
*/
61+
debug(message?: any, ...optionalParams: any[]): void {
62+
if (this.logLevel >= LogLevel.Debug) {
63+
this.logger.debug(message, ...optionalParams);
64+
}
65+
}
66+
67+
/**
68+
* Log a verbose message
69+
* @param message The message to log
70+
* @param optionalParams Additional parameters to log
71+
*/
72+
verbose(message?: any, ...optionalParams: any[]): void {
73+
if (this.logLevel >= LogLevel.Verbose) {
74+
this.logger.verbose(message, ...optionalParams);
75+
}
76+
}
77+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/* eslint-disable no-console,@typescript-eslint/no-explicit-any*/
2+
import { Logger } from '../types/logger';
3+
4+
/**
5+
* Default console-based logger implementation.
6+
* This logger uses the browser's console API to output log messages.
7+
* Log level filtering is handled by the AmpLogger wrapper class.
8+
* @category Logging
9+
*/
10+
export class ConsoleLogger implements Logger {
11+
/**
12+
* Log an error message
13+
* @param message The message to log
14+
* @param optionalParams Additional parameters to log
15+
*/
16+
error(message?: any, ...optionalParams: any[]): void {
17+
console.error(message, ...optionalParams);
18+
}
19+
20+
/**
21+
* Log a warning message
22+
* @param message The message to log
23+
* @param optionalParams Additional parameters to log
24+
*/
25+
warn(message?: any, ...optionalParams: any[]): void {
26+
console.warn(message, ...optionalParams);
27+
}
28+
29+
/**
30+
* Log an informational message
31+
* @param message The message to log
32+
* @param optionalParams Additional parameters to log
33+
*/
34+
info(message?: any, ...optionalParams: any[]): void {
35+
console.info(message, ...optionalParams);
36+
}
37+
38+
/**
39+
* Log a debug message
40+
* @param message The message to log
41+
* @param optionalParams Additional parameters to log
42+
*/
43+
debug(message?: any, ...optionalParams: any[]): void {
44+
console.debug(message, ...optionalParams);
45+
}
46+
47+
/**
48+
* Log a verbose message
49+
* @param message The message to log
50+
* @param optionalParams Additional parameters to log
51+
*/
52+
verbose(message?: any, ...optionalParams: any[]): void {
53+
console.debug(message, ...optionalParams);
54+
}
55+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* Log level enumeration for controlling logging verbosity.
3+
* @category Logging
4+
*/
5+
export enum LogLevel {
6+
/**
7+
* Disable all logging
8+
*/
9+
Disable = 0,
10+
/**
11+
* Error level logging - only critical errors
12+
*/
13+
Error = 1,
14+
/**
15+
* Warning level logging - errors and warnings
16+
*/
17+
Warn = 2,
18+
/**
19+
* Info level logging - errors, warnings, and informational messages
20+
*/
21+
Info = 3,
22+
/**
23+
* Debug level logging - errors, warnings, info, and debug messages
24+
*/
25+
Debug = 4,
26+
/**
27+
* Verbose level logging - all messages including verbose details
28+
*/
29+
Verbose = 5,
30+
}
31+
32+
/**
33+
* Logger interface that can be implemented to provide custom logging.
34+
* @category Logging
35+
*/
36+
export interface Logger {
37+
/**
38+
* Log an error message
39+
* @param message The message to log
40+
* @param optionalParams Additional parameters to log
41+
*/
42+
error(message?: any, ...optionalParams: any[]): void;
43+
44+
/**
45+
* Log a warning message
46+
* @param message The message to log
47+
* @param optionalParams Additional parameters to log
48+
*/
49+
warn(message?: any, ...optionalParams: any[]): void;
50+
51+
/**
52+
* Log an informational message
53+
* @param message The message to log
54+
* @param optionalParams Additional parameters to log
55+
*/
56+
info(message?: any, ...optionalParams: any[]): void;
57+
58+
/**
59+
* Log a debug message
60+
* @param message The message to log
61+
* @param optionalParams Additional parameters to log
62+
*/
63+
debug(message?: any, ...optionalParams: any[]): void;
64+
65+
/**
66+
* Log a verbose message
67+
* @param message The message to log
68+
* @param optionalParams Additional parameters to log
69+
*/
70+
verbose(message?: any, ...optionalParams: any[]): void;
71+
}

0 commit comments

Comments
 (0)