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
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Fully typed with JSDocs and Typescript.
* [Settings](#settings)
* [Environment Variables](#environment-variables)
* [Options](#options)
* [Writing to file](#writing-to-file)
* [Serializers](#serializers)
* [Levels](#levels)
* [Namespaces](#namespaces)
Expand All @@ -46,6 +47,7 @@ Fully typed with JSDocs and Typescript.
* [Wrap console logs](#wrap-console-logs)
* [Wrap debug output](#wrap-debug-output)
* [Handle node exit events](#handle-node-exit-events)
* [Emit Log events with ProcLog](#emit-log-events-with-proclog)
* [Logging HTTP requests](#logging-http-requests)
* [Logging Browser messages](#logging-browser-messages)
* [Logging in Elastic Common Schema (ECS)](#logging-in-elastic-common-schema-ecs)
Expand Down Expand Up @@ -253,7 +255,7 @@ log.debug({ object: 1 }) // ...
Consider using a tool like [logrotate](https://github.com/logrotate/logrotate) to rotate the log-file.

```sh
$ node server.js 2> /var/log/server.log
$ node server.js 2> /var/log/server.log
```

To rotate the file with logrotate, add the following to `/etc/logrotate.d/server`:
Expand Down Expand Up @@ -535,6 +537,43 @@ Log.handleExitEvents()
Log.handleExitEvents('process-exit')
```

## Emit Log events with ProcLog

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', level, name, fmt, args)
```

where
- `level` is TRACE, DEBUG, INFO, WARN, ERROR, FATAL, LOG
- `name` is the namespace of the logger
- `fmt` is optional formatter, e.g. `%s`
- `args` is an array of arguments passed to the logger

Only enabled namespaces emit log events.

```js
import { ProcLog, initProcLog } from 'debug-level'

// Initialize process event logging with 'debug-level'
// define here serializer, stream options, etc.
// If using a different logger you'd need to provide a custom initializer which
// connects to the framework of choice.
initProcLog({ serializers: {...}, Log: LogEcs })

// Add a logger with a namespace.
// Use options only for defining the log-level (or leave undefined to control
// via env-vars)
const log = new ProcLog('app:namespace')
// add some logging
log.info('show some logging')
```

## Logging HTTP requests

To log http requests/ responses you can enable the `httpLogs` middleware in your
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "debug-level",
"version": "4.0.0",
"version": "4.1.0-0",
"description": "debug with levels",
"keywords": [
"debug",
Expand Down
93 changes: 93 additions & 0 deletions src/ProcLog.js
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)
})
}
12 changes: 11 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,17 @@ import { LogEcs } from './ecs/LogEcs.js'
import { logger } from './logger.js'
import { browserLogs } from './browserLogs.js'
import { httpLogs } from './httpLogs.js'
import { ProcLog, initProcLog, EVENT_NAME } from './ProcLog.js'

export default Log

export { Log, LogEcs, logger, browserLogs, httpLogs }
export {
Log,
LogEcs,
logger,
ProcLog,
initProcLog,
EVENT_NAME,
browserLogs,
httpLogs
}
6 changes: 5 additions & 1 deletion src/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,11 @@ export class Log extends LogBase {
* @private
*/
_color(str, color, isBold) {
return !this.opts.colors ? str : isBold ? color.bold(str) : color(str)
return !color || !this.opts?.colors
? str
: isBold
? color.bold(str)
: color(str)
}
}

Expand Down
105 changes: 105 additions & 0 deletions test/ProcLog.test.js
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 }
}
57 changes: 57 additions & 0 deletions types/ProcLog.d.ts
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';
5 changes: 4 additions & 1 deletion types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export type IncomingMessageWithId = import("./httpLogs.js").IncomingMessageWithI
import { Log } from './node.js';
import { LogEcs } from './ecs/LogEcs.js';
import { logger } from './logger.js';
import { ProcLog } from './ProcLog.js';
import { initProcLog } from './ProcLog.js';
import { EVENT_NAME } from './ProcLog.js';
import { browserLogs } from './browserLogs.js';
import { httpLogs } from './httpLogs.js';
export { Log, LogEcs, logger, browserLogs, httpLogs };
export { Log, LogEcs, logger, ProcLog, initProcLog, EVENT_NAME, browserLogs, httpLogs };