Skip to content
Open
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
42 changes: 34 additions & 8 deletions crates/bindings-typescript/src/sdk/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,39 @@ export const stringify = (value: unknown): string | undefined =>
return current;
});

/**
* Receives the SDK's log output once it has passed the global log level.
* Lazy messages and arguments are already resolved.
*/
export type Logger = (
level: LogLevel,
message: unknown,
...args: unknown[]
) => void;

/** The default {@link Logger}: styled output through `console.log`. */
export const consoleLogger: Logger = (level, message, ...args) => {
console.log(
`%c${LogLevelIdentifierIcon[level]} ${level.toUpperCase()}%c ${message}`,
LogStyle[level],
LogTextStyle[level],
...args
);
};

let globalLogger: Logger = consoleLogger;

/**
* Replaces where the SDK's log output goes, e.g. to forward it to an app's
* own logger, or to environments where `%c` styling isn't supported (such as
* React Native). Pass {@link consoleLogger} to restore the default.
*/
export const setGlobalLogger = (logger: Logger): void => {
globalLogger = logger;
};

export const getGlobalLogger = (): Logger => globalLogger;

export const stdbLogger = (
level: LogLevel,
message: Lazy<any>,
Expand All @@ -123,12 +156,5 @@ export const stdbLogger = (
if (!shouldLog(level)) {
return;
}
const resolvedMessage = resolveLazy(message);
const resolvedArgs = args.map(resolveLazy);
console.log(
`%c${LogLevelIdentifierIcon[level]} ${level.toUpperCase()}%c ${resolvedMessage}`,
LogStyle[level],
LogTextStyle[level],
...resolvedArgs
);
globalLogger(level, resolveLazy(message), ...args.map(resolveLazy));
};
47 changes: 47 additions & 0 deletions crates/bindings-typescript/tests/logger.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { ConnectionId, Identity } from '../src';
import {
consoleLogger,
getGlobalLogger,
getGlobalLogLevel,
setGlobalLogger,
setGlobalLogLevel,
stringify,
stdbLogger,
Expand All @@ -10,6 +13,7 @@ import {
describe('logger', () => {
beforeEach(() => {
setGlobalLogLevel('debug');
setGlobalLogger(consoleLogger);
vi.restoreAllMocks();
});

Expand Down Expand Up @@ -54,6 +58,49 @@ describe('logger', () => {
expect(getGlobalLogLevel()).toBe('info');
});

test('setGlobalLogger receives resolved logs instead of the console', () => {
const spy = vi.spyOn(console, 'log').mockImplementation(() => {});
const logger = vi.fn();

setGlobalLogger(logger);
stdbLogger(
'warn',
() => 'lazy message',
() => 'lazy arg',
42
);

expect(logger).toHaveBeenCalledWith('warn', 'lazy message', 'lazy arg', 42);
expect(spy).not.toHaveBeenCalled();
});

test('setGlobalLogger still respects the global log level', () => {
const logger = vi.fn();
const lazyMessage = vi.fn(() => 'computed');

setGlobalLogger(logger);
setGlobalLogLevel('error');
stdbLogger('info', lazyMessage);

expect(logger).not.toHaveBeenCalled();
expect(lazyMessage).not.toHaveBeenCalled();
});

test('consoleLogger restores the default output', () => {
const spy = vi.spyOn(console, 'log').mockImplementation(() => {});

setGlobalLogger(vi.fn());
setGlobalLogger(consoleLogger);
stdbLogger('error', 'error message');

expect(getGlobalLogger()).toBe(consoleLogger);
expect(spy).toHaveBeenCalledWith(
expect.stringContaining('ERROR%c error message'),
expect.any(String),
expect.any(String)
);
});

test('stringify renders short Uint8Array as full hex', () => {
const payload = new Uint8Array([0, 1, 15, 16, 255]);
expect(stringify({ payload })).toBe('{"payload":"0x00010f10ff"}');
Expand Down