Skip to content

Commit

Permalink
feat(Throttling): Allow for throttling of events in handler
Browse files Browse the repository at this point in the history
  • Loading branch information
nokome committed Sep 3, 2019
1 parent 44c08ab commit b11633a
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 6 deletions.
51 changes: 49 additions & 2 deletions index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
LogLevel,
removeHandler,
removeHandlers,
replaceHandlers
replaceHandlers,
defaultHandler
} from './index'

test('logging', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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()
})

45 changes: 43 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ export function replaceHandlers(handler: LogHandler) {
addHandler(handler)
}

const defaultHandlerHistory = new Map<string, number>()

/**
* Default log data handler.
*
Expand All @@ -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
Expand All @@ -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 })
}
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down

0 comments on commit b11633a

Please # to comment.