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
10 changes: 10 additions & 0 deletions src/__tests__/__snapshots__/logger.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ exports[`createLogger should attempt to subscribe and unsubscribe from a channel
}
`;

exports[`formatLogEvent should return a formatted log event, all available fields 1`] = `"[INFO]: lorem ipsum, debug :1 2 3"`;

exports[`formatLogEvent should return a formatted log event, default 1`] = `"[DEBUG]: lorem ipsum, debug"`;

exports[`formatLogEvent should return a formatted log event, null 1`] = `"[INFO]:"`;

exports[`formatLogEvent should return a formatted log event, partial 1`] = `"[DEBUG]:"`;

exports[`formatLogEvent should return a formatted log event, undefined 1`] = `"[INFO]:"`;

exports[`formatUnknownError should attempt to return a formatted error on non-errors, bigint 1`] = `"9007199254740991n"`;

exports[`formatUnknownError should attempt to return a formatted error on non-errors, boolean 1`] = `"Non-Error thrown: true"`;
Expand Down
43 changes: 42 additions & 1 deletion src/__tests__/logger.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import diagnostics_channel from 'node:diagnostics_channel';
import { setOptions, getLoggerOptions } from '../options.context';
import { logSeverity, truncate, formatUnknownError, publish, subscribeToChannel, registerStderrSubscriber, createLogger } from '../logger';
import { logSeverity, truncate, formatUnknownError, formatLogEvent, publish, subscribeToChannel, registerStderrSubscriber, createLogger } from '../logger';

describe('logSeverity', () => {
it.each([
Expand Down Expand Up @@ -113,6 +113,47 @@ describe('formatUnknownError', () => {
});
});

describe('formatLogEvent', () => {
it.each([
{
description: 'default',
event: {
level: 'debug',
timestamp: new Date('2025-11-01T00:00:00Z'),
msg: 'lorem ipsum, debug'
}
},
{
description: 'all available fields',
event: {
level: 'info',
timestamp: new Date('2025-11-01T00:00:00.000Z'),
msg: 'lorem ipsum, debug',
fields: { lorem: 'ipsum dolor sit amet' },
source: 'loremIpsum',
args: [1, 2, 3],
transport: 'dolorSit'
}
},
{
description: 'undefined',
event: undefined
},
{
description: 'null',
event: null
},
{
description: 'partial',
event: {
level: 'debug'
}
}
])('should return a formatted log event, $description', ({ event }) => {
expect(formatLogEvent(event as any)).toMatchSnapshot();
});
});

describe('publish', () => {
let channelSpy: jest.SpyInstance;
const mockPublish = jest.fn();
Expand Down
58 changes: 34 additions & 24 deletions src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,29 @@ const formatUnknownError = (value: unknown): string => {
}
};

/**
* Format a structured log event for output to stderr.
*
* @param event - Log event to format
*/
const formatLogEvent = (event: LogEvent) => {
const level = event?.level?.toUpperCase() || 'INFO';
const eventLevel = `[${level}]`;
const message = event?.msg || '';

const rest = event?.args?.map(arg => {
try {
return typeof arg === 'string' ? arg : JSON.stringify(arg);
} catch {
return String(arg);
}
}).join(' ') || '';

const separator = rest ? '\t:' : '';

return `${eventLevel}: ${message}${separator}${rest}`.trim();
};

/**
* Publish a structured log event to the diagnostics channel.
*
Expand Down Expand Up @@ -207,7 +230,7 @@ const subscribeToChannel = (
try {
handler.call(null, event);
} catch (error) {
log.debug('Error invoking logging subscriber', event, error);
process.stderr.write(`Error invoking logging subscriber: ${formatUnknownError(error)}\n`);
}
};

Expand All @@ -227,28 +250,14 @@ const subscribeToChannel = (
* @param [formatter] - Optional custom formatter for log events. Default prints: `[LEVEL] msg ...args`
* @returns Unsubscribe function to remove the subscriber
*/
const registerStderrSubscriber = (options: LoggingSession, formatter?: (e: LogEvent) => string): Unsubscribe => {
const format = formatter || ((event: LogEvent) => {
const eventLevel = `[${event.level.toUpperCase()}]`;
const message = event.msg || '';
const rest = event?.args?.map(arg => {
try {
return typeof arg === 'string' ? arg : JSON.stringify(arg);
} catch {
return String(arg);
}
}).join(' ') || '';
const separator = rest ? '\t:' : '';

return `${eventLevel}: ${message}${separator}${rest}`.trim();
});

return subscribeToChannel((event: LogEvent) => {
if (logSeverity(event.level) >= logSeverity(options.level)) {
process.stderr.write(`${format(event)}\n`);
}
});
};
const registerStderrSubscriber = (
options: LoggingSession,
formatter: (e: LogEvent) => string = formatLogEvent
): Unsubscribe => subscribeToChannel((event: LogEvent) => {
if (logSeverity(event.level) >= logSeverity(options.level)) {
process.stderr.write(`${formatter(event)}\n`);
}
});

/**
* Creates a logger initialization function and supports registering logging subscribers.
Expand All @@ -268,7 +277,7 @@ const createLogger = (options: LoggingSession = getLoggerOptions()): Unsubscribe
try {
unsubscribe();
} catch (error) {
log.debug('Error unsubscribing from diagnostics channel', error);
process.stderr.write(`Error unsubscribing from diagnostics channel: ${formatUnknownError(error)}\n`);
}
});

Expand All @@ -279,6 +288,7 @@ const createLogger = (options: LoggingSession = getLoggerOptions()): Unsubscribe
export {
LOG_LEVELS,
createLogger,
formatLogEvent,
formatUnknownError,
log,
logSeverity,
Expand Down
Loading