Skip to content

Commit ccf5f80

Browse files
committed
Existing instances of default logger should be replaceable
This allows for module-level imports of default logger to get switched to new desired behavior. For cases where we really do want the deafult console logger, can import consoleLogger directly.
1 parent cdb93bb commit ccf5f80

File tree

2 files changed

+187
-3
lines changed

2 files changed

+187
-3
lines changed

src/lib/utility/logger.test.ts

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import { describe, expect, it, vi } from 'vitest';
2+
3+
import type { Logger } from '~/lib/utility/logger';
4+
import { consoleLogger, getDefaultLogger, setDefaultLogger } from '~/lib/utility/logger';
5+
6+
describe('logger', () => {
7+
describe('consoleLogger', () => {
8+
it('should have all required methods', () => {
9+
expect(consoleLogger.debug).toBeTypeOf('function');
10+
expect(consoleLogger.debugWarn).toBeTypeOf('function');
11+
expect(consoleLogger.info).toBeTypeOf('function');
12+
expect(consoleLogger.warn).toBeTypeOf('function');
13+
expect(consoleLogger.error).toBeTypeOf('function');
14+
});
15+
});
16+
17+
describe('getDefaultLogger', () => {
18+
it('should return the same logger instance on multiple calls', () => {
19+
const logger1 = getDefaultLogger();
20+
const logger2 = getDefaultLogger();
21+
expect(logger1).toBe(logger2);
22+
});
23+
24+
it('should initially use consoleLogger behavior', () => {
25+
const logger = getDefaultLogger();
26+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
27+
28+
logger.warn('test message');
29+
expect(consoleSpy).toHaveBeenCalledWith('test message');
30+
});
31+
});
32+
33+
describe('setDefaultLogger and dynamic wrapper behavior', () => {
34+
it('should update behavior for existing logger references', () => {
35+
// Get logger reference before setting custom logger
36+
const earlyLogger = getDefaultLogger();
37+
38+
// Create mock logger
39+
const mockLogger: Logger = {
40+
debug: vi.fn(),
41+
debugWarn: vi.fn(),
42+
info: vi.fn(),
43+
warn: vi.fn(),
44+
error: vi.fn(),
45+
};
46+
47+
// Set new logger
48+
setDefaultLogger(mockLogger);
49+
50+
// The early reference should now use the mock logger
51+
earlyLogger.warn('test message');
52+
earlyLogger.error('error message');
53+
earlyLogger.info('info message');
54+
earlyLogger.debug('debug message');
55+
earlyLogger.debugWarn('debug warn message');
56+
57+
expect(mockLogger.warn).toHaveBeenCalledWith('test message');
58+
expect(mockLogger.error).toHaveBeenCalledWith('error message');
59+
expect(mockLogger.info).toHaveBeenCalledWith('info message');
60+
expect(mockLogger.debug).toHaveBeenCalledWith('debug message');
61+
expect(mockLogger.debugWarn).toHaveBeenCalledWith('debug warn message');
62+
});
63+
64+
it('should work with multiple logger changes', () => {
65+
const earlyLogger = getDefaultLogger();
66+
67+
// First mock logger
68+
const mockLogger1: Logger = {
69+
debug: vi.fn(),
70+
debugWarn: vi.fn(),
71+
info: vi.fn(),
72+
warn: vi.fn(),
73+
error: vi.fn(),
74+
};
75+
76+
// Second mock logger
77+
const mockLogger2: Logger = {
78+
debug: vi.fn(),
79+
debugWarn: vi.fn(),
80+
info: vi.fn(),
81+
warn: vi.fn(),
82+
error: vi.fn(),
83+
};
84+
85+
// Set first logger
86+
setDefaultLogger(mockLogger1);
87+
earlyLogger.warn('message 1');
88+
expect(mockLogger1.warn).toHaveBeenCalledWith('message 1');
89+
expect(mockLogger2.warn).not.toHaveBeenCalled();
90+
91+
// Set second logger
92+
setDefaultLogger(mockLogger2);
93+
earlyLogger.warn('message 2');
94+
expect(mockLogger2.warn).toHaveBeenCalledWith('message 2');
95+
// mockLogger1 should not receive new calls
96+
expect(mockLogger1.warn).toHaveBeenCalledTimes(1);
97+
});
98+
99+
it('should work for new logger references after setDefaultLogger', () => {
100+
const mockLogger: Logger = {
101+
debug: vi.fn(),
102+
debugWarn: vi.fn(),
103+
info: vi.fn(),
104+
warn: vi.fn(),
105+
error: vi.fn(),
106+
};
107+
108+
setDefaultLogger(mockLogger);
109+
110+
// Get logger reference after setting custom logger
111+
const newLogger = getDefaultLogger();
112+
113+
newLogger.error('test error');
114+
expect(mockLogger.error).toHaveBeenCalledWith('test error');
115+
});
116+
117+
it('should maintain consistent behavior across all references', () => {
118+
const earlyLogger = getDefaultLogger();
119+
120+
const mockLogger: Logger = {
121+
debug: vi.fn(),
122+
debugWarn: vi.fn(),
123+
info: vi.fn(),
124+
warn: vi.fn(),
125+
error: vi.fn(),
126+
};
127+
128+
setDefaultLogger(mockLogger);
129+
130+
const laterLogger = getDefaultLogger();
131+
132+
// Both references should use the same underlying logger
133+
earlyLogger.info('early message');
134+
laterLogger.info('later message');
135+
136+
expect(mockLogger.info).toHaveBeenCalledTimes(2);
137+
expect(mockLogger.info).toHaveBeenCalledWith('early message');
138+
expect(mockLogger.info).toHaveBeenCalledWith('later message');
139+
});
140+
141+
it('should restore to consoleLogger when set back', () => {
142+
const logger = getDefaultLogger();
143+
144+
// Set mock logger
145+
const mockLogger: Logger = {
146+
debug: vi.fn(),
147+
debugWarn: vi.fn(),
148+
info: vi.fn(),
149+
warn: vi.fn(),
150+
error: vi.fn(),
151+
};
152+
153+
setDefaultLogger(mockLogger);
154+
logger.warn('mock message');
155+
expect(mockLogger.warn).toHaveBeenCalledWith('mock message');
156+
157+
// Restore console logger
158+
setDefaultLogger(consoleLogger);
159+
160+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
161+
logger.warn('console message');
162+
expect(consoleSpy).toHaveBeenCalledWith('console message');
163+
});
164+
});
165+
});

src/lib/utility/logger.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,44 @@ export interface Logger {
1212
error: typeof console.error;
1313
}
1414

15-
let defaultLogger: Logger = {
16-
// Wrap everything so tests can still mock console.<method>
15+
/**
16+
* Original console logger that preserves the console logging behavior
17+
* including not logging debug levels in prod and not logging info in test.
18+
*/
19+
export const consoleLogger: Logger = {
1720
debug: (...args) => (import.meta.env.PROD ? undefined : console.debug(...args)),
1821
debugWarn: (...args) => (import.meta.env.PROD ? undefined : console.warn(...args)),
1922
info: (...args) => (import.meta.env.MODE === 'test' ? undefined : console.info(...args)),
2023
warn: (...args) => console.warn(...args),
2124
error: (...args) => console.error(...args),
2225
};
2326

27+
let defaultLogger: Logger = consoleLogger;
28+
2429
/**
2530
* Set the default logger for the application.
2631
*/
2732
export function setDefaultLogger(logger: Logger) {
2833
defaultLogger = logger;
2934
}
3035

36+
/**
37+
* Dynamic logger wrapper that always delegates to the current default logger.
38+
* This ensures that early references to getDefaultLogger() continue to work
39+
* even after setDefaultLogger() is called.
40+
*/
41+
const dynamicLogger: Logger = {
42+
debug: (...args) => defaultLogger.debug(...args),
43+
debugWarn: (...args) => defaultLogger.debugWarn(...args),
44+
info: (...args) => defaultLogger.info(...args),
45+
warn: (...args) => defaultLogger.warn(...args),
46+
error: (...args) => defaultLogger.error(...args),
47+
};
48+
3149
/**
3250
* Get the default logger for the application.
51+
* Returns a dynamic wrapper that always delegates to the current logger.
3352
*/
3453
export function getDefaultLogger(): Logger {
35-
return defaultLogger;
54+
return dynamicLogger;
3655
}

0 commit comments

Comments
 (0)