-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #29 from commenthol/feat-proclog
Feature: ProcLog
- Loading branch information
Showing
8 changed files
with
316 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import { LogBase } from './LogBase.js' | ||
import { Log } from './node.js' | ||
import { inspectOpts, inspectNamespaces, INFO } from './utils.js' | ||
|
||
/** | ||
* @typedef {import('./node.js').LogOptions} LogOptions | ||
*/ | ||
/** | ||
* @typedef {LogOptions & {Log: typeof Log}} LogOptionsWithCustomLog | ||
*/ | ||
|
||
export const EVENT_NAME = 'log-level' | ||
|
||
const defaultOptions = { | ||
level: INFO, | ||
namespaces: undefined | ||
} | ||
|
||
/** | ||
* Decouple logging via process event 'log'. This allows to use a different | ||
* logger framework than 'debug-level'. In such cases you'd need to adapt your | ||
* framework of choice for logging. Check `initProcLog()` for inspiration. | ||
* | ||
* Emits the following process event: | ||
* ``` | ||
* process.emit('log', level, name, fmt, args) | ||
* ``` | ||
* where | ||
* - `level` is TRACE, DEBUG, INFO, WARN, ERROR, FATAL, LOG | ||
* - `name` is namespace of the logger | ||
* - `fmt` is optional formatter, e.g. `%s` | ||
* - `args` is an array of arguments passed to the logger | ||
* | ||
* @example | ||
* ```js | ||
* import { ProcLog, initProcLog } from 'debug-level' | ||
* | ||
* // initialize process event logging with 'debug-level' | ||
* // define here serializer, stream options, etc. | ||
* initProcLog({ serializers: {...}, Log: LogEcs }) | ||
* | ||
* // add a logger with a namespace | ||
* // use options only for defining the logLevel (or leave undefined to control | ||
* // via env-vars) | ||
* const log = new ProcLog('app:namespace') | ||
* // add some logging | ||
* log.info('show some logging') | ||
* ``` | ||
*/ | ||
export class ProcLog extends LogBase { | ||
/** | ||
* creates a new logger | ||
* @param {String} name - namespace of Logger | ||
* @param {LogOptions} [opts] - see Log.options | ||
*/ | ||
constructor(name, opts) { | ||
const _opts = { | ||
...defaultOptions, | ||
...inspectOpts(process.env), | ||
...inspectNamespaces(process.env), | ||
...opts, | ||
// disallow numbers in event | ||
levelNumbers: false, | ||
// don't use serializers, define them in the initProcLog options | ||
serializers: {} | ||
} | ||
super(name, _opts) | ||
} | ||
|
||
_log(level, fmt, args) { | ||
// @ts-expect-error | ||
process.emit(EVENT_NAME, level, this.name, fmt, args) | ||
} | ||
} | ||
|
||
/** | ||
* logging via process event 'log' | ||
* @param {LogOptionsWithCustomLog} [options] | ||
*/ | ||
export function initProcLog(options) { | ||
const LogCls = options?.Log || Log | ||
const logger = {} | ||
const getLogger = (namespace) => | ||
logger[namespace] || (logger[namespace] = new LogCls(namespace, options)) | ||
|
||
// prevent multiple log-lines from adding more than one listener | ||
process.removeAllListeners(EVENT_NAME) | ||
// listen on event | ||
process.on(EVENT_NAME, (level, namespace, fmt, args) => { | ||
const log = getLogger(namespace) | ||
log[level.toLowerCase()]?.(fmt, ...args) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import assert from 'node:assert' | ||
import { LogEcs } from '../src/index.js' | ||
import { ProcLog, initProcLog, EVENT_NAME } from '../src/ProcLog.js' | ||
|
||
describe('ProcLog', function () { | ||
beforeEach(function () { | ||
initProcLog() | ||
}) | ||
|
||
it('should log via process.emit', function () { | ||
const { lines } = myInitProcLog() | ||
const log = new ProcLog('test:1') | ||
log.log('a log line') | ||
assert.deepEqual(lines, ['LOG', 'test:1', 'a log line', []]) | ||
}) | ||
|
||
it('should log deep object', function () { | ||
const { lines } = myInitProcLog() | ||
const log = new ProcLog('test:2') | ||
log.log({ a: { nested: 'object' } }) | ||
assert.deepEqual(lines, [ | ||
'LOG', | ||
'test:2', | ||
{ | ||
a: { | ||
nested: 'object' | ||
} | ||
}, | ||
[] | ||
]) | ||
}) | ||
|
||
it('should log deep object with format', function () { | ||
const { lines } = myInitProcLog() | ||
const log = new ProcLog('test:2') | ||
log.info('%j', { a: { nested: 'object' } }) | ||
assert.deepEqual(lines, [ | ||
'INFO', | ||
'test:2', | ||
'%j', | ||
[ | ||
{ | ||
a: { | ||
nested: 'object' | ||
} | ||
} | ||
] | ||
]) | ||
}) | ||
|
||
it('should use the Ecs logger', function () { | ||
initProcLog({ Log: LogEcs, json: true, colors: false }) | ||
const { lines } = myInitProcLog() | ||
const log = new ProcLog('test:3') | ||
log.warn('%j', { a: { nested: 'object' } }) | ||
assert.deepEqual(lines, [ | ||
'WARN', | ||
'test:3', | ||
'%j', | ||
[ | ||
{ | ||
a: { | ||
nested: 'object' | ||
} | ||
} | ||
] | ||
]) | ||
}) | ||
|
||
it('should not use number level or serializers', function () { | ||
initProcLog({ | ||
levelNumbers: true, | ||
serializers: { | ||
err: (err) => { | ||
return err?.message | ||
} | ||
} | ||
}) | ||
const { lines } = myInitProcLog() | ||
const log = new ProcLog('test:4') | ||
log.error({ err: { name: 'Error', message: 'error' } }) | ||
assert.deepEqual(lines, [ | ||
'ERROR', | ||
'test:4', | ||
{ | ||
err: { | ||
message: 'error', | ||
name: 'Error' | ||
} | ||
}, | ||
[] | ||
]) | ||
}) | ||
}) | ||
|
||
const myInitProcLog = () => { | ||
let lines = [] | ||
const reset = () => { | ||
lines = [] | ||
} | ||
process.on(EVENT_NAME, (...args) => { | ||
lines.push(...args) | ||
}) | ||
return { lines, reset } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/** | ||
* logging via process event 'log' | ||
* @param {LogOptionsWithCustomLog} [options] | ||
*/ | ||
export function initProcLog(options?: LogOptionsWithCustomLog): void; | ||
/** | ||
* @typedef {import('./node.js').LogOptions} LogOptions | ||
*/ | ||
/** | ||
* @typedef {LogOptions & {Log: typeof Log}} LogOptionsWithCustomLog | ||
*/ | ||
export const EVENT_NAME: "log-level"; | ||
/** | ||
* Decouple logging via process event 'log'. This allows to use a different | ||
* logger framework than 'debug-level'. In such cases you'd need to adapt your | ||
* framework of choice for logging. Check `initProcLog()` for inspiration. | ||
* | ||
* Emits the following process event: | ||
* ``` | ||
* process.emit('log', level, name, fmt, args) | ||
* ``` | ||
* where | ||
* - `level` is TRACE, DEBUG, INFO, WARN, ERROR, FATAL, LOG | ||
* - `name` is namespace of the logger | ||
* - `fmt` is optional formatter, e.g. `%s` | ||
* - `args` is an array of arguments passed to the logger | ||
* | ||
* @example | ||
* ```js | ||
* import { ProcLog, initProcLog } from 'debug-level' | ||
* | ||
* // initialize process event logging with 'debug-level' | ||
* // define here serializer, stream options, etc. | ||
* initProcLog({ serializers: {...}, Log: LogEcs }) | ||
* | ||
* // add a logger with a namespace | ||
* // use options only for defining the logLevel (or leave undefined to control | ||
* // via env-vars) | ||
* const log = new ProcLog('app:namespace') | ||
* // add some logging | ||
* log.info('show some logging') | ||
* ``` | ||
*/ | ||
export class ProcLog extends LogBase { | ||
/** | ||
* creates a new logger | ||
* @param {String} name - namespace of Logger | ||
* @param {LogOptions} [opts] - see Log.options | ||
*/ | ||
constructor(name: string, opts?: LogOptions); | ||
} | ||
export type LogOptions = import("./node.js").LogOptions; | ||
export type LogOptionsWithCustomLog = LogOptions & { | ||
Log: typeof Log; | ||
}; | ||
import { LogBase } from './LogBase.js'; | ||
import { Log } from './node.js'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters