From 8af6198968ad813d2cb87c5939b0219226c6515e Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 7 Apr 2021 19:07:42 +0200 Subject: [PATCH 1/6] Add pino.multistream Source code imported from https://github.com/pinojs/pino-multi-stream. --- benchmarks/multistream.js | 98 ++++++++ docs/api.md | 68 ++++++ docs/help.md | 23 +- lib/multistream.js | 138 +++++++++++ multistream.js | 98 ++++++++ pino.js | 2 + test/multistream.test.js | 476 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 884 insertions(+), 19 deletions(-) create mode 100644 benchmarks/multistream.js create mode 100644 lib/multistream.js create mode 100644 multistream.js create mode 100644 test/multistream.test.js diff --git a/benchmarks/multistream.js b/benchmarks/multistream.js new file mode 100644 index 000000000..d67b7e235 --- /dev/null +++ b/benchmarks/multistream.js @@ -0,0 +1,98 @@ +'use strict' + +const bench = require('fastbench') +const bunyan = require('bunyan') +const pino = require('../') +const fs = require('fs') +const dest = fs.createWriteStream('/dev/null') + +const tenStreams = [ + { stream: dest }, + { stream: dest }, + { stream: dest }, + { stream: dest }, + { stream: dest }, + { level: 'debug', stream: dest }, + { level: 'debug', stream: dest }, + { level: 'trace', stream: dest }, + { level: 'warn', stream: dest }, + { level: 'fatal', stream: dest } +] +const pinomsTen = pino({ level: 'debug' }, pino.multistream(tenStreams)) + +const fourStreams = [ + { stream: dest }, + { stream: dest }, + { level: 'debug', stream: dest }, + { level: 'trace', stream: dest } +] +const pinomsFour = pino({ level: 'debug' }, pino.multistream(fourStreams)) + +const pinomsOne = pino({ level: 'info' }, pino.multistream(dest)) +const blogOne = bunyan.createLogger({ + name: 'myapp', + streams: [{ stream: dest }] +}) + +const blogTen = bunyan.createLogger({ + name: 'myapp', + streams: tenStreams +}) +const blogFour = bunyan.createLogger({ + name: 'myapp', + streams: fourStreams +}) + +const max = 10 +const run = bench([ + function benchBunyanTen (cb) { + for (let i = 0; i < max; i++) { + blogTen.info('hello world') + blogTen.debug('hello world') + blogTen.trace('hello world') + blogTen.warn('hello world') + blogTen.fatal('hello world') + } + setImmediate(cb) + }, + function benchPinoMSTen (cb) { + for (let i = 0; i < max; i++) { + pinomsTen.info('hello world') + pinomsTen.debug('hello world') + pinomsTen.trace('hello world') + pinomsTen.warn('hello world') + pinomsTen.fatal('hello world') + } + setImmediate(cb) + }, + function benchBunyanFour (cb) { + for (let i = 0; i < max; i++) { + blogFour.info('hello world') + blogFour.debug('hello world') + blogFour.trace('hello world') + } + setImmediate(cb) + }, + function benchPinoMSFour (cb) { + for (let i = 0; i < max; i++) { + pinomsFour.info('hello world') + pinomsFour.debug('hello world') + pinomsFour.trace('hello world') + } + setImmediate(cb) + }, + function benchBunyanOne (cb) { + for (let i = 0; i < max; i++) { + blogOne.info('hello world') + } + setImmediate(cb) + }, + function benchPinoMSOne (cb) { + for (let i = 0; i < max; i++) { + pinomsOne.info('hello world') + } + setImmediate(cb) + } +], 10000) + +run() diff --git a/docs/api.md b/docs/api.md index 73b1bb4ad..ba893fa5a 100644 --- a/docs/api.md +++ b/docs/api.md @@ -24,6 +24,7 @@ * [Statics](#statics) * [pino.destination()](#pino-destination) * [pino.final()](#pino-final) + * [pino.multistream()](#pino-multistream) * [pino.stdSerializers](#pino-stdserializers) * [pino.stdTimeFunctions](#pino-stdtimefunctions) * [pino.symbols](#pino-symbols) @@ -938,6 +939,73 @@ finalLogger.info('exiting...') * See [Asynchronous logging ⇗](/docs/asynchronous.md) * See [Log loss prevention ⇗](/docs/asynchronous.md#log-loss-prevention) + + +### `pino.multistream(options) => Stream` + +Create a stream composed by multiple destination streams: + +```js +var fs = require('fs') +var pino = require('pino') +var streams = [ + {stream: fs.createWriteStream('/tmp/info.stream.out')}, + {level: 'debug', stream: fs.createWriteStream('/tmp/debug.stream.out')}, + {level: 'fatal', stream: fs.createWriteStream('/tmp/fatal.stream.out')} +] + +var log = pino({ + level: 'debug' // this MUST be set at the lowest level of the + // destinations +}, pino.multistream(streams)) + +log.debug('this will be written to /tmp/debug.stream.out') +log.info('this will be written to /tmp/debug.stream.out and /tmp/info.stream.out') +log.fatal('this will be written to /tmp/debug.stream.out, /tmp/info.stream.out and /tmp/fatal.stream.out') +``` + +In order for `multistream` to work, the log level _____must__ be set to the lowest level used in the streams array. + +#### Options + +* `levels`: Pass custom log level definitions to the instance as an object. + ++ `dedupe`: Set this to `true` to send logs only to the stream with the higher level. Default: `false` + + `dedupe` flag can be useful for example when using pino-multi-stream to redirect `error` logs to `process.stderr` and others to `process.stdout`: + + ```js + var pino = require('pino') + var multistream = require('pino-multi-stream').multistream + var streams = [ + {stream: process.stdout}, + {level: 'error', stream: process.stderr}, + ] + + var opts = { + levels: { + silent: Infinity, + fatal: 60, + error: 50, + warn: 50, + info: 30, + debug: 20, + trace: 10 + }, + dedupe: true, + } + + var log = pino({ + level: 'debug' // this MUST be set at the lowest level of the + // destinations + }, multistream(streams, opts)) + + log.debug('this will be written ONLY to process.stdout') + log.info('this will be written ONLY to process.stdout') + log.error('this will be written ONLY to process.stderr') + log.fatal('this will be written ONLY to process.stderr') + ``` + ### `pino.stdSerializers` (Object) diff --git a/docs/help.md b/docs/help.md index 2957b5202..091206716 100644 --- a/docs/help.md +++ b/docs/help.md @@ -118,20 +118,7 @@ Given a similar scenario as in the [Log rotation](#rotate) section a basic ## Saving to multiple files -Let's assume we want to store all error messages to a separate log file. - -Install [pino-tee](https://npm.im/pino-tee) with: - -```bash -npm i pino-tee -g -``` - -The following writes the log output of `app.js` to `./all-logs`, while -writing only warnings and errors to `./warn-log: - -```bash -node app.js | pino-tee warn ./warn-logs > ./all-logs -``` +See [`pino.multistream`](/doc/api.md#pino-multistream). ## Log Filtering @@ -164,14 +151,13 @@ ExecStart=/bin/sh -c '/path/to/node app.js | pino-transport' Pino's default log destination is the singular destination of `stdout`. While not recommended for performance reasons, multiple destinations can be targeted -by using [`pino-multi-stream`](https://github.com/pinojs/pino-multi-stream). +by using [`pino.multistream`](/doc/api.md#pino-multistream). In this example we use `stderr` for `error` level logs and `stdout` as default for all other levels (e.g. `debug`, `info`, and `warn`). ```js const pino = require('pino') -const { multistream } = require('pino-multi-stream') var streams = [ {level: 'debug', stream: process.stdout}, {level: 'error', stream: process.stderr}, @@ -180,11 +166,10 @@ var streams = [ const logger = pino({ name: 'my-app', - level: 'info', -}, multistream(streams)) + level: 'debug', // must be the lower level of all streams +}, pino.multistream(streams)) ``` - ## How Pino handles duplicate keys diff --git a/lib/multistream.js b/lib/multistream.js new file mode 100644 index 000000000..7ec058cc3 --- /dev/null +++ b/lib/multistream.js @@ -0,0 +1,138 @@ +'use strict' + +const metadata = Symbol.for('pino.metadata') + +const defaultLevels = { + silent: Infinity, + fatal: 60, + error: 50, + warn: 40, + info: 30, + debug: 20, + trace: 10 +} + +function multistream (streamsArray, opts) { + let counter = 0 + + streamsArray = streamsArray || [] + opts = opts || { dedupe: false } + + let levels = defaultLevels + if (opts.levels && typeof opts.levels === 'object') { + levels = opts.levels + } + + const res = { + write, + add, + flushSync, + minLevel: 0, + streams: [], + clone, + [metadata]: true + } + + if (Array.isArray(streamsArray)) { + streamsArray.forEach(add, res) + } else { + add.call(res, streamsArray) + } + + // clean this object up + // or it will stay allocated forever + // as it is closed on the following closures + streamsArray = null + + return res + + // we can exit early because the streams are ordered by level + function write (data) { + let dest + const level = this.lastLevel + const { streams } = this + let stream + for (let i = 0; i < streams.length; i++) { + dest = streams[i] + if (dest.level <= level) { + stream = dest.stream + if (stream[metadata]) { + const { lastTime, lastMsg, lastObj, lastLogger } = this + stream.lastLevel = level + stream.lastTime = lastTime + stream.lastMsg = lastMsg + stream.lastObj = lastObj + stream.lastLogger = lastLogger + } + if (!opts.dedupe) { + stream.write(data) + } + } else { + break + } + } + + if (opts.dedupe && stream) { + stream.write(data) + } + } + + function flushSync () { + for (const { stream } of this.streams) { + if (typeof stream.flushSync === 'function') { + stream.flushSync() + } + } + } + + function add (dest) { + const { streams } = this + if (typeof dest.write === 'function') { + return add.call(this, { stream: dest }) + } else if (typeof dest.levelVal === 'number') { + return add.call(this, Object.assign({}, dest, { level: dest.levelVal, levelVal: undefined })) + } else if (typeof dest.level === 'string') { + return add.call(this, Object.assign({}, dest, { level: levels[dest.level] })) + } else if (typeof dest.level !== 'number') { + // we default level to 'info' + dest = Object.assign({}, dest, { level: 30 }) + } else { + dest = Object.assign({}, dest) + } + dest.id = counter++ + + streams.unshift(dest) + streams.sort(compareByLevel) + + this.minLevel = streams[0].level + + return res + } + + function clone (level) { + const streams = new Array(this.streams.length) + + for (let i = 0; i < streams.length; i++) { + streams[i] = { + level: level, + stream: this.streams[i].stream + } + } + + return { + write, + add, + minLevel: level, + streams, + clone, + flushSync, + [metadata]: true + } + } +} + +function compareByLevel (a, b) { + return a.level - b.level +} + +module.exports = multistream diff --git a/multistream.js b/multistream.js new file mode 100644 index 000000000..2d06282c1 --- /dev/null +++ b/multistream.js @@ -0,0 +1,98 @@ +'use strict' + +const bench = require('fastbench') +const bunyan = require('bunyan') +const pinoms = require('./') +const fs = require('fs') +const dest = fs.createWriteStream('/dev/null') + +const tenStreams = [ + { stream: dest }, + { stream: dest }, + { stream: dest }, + { stream: dest }, + { stream: dest }, + { level: 'debug', stream: dest }, + { level: 'debug', stream: dest }, + { level: 'trace', stream: dest }, + { level: 'warn', stream: dest }, + { level: 'fatal', stream: dest } +] +const pinomsTen = pinoms({ streams: tenStreams }) + +const fourStreams = [ + { stream: dest }, + { stream: dest }, + { level: 'debug', stream: dest }, + { level: 'trace', stream: dest } +] +const pinomsFour = pinoms({ streams: fourStreams }) + +const pinomsOne = pinoms({ streams: [{ stream: dest }] }) +const blogOne = bunyan.createLogger({ + name: 'myapp', + streams: [{ stream: dest }] +}) + +const blogTen = bunyan.createLogger({ + name: 'myapp', + streams: tenStreams +}) +const blogFour = bunyan.createLogger({ + name: 'myapp', + streams: fourStreams +}) + +const max = 10 +const run = bench([ + function benchBunyanTen (cb) { + for (let i = 0; i < max; i++) { + blogTen.info('hello world') + blogTen.debug('hello world') + blogTen.trace('hello world') + blogTen.warn('hello world') + blogTen.fatal('hello world') + } + setImmediate(cb) + }, + function benchPinoMSTen (cb) { + for (let i = 0; i < max; i++) { + pinomsTen.info('hello world') + pinomsTen.debug('hello world') + pinomsTen.trace('hello world') + pinomsTen.warn('hello world') + pinomsTen.fatal('hello world') + } + setImmediate(cb) + }, + function benchBunyanFour (cb) { + for (let i = 0; i < max; i++) { + blogFour.info('hello world') + blogFour.debug('hello world') + blogFour.trace('hello world') + } + setImmediate(cb) + }, + function benchPinoMSFour (cb) { + for (let i = 0; i < max; i++) { + pinomsFour.info('hello world') + pinomsFour.debug('hello world') + pinomsFour.trace('hello world') + } + setImmediate(cb) + }, + function benchBunyanOne (cb) { + for (let i = 0; i < max; i++) { + blogOne.info('hello world') + } + setImmediate(cb) + }, + function benchPinoMSOne (cb) { + for (let i = 0; i < max; i++) { + pinomsOne.info('hello world') + } + setImmediate(cb) + } +], 10000) + +run() diff --git a/pino.js b/pino.js index 0d0e3e33f..05dddf226 100644 --- a/pino.js +++ b/pino.js @@ -223,6 +223,8 @@ module.exports.destination = (dest = process.stdout.fd) => { } } +module.exports.multistream = require('./lib/multistream') + module.exports.final = final module.exports.levels = mappings() module.exports.stdSerializers = serializers diff --git a/test/multistream.test.js b/test/multistream.test.js new file mode 100644 index 000000000..f5d49a0f5 --- /dev/null +++ b/test/multistream.test.js @@ -0,0 +1,476 @@ +'use strict' + +const writeStream = require('flush-write-stream') +const { join } = require('path') +const { readFileSync } = require('fs') +const os = require('os') +const test = require('tap').test +const pino = require('../') +const multistream = pino.multistream + +test('sends to multiple streams using string levels', function (t) { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const streams = [ + { stream: stream }, + { level: 'debug', stream: stream }, + { level: 'trace', stream: stream }, + { level: 'fatal', stream: stream }, + { level: 'silent', stream: stream } + ] + const log = pino({ + level: 'trace' + }, multistream(streams)) + log.info('info stream') + log.debug('debug stream') + log.fatal('fatal stream') + t.equal(messageCount, 9) + t.end() +}) + +test('sends to multiple streams using custom levels', function (t) { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const streams = [ + { stream: stream }, + { level: 'debug', stream: stream }, + { level: 'trace', stream: stream }, + { level: 'fatal', stream: stream }, + { level: 'silent', stream: stream } + ] + const log = pino({ + level: 'trace' + }, multistream(streams)) + log.info('info stream') + log.debug('debug stream') + log.fatal('fatal stream') + t.equal(messageCount, 9) + t.end() +}) + +test('sends to multiple streams using optionally predefined levels', function (t) { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const opts = { + levels: { + silent: Infinity, + fatal: 60, + error: 50, + warn: 50, + info: 30, + debug: 20, + trace: 10 + } + } + const streams = [ + { stream: stream }, + { level: 'trace', stream: stream }, + { level: 'debug', stream: stream }, + { level: 'info', stream: stream }, + { level: 'warn', stream: stream }, + { level: 'error', stream: stream }, + { level: 'fatal', stream: stream }, + { level: 'silent', stream: stream } + ] + const mstream = multistream(streams, opts) + const log = pino({ + level: 'trace' + }, mstream) + log.trace('trace stream') + log.debug('debug stream') + log.info('info stream') + log.warn('warn stream') + log.error('error stream') + log.fatal('fatal stream') + log.silent('silent stream') + t.equal(messageCount, 24) + t.end() +}) + +test('sends to multiple streams using number levels', function (t) { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const streams = [ + { stream: stream }, + { level: 20, stream: stream }, + { level: 60, stream: stream } + ] + const log = pino({ + level: 'debug' + }, multistream(streams)) + log.info('info stream') + log.debug('debug stream') + log.fatal('fatal stream') + t.equal(messageCount, 6) + t.end() +}) + +test('level include higher levels', function (t) { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const log = pino({}, multistream([{ level: 'info', stream: stream }])) + log.fatal('message') + t.equal(messageCount, 1) + t.end() +}) + +test('supports multiple arguments', function (t) { + const messages = [] + const stream = writeStream(function (data, enc, cb) { + messages.push(JSON.parse(data)) + if (messages.length === 2) { + const msg1 = messages[0] + t.equal(msg1.msg, 'foo bar baz foobar') + + const msg2 = messages[1] + t.equal(msg2.msg, 'foo bar baz foobar barfoo foofoo') + + t.end() + } + cb() + }) + const log = pino({}, multistream({ stream })) + log.info('%s %s %s %s', 'foo', 'bar', 'baz', 'foobar') // apply not invoked + log.info('%s %s %s %s %s %s', 'foo', 'bar', 'baz', 'foobar', 'barfoo', 'foofoo') // apply invoked +}) + +test('supports children', function (t) { + const stream = writeStream(function (data, enc, cb) { + const input = JSON.parse(data) + t.equal(input.msg, 'child stream') + t.equal(input.child, 'one') + t.end() + cb() + }) + const streams = [ + { stream: stream } + ] + const log = pino({}, multistream(streams)).child({ child: 'one' }) + log.info('child stream') +}) + +test('supports grandchildren', function (t) { + const messages = [] + const stream = writeStream(function (data, enc, cb) { + messages.push(JSON.parse(data)) + if (messages.length === 3) { + const msg1 = messages[0] + t.equal(msg1.msg, 'grandchild stream') + t.equal(msg1.child, 'one') + t.equal(msg1.grandchild, 'two') + + const msg2 = messages[1] + t.equal(msg2.msg, 'grandchild stream') + t.equal(msg2.child, 'one') + t.equal(msg2.grandchild, 'two') + + const msg3 = messages[2] + t.equal(msg3.msg, 'debug grandchild') + t.equal(msg3.child, 'one') + t.equal(msg3.grandchild, 'two') + + t.end() + } + cb() + }) + const streams = [ + { stream: stream }, + { level: 'debug', stream: stream } + ] + const log = pino({ + level: 'debug' + }, multistream(streams)).child({ child: 'one' }).child({ grandchild: 'two' }) + log.info('grandchild stream') + log.debug('debug grandchild') +}) + +test('supports custom levels', function (t) { + const stream = writeStream(function (data, enc, cb) { + t.equal(JSON.parse(data).msg, 'bar') + t.end() + }) + const log = pino({ + customLevels: { + foo: 35 + } + }, multistream([{ level: 35, stream: stream }])) + log.foo('bar') +}) + +test('supports pretty print', function (t) { + const stream = writeStream(function (data, enc, cb) { + t.not(data.toString().match(/INFO.*: pretty print/), null) + t.end() + cb() + }) + const outStream = pino({ + prettyPrint: { + levelFirst: true, + colorize: false + } + }, stream) + + const log = pino({ + level: 'debug', + name: 'helloName' + }, multistream([ + { stream: outStream[pino.symbols.streamSym] } + ])) + + log.info('pretty print') +}) + +test('children support custom levels', function (t) { + const stream = writeStream(function (data, enc, cb) { + t.equal(JSON.parse(data).msg, 'bar') + t.end() + }) + const parent = pino({ + customLevels: { + foo: 35 + } + }, multistream([{ level: 35, stream: stream }])) + const child = parent.child({ child: 'yes' }) + child.foo('bar') +}) + +test('levelVal ovverides level', function (t) { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const streams = [ + { stream: stream }, + { level: 'blabla', levelVal: 15, stream: stream }, + { level: 60, stream: stream } + ] + const log = pino({ + level: 'debug' + }, multistream(streams)) + log.info('info stream') + log.debug('debug stream') + log.fatal('fatal stream') + t.equal(messageCount, 6) + t.end() +}) + +test('forwards metadata', function (t) { + t.plan(3) + const streams = [ + { + stream: { + [Symbol.for('pino.metadata')]: true, + write (chunk) { + t.equal(log, this.lastLogger) + t.equal(30, this.lastLevel) + t.same({ hello: 'world', msg: 'a msg' }, this.lastObj) + } + } + } + ] + + const log = pino({ + level: 'debug' + }, multistream(streams)) + + log.info({ hello: 'world' }, 'a msg') + t.end() +}) + +test('forward name', function (t) { + t.plan(2) + const streams = [ + { + stream: { + [Symbol.for('pino.metadata')]: true, + write (chunk) { + const line = JSON.parse(chunk) + t.equal(line.name, 'helloName') + t.equal(line.hello, 'world') + } + } + } + ] + + const log = pino({ + level: 'debug', + name: 'helloName' + }, multistream(streams)) + + log.info({ hello: 'world' }, 'a msg') + t.end() +}) + +test('forward name with child', function (t) { + t.plan(3) + const streams = [ + { + stream: { + write (chunk) { + const line = JSON.parse(chunk) + t.equal(line.name, 'helloName') + t.equal(line.hello, 'world') + t.equal(line.component, 'aComponent') + } + } + } + ] + + const log = pino({ + level: 'debug', + name: 'helloName' + }, multistream(streams)).child({ component: 'aComponent' }) + + log.info({ hello: 'world' }, 'a msg') + t.end() +}) + +test('clone generates a new multistream with all stream at the same level', function (t) { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const streams = [ + { stream: stream }, + { level: 'debug', stream: stream }, + { level: 'trace', stream: stream }, + { level: 'fatal', stream: stream } + ] + const ms = multistream(streams) + const clone = ms.clone(30) + + t.not(clone, ms) + + clone.streams.forEach((s, i) => { + t.not(s, streams[i]) + t.equal(s.stream, streams[i].stream) + t.equal(s.level, 30) + }) + + const log = pino({ + level: 'trace' + }, clone) + + log.info('info stream') + log.debug('debug message not counted') + log.fatal('fatal stream') + t.equal(messageCount, 8) + + t.end() +}) + +test('one stream', function (t) { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const log = pino({ + level: 'trace' + }, multistream({ stream, level: 'fatal' })) + log.info('info stream') + log.debug('debug stream') + log.fatal('fatal stream') + t.equal(messageCount, 1) + t.end() +}) + +test('dedupe', function (t) { + let messageCount = 0 + const stream1 = writeStream(function (data, enc, cb) { + messageCount -= 1 + cb() + }) + + const stream2 = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + + const streams = [ + { + stream: stream1, + level: 'info' + }, + { + stream: stream2, + level: 'fatal' + } + ] + + const log = pino({ + level: 'trace' + }, multistream(streams, { dedupe: true })) + log.info('info stream') + log.fatal('fatal stream') + log.fatal('fatal stream') + t.equal(messageCount, 1) + t.end() +}) + +test('no stream', function (t) { + const log = pino({ + level: 'trace' + }, multistream()) + log.info('info stream') + log.debug('debug stream') + log.fatal('fatal stream') + t.end() +}) + +test('add a stream', function (t) { + let messageCount = 0 + const stream = writeStream(function (data, enc, cb) { + messageCount += 1 + cb() + }) + const log = pino({ + level: 'trace' + }, multistream(stream)) + log.info('info stream') + log.debug('debug stream') + log.fatal('fatal stream') + t.equal(messageCount, 2) + t.end() +}) + +test('flushSync', function (t) { + const tmp = join( + os.tmpdir(), + '_' + Math.random().toString(36).substr(2, 9) + ) + const destination = pino.destination({ dest: tmp, sync: false, minLength: 4096 }) + const log = pino({ level: 'info' }, multistream([{ level: 'info', stream: destination }])) + destination.on('ready', () => { + log.info('foo') + log.info('bar') + t.equal(readFileSync(tmp, { encoding: 'utf-8' }).split('\n').length - 1, 0) + pino.final(log, (err, finalLogger) => { + if (err) { + t.fail() + return t.end() + } + t.equal(readFileSync(tmp, { encoding: 'utf-8' }).split('\n').length - 1, 2) + finalLogger.info('biz') + t.equal(readFileSync(tmp, { encoding: 'utf-8' }).split('\n').length - 1, 3) + t.end() + })() + }) +}) From a0f5998aa4dd32d0f8dedba63d55898ed5985a59 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 8 Apr 2021 01:19:51 +0200 Subject: [PATCH 2/6] Update docs/api.md Co-authored-by: James Sumners --- docs/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.md b/docs/api.md index ba893fa5a..a0b9020d5 100644 --- a/docs/api.md +++ b/docs/api.md @@ -964,7 +964,7 @@ log.info('this will be written to /tmp/debug.stream.out and /tmp/info.stream.out log.fatal('this will be written to /tmp/debug.stream.out, /tmp/info.stream.out and /tmp/fatal.stream.out') ``` -In order for `multistream` to work, the log level _____must__ be set to the lowest level used in the streams array. +In order for `multistream` to work, the log level __must__ be set to the lowest level used in the streams array. #### Options From ddac63fae4cfe44f2b392920721f5a804135a364 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 8 Apr 2021 01:20:20 +0200 Subject: [PATCH 3/6] Update docs/api.md Co-authored-by: James Sumners --- docs/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.md b/docs/api.md index a0b9020d5..e82492cc2 100644 --- a/docs/api.md +++ b/docs/api.md @@ -976,7 +976,7 @@ In order for `multistream` to work, the log level __must__ be set to the lowest ```js var pino = require('pino') - var multistream = require('pino-multi-stream').multistream + var multistream = pino.multistream var streams = [ {stream: process.stdout}, {level: 'error', stream: process.stderr}, From 0636c9cc4482480b0c0bd93356f72d9bea855077 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 8 Apr 2021 01:20:43 +0200 Subject: [PATCH 4/6] Update docs/help.md Co-authored-by: James Sumners --- docs/help.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/help.md b/docs/help.md index 091206716..b713ad610 100644 --- a/docs/help.md +++ b/docs/help.md @@ -166,7 +166,7 @@ var streams = [ const logger = pino({ name: 'my-app', - level: 'debug', // must be the lower level of all streams + level: 'debug', // must be the lowest level of all streams }, pino.multistream(streams)) ``` From 14ca1c2dd99279b827c7e40942ff15857ead70bd Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 8 Apr 2021 01:22:24 +0200 Subject: [PATCH 5/6] deleted multistream.js --- multistream.js | 98 -------------------------------------------------- 1 file changed, 98 deletions(-) delete mode 100644 multistream.js diff --git a/multistream.js b/multistream.js deleted file mode 100644 index 2d06282c1..000000000 --- a/multistream.js +++ /dev/null @@ -1,98 +0,0 @@ -'use strict' - -const bench = require('fastbench') -const bunyan = require('bunyan') -const pinoms = require('./') -const fs = require('fs') -const dest = fs.createWriteStream('/dev/null') - -const tenStreams = [ - { stream: dest }, - { stream: dest }, - { stream: dest }, - { stream: dest }, - { stream: dest }, - { level: 'debug', stream: dest }, - { level: 'debug', stream: dest }, - { level: 'trace', stream: dest }, - { level: 'warn', stream: dest }, - { level: 'fatal', stream: dest } -] -const pinomsTen = pinoms({ streams: tenStreams }) - -const fourStreams = [ - { stream: dest }, - { stream: dest }, - { level: 'debug', stream: dest }, - { level: 'trace', stream: dest } -] -const pinomsFour = pinoms({ streams: fourStreams }) - -const pinomsOne = pinoms({ streams: [{ stream: dest }] }) -const blogOne = bunyan.createLogger({ - name: 'myapp', - streams: [{ stream: dest }] -}) - -const blogTen = bunyan.createLogger({ - name: 'myapp', - streams: tenStreams -}) -const blogFour = bunyan.createLogger({ - name: 'myapp', - streams: fourStreams -}) - -const max = 10 -const run = bench([ - function benchBunyanTen (cb) { - for (let i = 0; i < max; i++) { - blogTen.info('hello world') - blogTen.debug('hello world') - blogTen.trace('hello world') - blogTen.warn('hello world') - blogTen.fatal('hello world') - } - setImmediate(cb) - }, - function benchPinoMSTen (cb) { - for (let i = 0; i < max; i++) { - pinomsTen.info('hello world') - pinomsTen.debug('hello world') - pinomsTen.trace('hello world') - pinomsTen.warn('hello world') - pinomsTen.fatal('hello world') - } - setImmediate(cb) - }, - function benchBunyanFour (cb) { - for (let i = 0; i < max; i++) { - blogFour.info('hello world') - blogFour.debug('hello world') - blogFour.trace('hello world') - } - setImmediate(cb) - }, - function benchPinoMSFour (cb) { - for (let i = 0; i < max; i++) { - pinomsFour.info('hello world') - pinomsFour.debug('hello world') - pinomsFour.trace('hello world') - } - setImmediate(cb) - }, - function benchBunyanOne (cb) { - for (let i = 0; i < max; i++) { - blogOne.info('hello world') - } - setImmediate(cb) - }, - function benchPinoMSOne (cb) { - for (let i = 0; i < max; i++) { - pinomsOne.info('hello world') - } - setImmediate(cb) - } -], 10000) - -run() From 9ac31324837e75605f1217916bb9e87c4928bd1d Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 8 Apr 2021 01:27:43 +0200 Subject: [PATCH 6/6] resue levels --- lib/levels.js | 1 + lib/multistream.js | 12 +++--------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/levels.js b/lib/levels.js index df65e5e67..1a707277b 100644 --- a/lib/levels.js +++ b/lib/levels.js @@ -188,6 +188,7 @@ module.exports = { setLevel, isLevelEnabled, mappings, + levels, assertNoLevelCollisions, assertDefaultLevelFound } diff --git a/lib/multistream.js b/lib/multistream.js index 7ec058cc3..3756523a6 100644 --- a/lib/multistream.js +++ b/lib/multistream.js @@ -1,16 +1,10 @@ 'use strict' const metadata = Symbol.for('pino.metadata') +const { levels } = require('./levels') -const defaultLevels = { - silent: Infinity, - fatal: 60, - error: 50, - warn: 40, - info: 30, - debug: 20, - trace: 10 -} +const defaultLevels = Object.create(levels) +defaultLevels.silent = Infinity function multistream (streamsArray, opts) { let counter = 0