Skip to content

Commit e916703

Browse files
committed
feat(logger): Add transports and a little structure
1 parent bdfe256 commit e916703

File tree

4 files changed

+303
-57
lines changed

4 files changed

+303
-57
lines changed

packages/utils/src/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ describe('index', () => {
1010
'isPrimitive',
1111
'isTypedArray',
1212
'isTypedObject',
13+
'Logger',
1314
'makeCounter',
1415
'makeLogger',
1516
'stringify',

packages/utils/src/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
export type { Logger } from './logger.ts';
2-
export { makeLogger } from './logger.ts';
1+
export { Logger, makeLogger } from './logger.ts';
32
export { delay, makeCounter } from './misc.ts';
43
export { stringify } from './stringify.ts';
54
export type { ExtractGuardType, PromiseCallbacks, TypeGuard } from './types.ts';

packages/utils/src/logger.test.ts

Lines changed: 165 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,200 @@
11
import { describe, it, expect, vi } from 'vitest';
22

3-
import { makeLogger } from './logger.ts';
3+
import {
4+
consoleTransport,
5+
DEFAULT_OPTIONS,
6+
Logger,
7+
makeLogger,
8+
mergeOptions,
9+
} from './logger.ts';
10+
import type { LoggerOptions, LogLevel } from './logger.ts';
411

5-
describe('makeLogger', () => {
6-
const consoleMethod = ['log', 'debug', 'info', 'warn', 'error'] as const;
12+
const consoleMethod = ['log', 'debug', 'info', 'warn', 'error'] as const;
713

14+
describe('Logger', () => {
815
it.each(consoleMethod)('has method %j', (method) => {
9-
const testLogger = makeLogger('test');
16+
const testLogger = new Logger({ tags: ['test'] });
1017
expect(testLogger).toHaveProperty(method);
1118
expect(testLogger[method]).toBeTypeOf('function');
1219
});
1320

1421
it.each(consoleMethod)(
15-
'calls %j with the provided label followed by a single argument',
22+
'calls %j with the provided tags followed by a single argument',
1623
(method) => {
1724
const methodSpy = vi.spyOn(console, method);
18-
const testLogger = makeLogger('test');
25+
const tags = ['test'];
26+
const testLogger = new Logger({ tags });
1927
testLogger[method]('foo');
20-
expect(methodSpy).toHaveBeenCalledWith('test', 'foo');
28+
expect(methodSpy).toHaveBeenCalledWith(tags, 'foo');
2129
},
2230
);
2331

2432
it.each(consoleMethod)(
25-
'calls %j with the provided label followed by multiple arguments',
33+
'calls %j with the provided tags followed by multiple arguments',
2634
(method) => {
2735
const methodSpy = vi.spyOn(console, method);
28-
const testLogger = makeLogger('test');
36+
const tags = ['test'];
37+
const testLogger = new Logger({ tags });
2938
testLogger[method]('foo', { bar: 'bar' });
30-
expect(methodSpy).toHaveBeenCalledWith('test', 'foo', { bar: 'bar' });
39+
expect(methodSpy).toHaveBeenCalledWith(tags, 'foo', { bar: 'bar' });
3140
},
3241
);
3342

3443
it.each(consoleMethod)(
35-
'calls %j with the provided label when given no argument',
44+
'calls %j with the provided tags when given no argument',
3645
(method) => {
3746
const methodSpy = vi.spyOn(console, method);
38-
const testLogger = makeLogger('test');
47+
const tags = ['test'];
48+
const testLogger = new Logger({ tags });
3949
testLogger[method]();
40-
expect(methodSpy).toHaveBeenCalledWith('test');
50+
expect(methodSpy).toHaveBeenCalledWith(tags);
4151
},
4252
);
4353

4454
it('can be nested', () => {
4555
const consoleSpy = vi.spyOn(console, 'log');
46-
const vatLogger = makeLogger('[vat 0x01]');
47-
const subLogger = makeLogger('(process)', vatLogger);
56+
const vatLogger = new Logger({ tags: ['vat 0x01'] });
57+
const subLogger = vatLogger.subLogger({ tags: ['(process)'] });
4858
subLogger.log('foo');
49-
expect(consoleSpy).toHaveBeenCalledWith('[vat 0x01]', '(process)', 'foo');
59+
expect(consoleSpy).toHaveBeenCalledWith(['vat 0x01', '(process)'], 'foo');
60+
});
61+
62+
it('omits tagline when no tags are provided', () => {
63+
const consoleSpy = vi.spyOn(console, 'log');
64+
const logger = new Logger();
65+
logger.log('foo');
66+
expect(consoleSpy).toHaveBeenCalledWith('foo');
67+
});
68+
69+
it('passes objects directly in the data field', () => {
70+
const consoleSpy = vi.spyOn(console, 'log');
71+
const logger = new Logger({ tags: ['test'] });
72+
const message = 'foo';
73+
const data = { bar: 'bar' };
74+
logger.log(message, data);
75+
expect(consoleSpy).toHaveBeenCalledWith(['test'], message, data);
76+
});
77+
78+
describe('subLogger', () => {
79+
it('creates a new logger with the merged options', () => {
80+
const consoleSpy = vi.spyOn(console, 'log');
81+
const logger = new Logger({ tags: ['test'] });
82+
const subLogger = logger.subLogger({ tags: ['sub'] });
83+
expect(subLogger).toBeInstanceOf(Logger);
84+
subLogger.log('foo');
85+
expect(consoleSpy).toHaveBeenCalledWith(['test', 'sub'], 'foo');
86+
});
87+
88+
it('works with no options', () => {
89+
const consoleSpy = vi.spyOn(console, 'log');
90+
const logger = new Logger({ tags: ['test'] });
91+
const subLogger = logger.subLogger();
92+
expect(subLogger).toBeInstanceOf(Logger);
93+
subLogger.log('foo');
94+
expect(consoleSpy).toHaveBeenCalledWith(['test'], 'foo');
95+
});
96+
});
97+
});
98+
99+
describe('consoleTransport', () => {
100+
it.each(consoleMethod)('logs to the console with method %j', (method) => {
101+
const consoleSpy = vi.spyOn(console, method);
102+
const logger = new Logger({ tags: ['test'] });
103+
logger[method]('foo');
104+
expect(consoleSpy).toHaveBeenCalledWith(['test'], 'foo');
105+
});
106+
107+
it.each(consoleMethod)('default data is an empty array for %j', (level) => {
108+
const consoleSpy = vi.spyOn(console, level);
109+
const entry = { tags: ['test'], level, message: 'foo' };
110+
consoleTransport(entry);
111+
expect(consoleSpy).toHaveBeenCalledWith(['test'], 'foo');
112+
});
113+
114+
it('does not log when the level is silent', () => {
115+
const consoleMethodSpies = consoleMethod.map((method) =>
116+
vi.spyOn(console, method),
117+
);
118+
consoleTransport({ tags: ['test'], level: 'silent', message: 'foo' });
119+
consoleMethodSpies.forEach((spy) => expect(spy).not.toHaveBeenCalled());
120+
});
121+
});
122+
123+
describe('mergeOptions', () => {
124+
it.each([
125+
{ left: ['test'], right: ['sub'], result: ['test', 'sub'] },
126+
{ left: ['test', 'test'], right: ['sub'], result: ['test', 'sub'] },
127+
{
128+
left: ['test', 'fizz'],
129+
right: ['test', 'buzz'],
130+
result: ['test', 'fizz', 'buzz'],
131+
},
132+
])('merges tags as expected: $left and $right', ({ left, right, result }) => {
133+
const options = mergeOptions({ tags: left }, { tags: right });
134+
expect(options.tags).toStrictEqual(result);
135+
});
136+
137+
it('defaults to the default options', () => {
138+
const options = mergeOptions();
139+
expect(options).toStrictEqual(DEFAULT_OPTIONS);
140+
});
141+
142+
const transportA = vi.fn();
143+
const transportB = vi.fn();
144+
145+
it.each([
146+
{ left: { transports: [] }, right: { transports: [] }, result: [] },
147+
{
148+
left: { transports: [transportA] },
149+
right: { transports: [] },
150+
result: [transportA],
151+
},
152+
{
153+
left: { transports: [transportA] },
154+
right: { transports: [transportA] },
155+
result: [transportA],
156+
},
157+
{
158+
left: { transports: [transportA] },
159+
right: { transports: [transportB] },
160+
result: [transportA, transportB],
161+
},
162+
])(
163+
'merges transports as expected: $left and $right',
164+
({ left, right, result }) => {
165+
const options = mergeOptions(left, right);
166+
expect(options.transports).toStrictEqual(result);
167+
},
168+
);
169+
170+
it.each([
171+
{ left: { level: 'warn' }, right: { level: 'error' }, result: 'error' },
172+
{ left: { level: undefined }, right: { level: 'warn' }, result: 'warn' },
173+
{ left: { level: 'info' }, right: {}, result: 'info' },
174+
] as { left: LoggerOptions; right: LoggerOptions; result: LogLevel }[])(
175+
'merges levels as expected: $left and $right',
176+
({ left, right, result }) => {
177+
const options = mergeOptions(
178+
{ ...left, transports: [] },
179+
{ ...right, transports: [] },
180+
);
181+
expect(options.level).toBe(result);
182+
},
183+
);
184+
});
185+
186+
describe('makeLogger', () => {
187+
it('creates a new logger from a label and a parent logger', () => {
188+
const logger = new Logger({ tags: ['test'] });
189+
const subLogger = makeLogger('sub', logger);
190+
expect(subLogger).toBeInstanceOf(Logger);
191+
});
192+
193+
it('creates a new logger from a label', () => {
194+
const logSpy = vi.spyOn(console, 'log');
195+
const logger = makeLogger('test');
196+
expect(logger).toBeInstanceOf(Logger);
197+
logger.log('foo');
198+
expect(logSpy).toHaveBeenCalledWith(['test'], 'foo');
50199
});
51200
});

0 commit comments

Comments
 (0)