Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/store/src/sqlite/nodejs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ async function initDB(
const dbPath = await getDBFilename(dbFilename);
logger.debug('dbPath:', dbPath);
return new Sqlite(dbPath, {
verbose: verbose ? (...args) => logger.info(...args) : undefined,
verbose: (verbose ? logger.info.bind(logger) : undefined) as
| ((...args: unknown[]) => void)
| undefined,
});
}

Expand Down
2 changes: 1 addition & 1 deletion packages/store/src/sqlite/wasm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ describe('makeSQLKernelDatabase', () => {
const consoleSpy = vi.spyOn(console, 'log');
await makeSQLKernelDatabase({ verbose: true });
expect(consoleSpy).toHaveBeenCalledWith(
'[sqlite]',
['[sqlite]'],
'Initializing kernel store',
);
});
Expand Down
1 change: 1 addition & 0 deletions packages/utils/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe('index', () => {
'isPrimitive',
'isTypedArray',
'isTypedObject',
'Logger',
'makeCounter',
'makeLogger',
'stringify',
Expand Down
3 changes: 1 addition & 2 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export type { Logger } from './logger.ts';
export { makeLogger } from './logger.ts';
export { Logger, makeLogger } from './logger.ts';
export { delay, makeCounter } from './misc.ts';
export { stringify } from './stringify.ts';
export type { ExtractGuardType, PromiseCallbacks, TypeGuard } from './types.ts';
Expand Down
201 changes: 185 additions & 16 deletions packages/utils/src/logger.test.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,220 @@
import { describe, it, expect, vi } from 'vitest';

import { makeLogger } from './logger.ts';
import {
consoleTransport,
DEFAULT_OPTIONS,
Logger,
makeLogger,
mergeOptions,
} from './logger.ts';
import type { LoggerOptions, LogLevel } from './logger.ts';

describe('makeLogger', () => {
const consoleMethod = ['log', 'debug', 'info', 'warn', 'error'] as const;
const consoleMethod = ['log', 'debug', 'info', 'warn', 'error'] as const;

describe('Logger', () => {
it('can be created from a string', () => {
const logSpy = vi.spyOn(console, 'log');
const logger = new Logger('test');
logger.log('foo');
expect(logger).toBeInstanceOf(Logger);
expect(logSpy).toHaveBeenCalledWith(['test'], 'foo');
});

it.each(consoleMethod)('has method %j', (method) => {
const testLogger = makeLogger('test');
const testLogger = new Logger({ tags: ['test'] });
expect(testLogger).toHaveProperty(method);
expect(testLogger[method]).toBeTypeOf('function');
});

it.each(consoleMethod)(
'calls %j with the provided label followed by a single argument',
'calls %j with the provided tags followed by a single argument',
(method) => {
const methodSpy = vi.spyOn(console, method);
const testLogger = makeLogger('test');
const tags = ['test'];
const testLogger = new Logger({ tags });
testLogger[method]('foo');
expect(methodSpy).toHaveBeenCalledWith('test', 'foo');
expect(methodSpy).toHaveBeenCalledWith(tags, 'foo');
},
);

it.each(consoleMethod)(
'calls %j with the provided label followed by multiple arguments',
'calls %j with the provided tags followed by multiple arguments',
(method) => {
const methodSpy = vi.spyOn(console, method);
const testLogger = makeLogger('test');
const tags = ['test'];
const testLogger = new Logger({ tags });
testLogger[method]('foo', { bar: 'bar' });
expect(methodSpy).toHaveBeenCalledWith('test', 'foo', { bar: 'bar' });
expect(methodSpy).toHaveBeenCalledWith(tags, 'foo', { bar: 'bar' });
},
);

it.each(consoleMethod)(
'calls %j with the provided label when given no argument',
'calls %j with the provided tags when given no argument',
(method) => {
const methodSpy = vi.spyOn(console, method);
const testLogger = makeLogger('test');
const tags = ['test'];
const testLogger = new Logger({ tags });
testLogger[method]();
expect(methodSpy).toHaveBeenCalledWith('test');
expect(methodSpy).toHaveBeenCalledWith(tags);
},
);

it('can be nested', () => {
const consoleSpy = vi.spyOn(console, 'log');
const vatLogger = makeLogger('[vat 0x01]');
const subLogger = makeLogger('(process)', vatLogger);
const vatLogger = new Logger({ tags: ['vat 0x01'] });
const subLogger = vatLogger.subLogger({ tags: ['(process)'] });
subLogger.log('foo');
expect(consoleSpy).toHaveBeenCalledWith('[vat 0x01]', '(process)', 'foo');
expect(consoleSpy).toHaveBeenCalledWith(['vat 0x01', '(process)'], 'foo');
});

it('omits tagline when no tags are provided', () => {
const consoleSpy = vi.spyOn(console, 'log');
const logger = new Logger();
logger.log('foo');
expect(consoleSpy).toHaveBeenCalledWith('foo');
});

it('passes objects directly in the data field', () => {
const consoleSpy = vi.spyOn(console, 'log');
const logger = new Logger({ tags: ['test'] });
const message = 'foo';
const data = { bar: 'bar' };
logger.log(message, data);
expect(consoleSpy).toHaveBeenCalledWith(['test'], message, data);
});

describe('subLogger', () => {
it('creates a new logger with the merged options', () => {
const consoleSpy = vi.spyOn(console, 'log');
const logger = new Logger({ tags: ['test'] });
const subLogger = logger.subLogger({ tags: ['sub'] });
expect(subLogger).toBeInstanceOf(Logger);
subLogger.log('foo');
expect(consoleSpy).toHaveBeenCalledWith(['test', 'sub'], 'foo');
});

it('works with no options', () => {
const consoleSpy = vi.spyOn(console, 'log');
const logger = new Logger({ tags: ['test'] });
const subLogger = logger.subLogger();
expect(subLogger).toBeInstanceOf(Logger);
subLogger.log('foo');
expect(consoleSpy).toHaveBeenCalledWith(['test'], 'foo');
});

it('works with a string', () => {
const consoleSpy = vi.spyOn(console, 'log');
const logger = new Logger({ tags: ['test'] });
const subLogger = logger.subLogger('sub');
expect(subLogger).toBeInstanceOf(Logger);
subLogger.log('foo');
expect(consoleSpy).toHaveBeenCalledWith(['test', 'sub'], 'foo');
});
});
});

describe('consoleTransport', () => {
it.each(consoleMethod)('logs to the console with method %j', (method) => {
const consoleSpy = vi.spyOn(console, method);
const logger = new Logger({ tags: ['test'] });
logger[method]('foo');
expect(consoleSpy).toHaveBeenCalledWith(['test'], 'foo');
});

it.each(consoleMethod)('default data is an empty array for %j', (level) => {
const consoleSpy = vi.spyOn(console, level);
const entry = { tags: ['test'], level, message: 'foo' };
consoleTransport(entry);
expect(consoleSpy).toHaveBeenCalledWith(['test'], 'foo');
});

it('does not log when the level is silent', () => {
const consoleMethodSpies = consoleMethod.map((method) =>
vi.spyOn(console, method),
);
consoleTransport({ tags: ['test'], level: 'silent', message: 'foo' });
consoleMethodSpies.forEach((spy) => expect(spy).not.toHaveBeenCalled());
});
});

describe('mergeOptions', () => {
it.each([
{ left: ['test'], right: ['sub'], result: ['test', 'sub'] },
{ left: ['test', 'test'], right: ['sub'], result: ['test', 'sub'] },
{
left: ['test', 'fizz'],
right: ['test', 'buzz'],
result: ['test', 'fizz', 'buzz'],
},
])('merges tags as expected: $left and $right', ({ left, right, result }) => {
const options = mergeOptions({ tags: left }, { tags: right });
expect(options.tags).toStrictEqual(result);
});

it('defaults to the default options', () => {
const options = mergeOptions();
expect(options).toStrictEqual(DEFAULT_OPTIONS);
});

const transportA = vi.fn();
const transportB = vi.fn();

it.each([
{ left: { transports: [] }, right: { transports: [] }, result: [] },
{
left: { transports: [transportA] },
right: { transports: [] },
result: [transportA],
},
{
left: { transports: [transportA] },
right: { transports: [transportA] },
result: [transportA],
},
{
left: { transports: [transportA] },
right: { transports: [transportB] },
result: [transportA, transportB],
},
])(
'merges transports as expected: $left and $right',
({ left, right, result }) => {
const options = mergeOptions(left, right);
expect(options.transports).toStrictEqual([
...DEFAULT_OPTIONS.transports,
...result,
]);
},
);

it.each([
{ left: { level: 'warn' }, right: { level: 'error' }, result: 'error' },
{ left: { level: undefined }, right: { level: 'warn' }, result: 'warn' },
{ left: { level: 'info' }, right: {}, result: 'info' },
] as { left: LoggerOptions; right: LoggerOptions; result: LogLevel }[])(
'merges levels as expected: $left and $right',
({ left, right, result }) => {
const options = mergeOptions(
{ ...left, transports: [] },
{ ...right, transports: [] },
);
expect(options.level).toBe(result);
},
);
});

describe('makeLogger', () => {
it('creates a new logger from a label and a parent logger', () => {
const logger = new Logger({ tags: ['test'] });
const subLogger = makeLogger('sub', logger);
expect(subLogger).toBeInstanceOf(Logger);
});

it('creates a new logger from a label', () => {
const logSpy = vi.spyOn(console, 'log');
const logger = makeLogger('test');
expect(logger).toBeInstanceOf(Logger);
logger.log('foo');
expect(logSpy).toHaveBeenCalledWith(['test'], 'foo');
});
});
Loading
Loading