diff --git a/index.test.ts b/index.test.ts index a3f3b82..c4a34d2 100644 --- a/index.test.ts +++ b/index.test.ts @@ -5,7 +5,8 @@ import { LogLevel, removeHandler, removeHandlers, - replaceHandlers + replaceHandlers, + defaultHandler } from './index' test('logging', () => { @@ -40,7 +41,6 @@ test('logging', () => { expect(events[3].stack).toMatch(/^Error:/) // Second line (first call stack) in stack trace should be this file expect(events[3].stack.split('\n')[1]).toMatch(/logga\/index\.test\.ts/) - }) test('TTY', () => { @@ -108,3 +108,50 @@ test('adding and removing handlers', () => { // no more logging to console expect(consoleError.mock.calls.length).toBe(consoleErrorCalls) }) + +test('defaultHandler:level', () => { + const log = getLogger('logger') + + const consoleError = jest.spyOn(console, 'error') + const callsStart = consoleError.mock.calls.length + + log.debug('a debug message') + expect(consoleError.mock.calls.length).toBe(callsStart + 0) + + replaceHandlers(data => defaultHandler(data, { level: LogLevel.debug })) + log.debug('a debug message') + expect(consoleError.mock.calls.length).toBe(callsStart + 1) + + replaceHandlers(data => defaultHandler(data, { level: LogLevel.warn })) + log.debug('a debug message') + log.warn('a warn message') + expect(consoleError.mock.calls.length).toBe(callsStart + 2) + + removeHandlers() +}) + +test('defaultHandler:throttle', async () => { + const log = getLogger('logger') + + const consoleError = jest.spyOn(console, 'error') + const callsStart = consoleError.mock.calls.length + + replaceHandlers(data => defaultHandler(data, { throttle: { signature: '${message}', duration: 200 } })) + + log.error('a message') + expect(consoleError.mock.calls.length).toBe(callsStart + 1) + + log.error('a message') + expect(consoleError.mock.calls.length).toBe(callsStart + 1) + + await (new Promise(resolve => setTimeout(resolve, 300))) + + log.error('a message') + expect(consoleError.mock.calls.length).toBe(callsStart + 2) + + log.error('a different message') + expect(consoleError.mock.calls.length).toBe(callsStart + 3) + + removeHandlers() +}) + diff --git a/index.ts b/index.ts index 9010223..fd4312d 100644 --- a/index.ts +++ b/index.ts @@ -102,6 +102,8 @@ export function replaceHandlers(handler: LogHandler) { addHandler(handler) } +const defaultHandlerHistory = new Map() + /** * Default log data handler. * @@ -111,8 +113,47 @@ export function replaceHandlers(handler: LogHandler) { * - as JSON if stderr is not TTY (for machine consumption e.g. log files) * * @param data The log data to handle + * @param level The maximum log level to print. Defaults to `info` + * @param throttle.signature The log event signature to use for throttling. Defaults to '' (i.e. all events) + * @param throttle.duration The duration for throttling (milliseconds). Defaults to 1000ms */ -export function defaultHandler(data: LogData) { +export function defaultHandler( + data: LogData, + options?: { + level?: LogLevel + throttle?: { + signature?: string + duration?: number + } + } +) { + // Skip if greater than desired reporting level + const level = + options !== undefined && options.level !== undefined + ? options.level + : LogLevel.info + if (data.level > level) return + + // Skip if within throttling duration for the event signature + const throttle = options !== undefined ? options.throttle : undefined + if (throttle !== undefined) { + const signature = throttle.signature !== undefined + ? throttle.signature + : '' + const eventSignature = signature + .replace(/\${tag}/, data.tag) + .replace(/\${level}/, data.level.toString()) + .replace(/\${message}/, data.message) + const lastTime = defaultHandlerHistory.get(eventSignature) + if (lastTime !== undefined) { + const duration = throttle.duration !== undefined + ? throttle.duration + : 1000 + if ((Date.now() - lastTime) < duration) return + } + defaultHandlerHistory.set(eventSignature, Date.now()) + } + let entry if (process.stderr.isTTY) { const { tag, level, message, stack } = data @@ -133,7 +174,7 @@ export function defaultHandler(data: LogData) { const cyan = '\u001b[36m' const reset = '\u001b[0m' entry = `${emoji} ${colour}${label}${reset} ${cyan}${tag}${reset} ${message}` - if (level === LogLevel.error) entry += '\n ' + stack + if (entry.stack) entry += '\n ' + stack } else { entry = JSON.stringify({ time: new Date().toISOString(), ...data }) } diff --git a/package.json b/package.json index 231912b..d14ed5e 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "scripts": { "format": "npx prettier --write './**/*.{md,ts}'", "lint": "eslint 'src/**/*.{ts,js}' --fix", - "test": "jest", - "test:cover": "jest --collectCoverage", + "test": "jest --runInBand", + "test:cover": "jest --runInBand --collectCoverage", "build": "tsc index.ts --outDir dist --declaration" }, "repository": {