Skip to content

Commit 143f4e7

Browse files
[FSSDK-12298] client level attachment
1 parent 0aca0d3 commit 143f4e7

8 files changed

Lines changed: 71 additions & 113 deletions

File tree

src/client/createInstance.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
import { createInstance as jsCreateInstance } from '@optimizely/optimizely-sdk';
1818
import type { Config, Client } from '@optimizely/optimizely-sdk';
19-
import { getLoggerConfig } from '../logger/loggerConfigRegistry';
20-
import type { ReactLoggerConfig, ReactLogger } from '../logger/ReactLogger';
19+
import { REACT_LOGGER } from '../logger/createLogger';
20+
import type { ReactLogger } from '../logger/ReactLogger';
2121

2222
export const CLIENT_ENGINE = 'react-sdk';
2323
export const CLIENT_VERSION = '4.0.0';
@@ -27,7 +27,6 @@ export const REACT_CLIENT_META = Symbol('react-client-meta');
2727
export interface ReactClientMeta {
2828
hasOdpManager: boolean;
2929
hasVuidManager: boolean;
30-
loggerConfig?: ReactLoggerConfig;
3130
logger?: ReactLogger;
3231
}
3332

@@ -41,6 +40,13 @@ export interface ReactClientMeta {
4140
* @returns An OptimizelyClient instance with React SDK metadata
4241
*/
4342
export function createInstance(config: Config): Client {
43+
let reactLogger: ReactLogger | undefined;
44+
45+
if (config.logger) {
46+
reactLogger = (config.logger as Record<symbol, unknown>)[REACT_LOGGER] as ReactLogger | undefined;
47+
delete (config.logger as Record<symbol, unknown>)[REACT_LOGGER];
48+
}
49+
4450
const jsClient = jsCreateInstance({
4551
...config,
4652
clientEngine: CLIENT_ENGINE,
@@ -52,7 +58,7 @@ export function createInstance(config: Config): Client {
5258
reactClient[REACT_CLIENT_META] = {
5359
hasOdpManager: !!config.odpManager,
5460
hasVuidManager: !!config.vuidManager,
55-
loggerConfig: config.logger ? getLoggerConfig(config.logger) : undefined,
61+
logger: reactLogger,
5662
} satisfies ReactClientMeta;
5763

5864
return reactClient;

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export {
2424
createVuidManager,
2525
createErrorNotifier,
2626
} from './client/index';
27-
export { createLogger } from './logger/index';
27+
export { createLogger, DEBUG, ERROR, WARN, INFO } from './logger/index';
2828

2929
export type * from '@optimizely/optimizely-sdk';
3030

src/logger/ReactLogger.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ export interface ReactLoggerConfig {
2929
logHandler?: LogHandler;
3030
}
3131

32-
const LOG_PREFIX = '[ReactSDK]';
33-
32+
const LOG_PREFIX = '[OPTIMIZELY - REACT]';
3433
const defaultLogHandler: LogHandler = {
3534
log(level: LogLevel, message: string): void {
3635
switch (level) {
@@ -56,16 +55,16 @@ export function createReactLogger(config: ReactLoggerConfig): ReactLogger {
5655

5756
return {
5857
debug: (msg) => {
59-
if (level <= LogLevel.Debug) handler.log(LogLevel.Debug, `${LOG_PREFIX} ${msg}`);
58+
if (level <= LogLevel.Debug) handler.log(LogLevel.Debug, `${LOG_PREFIX} - DEBUG ${msg}`);
6059
},
6160
info: (msg) => {
62-
if (level <= LogLevel.Info) handler.log(LogLevel.Info, `${LOG_PREFIX} ${msg}`);
61+
if (level <= LogLevel.Info) handler.log(LogLevel.Info, `${LOG_PREFIX} - INFO ${msg}`);
6362
},
6463
warn: (msg) => {
65-
if (level <= LogLevel.Warn) handler.log(LogLevel.Warn, `${LOG_PREFIX} ${msg}`);
64+
if (level <= LogLevel.Warn) handler.log(LogLevel.Warn, `${LOG_PREFIX} - WARN ${msg}`);
6665
},
6766
error: (msg) => {
68-
if (level <= LogLevel.Error) handler.log(LogLevel.Error, `${LOG_PREFIX} ${msg}`);
67+
if (level <= LogLevel.Error) handler.log(LogLevel.Error, `${LOG_PREFIX} - ERROR ${msg}`);
6968
},
7069
};
7170
}

src/logger/createLogger.spec.ts

Lines changed: 43 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@
1616

1717
import { vi, describe, it, expect, beforeEach } from 'vitest';
1818
import { DEBUG, INFO, WARN, ERROR, LogLevel } from '@optimizely/optimizely-sdk';
19-
import type { LoggerConfig, LogHandler } from '@optimizely/optimizely-sdk';
20-
import { storeLoggerConfig, getLoggerConfig } from './loggerConfigRegistry';
19+
import type { LogHandler } from '@optimizely/optimizely-sdk';
2120
import { createReactLogger } from './ReactLogger';
22-
import type { ReactLoggerConfig } from './ReactLogger';
21+
import type { ReactLogger } from './ReactLogger';
2322

2423
const mockOpaqueLogger = vi.hoisted(() => ({ __opaque: true }));
2524

@@ -31,66 +30,57 @@ vi.mock('@optimizely/optimizely-sdk', async (importOriginal) => {
3130
};
3231
});
3332

34-
import { createLogger } from './createLogger';
33+
import { createLogger, REACT_LOGGER } from './createLogger';
3534

3635
describe('createLogger', () => {
3736
beforeEach(() => {
3837
vi.clearAllMocks();
3938
});
4039

4140
it('should return the opaque logger from the JS SDK', () => {
42-
const config: LoggerConfig = { level: INFO };
43-
const result = createLogger(config);
41+
const result = createLogger({ level: INFO });
4442
expect(result).toBe(mockOpaqueLogger);
4543
});
4644

47-
it('should store the resolved config in the registry', () => {
45+
it('should attach a ReactLogger via the REACT_LOGGER symbol', () => {
4846
const mockHandler: LogHandler = { log: vi.fn() };
49-
createLogger({ level: INFO, logHandler: mockHandler });
50-
51-
const storedConfig = getLoggerConfig(mockOpaqueLogger);
52-
expect(storedConfig).toBeDefined();
53-
expect(storedConfig!.logLevel).toBe(LogLevel.Info);
54-
expect(storedConfig!.logHandler).toBe(mockHandler);
47+
const result = createLogger({ level: INFO, logHandler: mockHandler });
48+
49+
const reactLogger = (result as Record<symbol, unknown>)[REACT_LOGGER] as ReactLogger;
50+
expect(reactLogger).toBeDefined();
51+
expect(reactLogger.debug).toBeTypeOf('function');
52+
expect(reactLogger.info).toBeTypeOf('function');
53+
expect(reactLogger.warn).toBeTypeOf('function');
54+
expect(reactLogger.error).toBeTypeOf('function');
5555
});
5656

57-
describe('log level resolution', () => {
58-
it.each([
59-
{ preset: DEBUG, expected: LogLevel.Debug, name: 'DEBUG' },
60-
{ preset: INFO, expected: LogLevel.Info, name: 'INFO' },
61-
{ preset: WARN, expected: LogLevel.Warn, name: 'WARN' },
62-
{ preset: ERROR, expected: LogLevel.Error, name: 'ERROR' },
63-
])('should resolve $name preset to LogLevel.$name', ({ preset, expected }) => {
64-
createLogger({ level: preset });
65-
const storedConfig = getLoggerConfig(mockOpaqueLogger);
66-
expect(storedConfig!.logLevel).toBe(expected);
67-
});
68-
});
69-
});
57+
it('should create a ReactLogger that uses the provided logHandler', () => {
58+
const mockHandler: LogHandler = { log: vi.fn() };
59+
const result = createLogger({ level: INFO, logHandler: mockHandler });
7060

71-
describe('loggerConfigRegistry', () => {
72-
it('should return undefined for unknown logger objects', () => {
73-
expect(getLoggerConfig({})).toBeUndefined();
74-
});
61+
const reactLogger = (result as Record<symbol, unknown>)[REACT_LOGGER] as ReactLogger;
62+
reactLogger.info('hello');
7563

76-
it('should store and retrieve config for a given logger', () => {
77-
const logger = {};
78-
const config: ReactLoggerConfig = { logLevel: LogLevel.Warn };
79-
storeLoggerConfig(logger, config);
80-
expect(getLoggerConfig(logger)).toBe(config);
64+
expect(mockHandler.log).toHaveBeenCalledWith(LogLevel.Info, '[OPTIMIZELY - REACT] - INFO hello');
8165
});
8266

83-
it('should support multiple loggers with different configs', () => {
84-
const logger1 = {};
85-
const logger2 = {};
86-
const config1: ReactLoggerConfig = { logLevel: LogLevel.Debug };
87-
const config2: ReactLoggerConfig = { logLevel: LogLevel.Error };
88-
89-
storeLoggerConfig(logger1, config1);
90-
storeLoggerConfig(logger2, config2);
91-
92-
expect(getLoggerConfig(logger1)).toBe(config1);
93-
expect(getLoggerConfig(logger2)).toBe(config2);
67+
describe('log level resolution', () => {
68+
it.each([
69+
{ preset: DEBUG, expectedCalls: 4, name: 'DEBUG' },
70+
{ preset: INFO, expectedCalls: 3, name: 'INFO' },
71+
{ preset: WARN, expectedCalls: 2, name: 'WARN' },
72+
{ preset: ERROR, expectedCalls: 1, name: 'ERROR' },
73+
])('should resolve $name preset correctly', ({ preset, expectedCalls }) => {
74+
const mockHandler: LogHandler = { log: vi.fn() };
75+
const result = createLogger({ level: preset, logHandler: mockHandler });
76+
77+
const reactLogger = (result as Record<symbol, unknown>)[REACT_LOGGER] as ReactLogger;
78+
reactLogger.debug('d');
79+
reactLogger.info('i');
80+
reactLogger.warn('w');
81+
reactLogger.error('e');
82+
expect(mockHandler.log).toHaveBeenCalledTimes(expectedCalls);
83+
});
9484
});
9585
});
9686

@@ -106,8 +96,8 @@ describe('createReactLogger', () => {
10696
logger.error('should appear');
10797

10898
expect(mockHandler.log).toHaveBeenCalledTimes(2);
109-
expect(mockHandler.log).toHaveBeenCalledWith(LogLevel.Warn, '[ReactSDK] should appear');
110-
expect(mockHandler.log).toHaveBeenCalledWith(LogLevel.Error, '[ReactSDK] should appear');
99+
expect(mockHandler.log).toHaveBeenCalledWith(LogLevel.Warn, '[OPTIMIZELY - REACT] - WARN should appear');
100+
expect(mockHandler.log).toHaveBeenCalledWith(LogLevel.Error, '[OPTIMIZELY - REACT] - ERROR should appear');
111101
});
112102

113103
it('should allow all messages when level is Debug', () => {
@@ -132,7 +122,7 @@ describe('createReactLogger', () => {
132122
logger.error('e');
133123

134124
expect(mockHandler.log).toHaveBeenCalledTimes(1);
135-
expect(mockHandler.log).toHaveBeenCalledWith(LogLevel.Error, '[ReactSDK] e');
125+
expect(mockHandler.log).toHaveBeenCalledWith(LogLevel.Error, '[OPTIMIZELY - REACT] - ERROR e');
136126
});
137127
});
138128

@@ -143,7 +133,7 @@ describe('createReactLogger', () => {
143133

144134
logger.info('hello');
145135

146-
expect(mockHandler.log).toHaveBeenCalledWith(LogLevel.Info, '[ReactSDK] hello');
136+
expect(mockHandler.log).toHaveBeenCalledWith(LogLevel.Info, '[OPTIMIZELY - REACT] - INFO hello');
147137
});
148138

149139
it('should use default console handler when logHandler is not provided', () => {
@@ -152,13 +142,13 @@ describe('createReactLogger', () => {
152142

153143
logger.info('hello');
154144

155-
expect(consoleSpy).toHaveBeenCalledWith('[ReactSDK] hello');
145+
expect(consoleSpy).toHaveBeenCalledWith('[OPTIMIZELY - REACT] - INFO hello');
156146
consoleSpy.mockRestore();
157147
});
158148
});
159149

160150
describe('message prefix', () => {
161-
it('should prepend [ReactSDK] to all messages', () => {
151+
it('should prepend [OPTIMIZELY - REACT] to all messages', () => {
162152
const mockHandler: LogHandler = { log: vi.fn() };
163153
const logger = createReactLogger({ logLevel: LogLevel.Debug, logHandler: mockHandler });
164154

@@ -168,7 +158,7 @@ describe('createReactLogger', () => {
168158
logger.error('test');
169159

170160
for (const call of (mockHandler.log as ReturnType<typeof vi.fn>).mock.calls) {
171-
expect(call[1]).toMatch(/^\[ReactSDK\] /);
161+
expect(call[1]).toMatch(/^\[OPTIMIZELY - REACT\] - (DEBUG|INFO|WARN|ERROR) /);
172162
}
173163
});
174164
});

src/logger/createLogger.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
import { createLogger as jsCreateLogger, LogLevel, DEBUG, INFO, WARN, ERROR } from '@optimizely/optimizely-sdk';
1818
import type { LoggerConfig, OpaqueLevelPreset } from '@optimizely/optimizely-sdk';
19-
import { storeLoggerConfig } from './loggerConfigRegistry';
19+
import { createReactLogger } from './ReactLogger';
20+
21+
export const REACT_LOGGER = Symbol('react-logger');
2022

2123
function resolveLogLevel(preset: OpaqueLevelPreset): LogLevel {
2224
if (preset === DEBUG) return LogLevel.Debug;
@@ -28,11 +30,10 @@ function resolveLogLevel(preset: OpaqueLevelPreset): LogLevel {
2830

2931
export function createLogger(config: LoggerConfig) {
3032
const opaqueLogger = jsCreateLogger(config);
31-
32-
storeLoggerConfig(opaqueLogger, {
33+
const reactLogger = createReactLogger({
3334
logLevel: resolveLogLevel(config.level),
3435
logHandler: config.logHandler,
3536
});
36-
37+
(opaqueLogger as Record<symbol, unknown>)[REACT_LOGGER] = reactLogger;
3738
return opaqueLogger;
3839
}

src/logger/getReactLogger.ts

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,14 @@
1717
import type { Client } from '@optimizely/optimizely-sdk';
1818
import { REACT_CLIENT_META } from '../client/createInstance';
1919
import type { ReactClientMeta } from '../client/createInstance';
20-
import { createReactLogger } from './ReactLogger';
20+
import type { ReactLogger } from './ReactLogger';
2121

2222
/**
23-
* Returns the cached ReactLogger instance for the given client.
24-
* Creates it lazily on first call; subsequent calls return the same instance.
25-
* Returns undefined if the client has no logger config (e.g., logger was
26-
* not created via the React SDK's createLogger wrapper).
23+
* Returns the ReactLogger instance for the given client, or undefined
24+
* if the client has no logger (e.g., logger was not created via the
25+
* React SDK's createLogger wrapper).
2726
*/
28-
export function getReactLogger(client: Client) {
27+
export function getReactLogger(client: Client): ReactLogger | undefined {
2928
const meta = (client as unknown as Record<symbol, ReactClientMeta>)[REACT_CLIENT_META];
30-
31-
if (meta.logger) return meta.logger;
32-
33-
if (meta.loggerConfig) {
34-
meta.logger = createReactLogger(meta.loggerConfig);
35-
return meta.logger;
36-
}
37-
38-
return undefined;
29+
return meta.logger;
3930
}

src/logger/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ export { createLogger } from './createLogger';
1818
export { getReactLogger } from './getReactLogger';
1919
export { createReactLogger } from './ReactLogger';
2020
export type { ReactLogger, ReactLoggerConfig } from './ReactLogger';
21+
export { ERROR, DEBUG, WARN, INFO } from '@optimizely/optimizely-sdk';

src/logger/loggerConfigRegistry.ts

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)