diff --git a/docs/api.md b/docs/api.md index 1e9c58ba3..697a22a1b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -936,9 +936,12 @@ const transports = pino.transport({ level: 'info', target: 'some-transport', options: { some: 'options for', the: 'transport' } + }, { + level: 'info', + target: 'pino-pretty' }, { level: 'trace', - target: '#pino/file', + target: 'pino/file', options: { destination: '/path/to/store/logs' } }] }) @@ -960,56 +963,11 @@ For more on transports, how they work, and how to create them see the [`Transpor #### Options -* `target`: The transport to pass logs through. This may be an installed module name, an absolute path or a built-in transport (see [Transport Builtins](#transport-builtins)) +* `target`: The transport to pass logs through. This may be an installed module name or an absolute path. * `options`: An options object which is serialized (see [Structured Clone Algorithm][https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm]), passed to the worker thread, parsed and then passed to the exported transport function. * `worker`: [Worker thread](https://nodejs.org/api/worker_threads.html#worker_threads_new_worker_filename_options) configuration options. Additionally, the `worker` option supports `worker.autoEnd`. If this is set to `false` logs will not be flushed on process exit. It is then up to the developer to call `transport.end()` to flush logs. * `targets`: May be specified instead of `target`. Must be an array of transport configurations. Transport configurations include the aforementioned `options` and `target` options plus a `level` option which will send only logs above a specified level to a transport. -#### Transport Builtins - -The `target` option may be an absolute path, a module name or builtin. - -A transport builtin takes the form `#pino/`. - -The following transport builtins are supported: - -##### `#pino/file` - -The `#pino/file` builtin routes logs to a file (or file descriptor). - -The `options.destination` property may be set to specify the desired file destination. - -```js -const pino = require('pino') -const transport = pino.transport({ - target: '#pino/file', - options: { destination: '/path/to/file' } -}) -pino(transport) -``` - -The `options.destination` property may also be a number to represent a file descriptor. Typically this would be `1` to write to STDOUT or `2` to write to STDERR. If `options.destination` is not set, it defaults to `1` which means logs will be written to STDOUT. - -The difference between using the `#pino/file` transport builtin and using `pino.destination` is that `pino.destination` runs in the main thread, whereas `#pino/file` sets up `pino.destination` in a worker thread. - -##### `#pino/pretty` - -The `#pino/pretty` builtin prettifies logs. - - -By default the `#pino/pretty` builtin logs to STDOUT. - -The `options.destination` property may be set to log pretty logs to a file descriptor or file. The following would send the prettified logs to STDERR: - -```js -const pino = require('pino') -const transport = pino.transport({ - target: '#pino/pretty', - options: { destination: 2 } -}) -pino(transport) -``` - ### `pino.final(logger, [handler]) => Function | FinalLogger` diff --git a/docs/transports.md b/docs/transports.md index d01697c62..b36dd17b4 100644 --- a/docs/transports.md +++ b/docs/transports.md @@ -16,7 +16,6 @@ now referred to as [Legacy Transports](#legacy-transports). From Pino v7 and upwards transports can also operate inside a [Worker Thread][worker-thread], and can be used or configured via the options object passed to `pino` on initialization. - [worker-thread]: https://nodejs.org/dist/latest-v14.x/docs/api/worker_threads.html ## v7+ Transports @@ -107,6 +106,80 @@ For more details on `pino.transport` see the [API docs for `pino.transport`][pin [pino-transport]: /docs/api.md#pino-transport [sca]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm +### Writing a Transport + +The module [pino-abstract-transport](https://github.com/pinojs/pino-abstract-transport) provides +a simple utility to parse each line. Its usage is highly recommended. + +You can see an example using a async iterator with ESM: + +```js +import build from 'pino-abstract-stream' + +exports default async function (opts) { + return build(async function (source) { + for await (let obj of source) { + console.log(obj) + } + }) +} +``` + +or using Node.js streams and CommonJS: + +```js +'use strict' + +const build = require('pino-abstract-stream') + +module.exports = function (opts) { + return build(function (source) { + source.on('data', function (obj) { + console.log(obj) + }) + }) +} +``` + +(It is possible to use the async iterators with CommonJS and streams with ESM.) + +### Notable transports + +#### `pino/file` + +The `pino/file` transport routes logs to a file (or file descriptor). + +The `options.destination` property may be set to specify the desired file destination. + +```js +const pino = require('pino') +const transport = pino.transport({ + target: '#pino/file', + options: { destination: '/path/to/file' } +}) +pino(transport) +``` + +The `options.destination` property may also be a number to represent a file descriptor. Typically this would be `1` to write to STDOUT or `2` to write to STDERR. If `options.destination` is not set, it defaults to `1` which means logs will be written to STDOUT. + +The difference between using the `#pino/file` transport builtin and using `pino.destination` is that `pino.destination` runs in the main thread, whereas `#pino/file` sets up `pino.destination` in a worker thread. + +#### `pino/pretty` + +The [`pino-pretty`][pino-pretty] transport prettifies logs. + +By default the `pino-pretty` builtin logs to STDOUT. + +The `options.destination` property may be set to log pretty logs to a file descriptor or file. The following would send the prettified logs to STDERR: + +```js +const pino = require('pino') +const transport = pino.transport({ + target: 'pino-pretty', + options: { destination: 1 } // use 2 for stderr +}) +pino(transport) +``` ### Asynchronous startup @@ -172,6 +245,7 @@ PR's to this document are welcome for any new transports! ### Pino v7+ Compatible + [pino-elasticsearch](#pino-elasticsearch) ++ [pino-pretty](#pino-pretty) ### Legacy @@ -559,3 +633,5 @@ $ node app.js | pino-websocket -a my-websocket-server.example.com -p 3004 ``` For full documentation of command line switches read the [README](https://github.com/abeai/pino-websocket#readme). + +[pino-pretty]: https://github.com/pinojs/pino-pretty diff --git a/examples/transport.js b/examples/transport.js index 25717e492..7fe71d4b4 100644 --- a/examples/transport.js +++ b/examples/transport.js @@ -9,19 +9,21 @@ const file = join(tmpdir(), `pino-${process.pid}-example`) const transport = pino.transport({ targets: [{ level: 'warn', - target: '#pino/file', + target: 'pino/file', options: { destination: file } + /* }, { level: 'info', target: 'pino-elasticsearch', options: { node: 'http://localhost:9200' } + */ }, { level: 'info', - target: '#pino/pretty' + target: 'pino-pretty' }] }) diff --git a/lib/file-target.js b/file.js similarity index 100% rename from lib/file-target.js rename to file.js diff --git a/lib/pretty-target.js b/lib/pretty-target.js deleted file mode 100644 index 5a8d0821a..000000000 --- a/lib/pretty-target.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict' - -const pinoPretty = require('pino-pretty') -const { Transform, pipeline } = require('stream') -const pino = require('../pino') -const { once } = require('events') - -module.exports = async function (opts = {}) { - const pretty = pinoPretty(opts) - const stream = new Transform({ - objectMode: true, - autoDestroy: true, - transform (chunk, enc, cb) { - const line = pretty(chunk.toString()) - cb(null, line) - } - }) - - const destination = pino.destination({ dest: opts.destination || 1, sync: false }) - if (destination.fd === 1) { - // We cannot close the output - destination.end = function () { - this.emit('close') - } - } - await once(destination, 'ready') - pipeline(stream, destination, () => {}) - return stream -} diff --git a/lib/tools.js b/lib/tools.js index 82b9eb759..c3502a9bc 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -213,7 +213,7 @@ function getPrettyStream (opts, prettifier, dest, instance) { return prettifierMetaWrapper(prettifier(opts), dest, opts) } try { - const prettyFactory = require('pino-pretty') + const prettyFactory = require('pino-pretty').prettyFactory prettyFactory.asMetaWrapper = prettifierMetaWrapper return prettifierMetaWrapper(prettyFactory(opts), dest, opts) } catch (e) { diff --git a/lib/transport.js b/lib/transport.js index f69ee837c..7d478e139 100644 --- a/lib/transport.js +++ b/lib/transport.js @@ -85,10 +85,10 @@ function transport (fullOptions) { } switch (origin) { - case '#pino/pretty': - return join(__dirname, 'pretty-target.js') - case '#pino/file': - return join(__dirname, 'file-target.js') + // This hack should not be needed, however it would not work otherwise + // when testing it from this module and in examples. + case 'pino/file': + return join(__dirname, '..', 'file.js') /* istanbul ignore next */ default: // TODO we cannot test this on Windows.. might not work. diff --git a/package.json b/package.json index e1c1c48a6..ab1a67617 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "import-fresh": "^3.2.1", "log": "^6.0.0", "loglevel": "^1.6.7", - "pino-pretty": "^5.1.0", + "pino-pretty": "pinojs/pino-pretty#master", "pre-commit": "^1.2.2", "proxyquire": "^2.1.3", "pump": "^3.0.0", @@ -102,12 +102,12 @@ "fast-safe-stringify": "^2.0.8", "fastify-warning": "^0.2.0", "get-caller-file": "^2.0.5", + "json-stringify-safe": "^5.0.1", "on-exit-leak-free": "^0.2.0", "pino-abstract-transport": "^0.2.0", "pino-std-serializers": "^4.0.0", - "json-stringify-safe": "^5.0.1", "quick-format-unescaped": "^4.0.3", - "sonic-boom": "^2.1.0", + "sonic-boom": "^2.2.1", "thread-stream": "^0.11.0" }, "tsd": { diff --git a/test/fixtures/pretty/pretty-factory.js b/test/fixtures/pretty/pretty-factory.js index 5c09053fa..b32c68405 100644 --- a/test/fixtures/pretty/pretty-factory.js +++ b/test/fixtures/pretty/pretty-factory.js @@ -2,5 +2,5 @@ global.process = { __proto__: process, pid: 123456 } Date.now = function () { return 1459875739796 } require('os').hostname = function () { return 'abcdefghijklmnopqr' } const pino = require(require.resolve('./../../../')) -const log = pino({ prettyPrint: { levelFirst: true }, prettifier: require('pino-pretty') }) +const log = pino({ prettyPrint: { levelFirst: true }, prettifier: require('pino-pretty').prettyFactory }) log.info('h') diff --git a/test/pretty.test.js b/test/pretty.test.js index 0f2569e06..b44e627c0 100644 --- a/test/pretty.test.js +++ b/test/pretty.test.js @@ -54,12 +54,12 @@ test('throws when prettyPrint is true but pino-pretty module is not installed', // pino pretty *is* installed, and probably also cached, so rather than // messing with the filesystem the simplest way to generate a not found // error is to simulate it: - const prettyFactory = require('pino-pretty') - require.cache[require.resolve('pino-pretty')].exports = () => { + const prettyFactory = require('pino-pretty').prettyFactory + require('pino-pretty').prettyFactory = () => { throw Error('Cannot find module \'pino-pretty\'') } throws(() => pino({ prettyPrint: true }), 'Missing `pino-pretty` module: `pino-pretty` must be installed separately') - require.cache[require.resolve('pino-pretty')].exports = prettyFactory + require('pino-pretty').prettyFactory = prettyFactory }) test('throws when prettyPrint has invalid options', async ({ throws }) => { @@ -75,7 +75,7 @@ test('can send pretty print to custom stream', async ({ equal }) => { }) const log = pino({ - prettifier: require('pino-pretty'), + prettifier: require('pino-pretty').prettyFactory, prettyPrint: { levelFirst: true, colorize: false diff --git a/test/targets.test.js b/test/targets.test.js index c6bc048b5..aa2bb8ee2 100644 --- a/test/targets.test.js +++ b/test/targets.test.js @@ -4,48 +4,10 @@ const { test } = require('tap') const proxyquire = require('proxyquire') const Writable = require('stream').Writable -test('pretty-target mocked', async function ({ equal, same, plan, pass }) { - plan(3) - let ret - const prettyTarget = proxyquire('../lib/pretty-target', { - '../pino': { - destination (opts) { - same(opts, { dest: 1, sync: false }) - - ret = new Writable() - ret.fd = opts.dest - - process.nextTick(() => { - ret.emit('ready') - }) - - Object.defineProperty(ret, 'end', { - get () { - return 'unused' - }, - set (end) { - pass('prop set') - const obj = { - emit (ev) { - equal(ev, 'close') - }, - end - } - obj.end() - } - }) - return ret - } - } - }) - - await prettyTarget() -}) - test('file-target mocked', async function ({ equal, same, plan, pass }) { plan(1) let ret - const fileTarget = proxyquire('../lib/file-target', { + const fileTarget = proxyquire('../file', { '../pino': { destination (opts) { same(opts, { dest: 1, sync: false }) diff --git a/test/transport.test.js b/test/transport.test.js index 4451c55b4..9cd95ee5d 100644 --- a/test/transport.test.js +++ b/test/transport.test.js @@ -186,7 +186,7 @@ test('pino.transport with two files', async ({ same, teardown }) => { }) }) -test('pino.transport with an array including a prettyPrint destination', async ({ same, match, teardown }) => { +test('pino.transport with an array including a pino-pretty destination', async ({ same, match, teardown }) => { const dest1 = join( os.tmpdir(), '_' + Math.random().toString(36).substr(2, 9) @@ -198,13 +198,13 @@ test('pino.transport with an array including a prettyPrint destination', async ( const transport = pino.transport({ targets: [{ level: 'info', - target: '#pino/file', + target: 'pino/file', options: { destination: dest1 } }, { level: 'info', - target: '#pino/pretty', + target: 'pino-pretty', options: { destination: dest2 } @@ -329,13 +329,13 @@ test('pino.transport with package as a target', { skip: isWin }, async ({ same, }) }) -test('pino.transport with target #pino/file', async ({ same, teardown }) => { +test('pino.transport with target pino/file', async ({ same, teardown }) => { const destination = join( os.tmpdir(), '_' + Math.random().toString(36).substr(2, 9) ) const transport = pino.transport({ - target: '#pino/file', + target: 'pino/file', options: { destination } }) teardown(transport.end.bind(transport)) @@ -352,13 +352,13 @@ test('pino.transport with target #pino/file', async ({ same, teardown }) => { }) }) -test('pino.transport with target #pino/pretty', async ({ match, teardown }) => { +test('pino.transport with target pino-pretty', async ({ match, teardown }) => { const destination = join( os.tmpdir(), '_' + Math.random().toString(36).substr(2, 9) ) const transport = pino.transport({ - target: '#pino/pretty', + target: 'pino-pretty', options: { destination } }) teardown(transport.end.bind(transport)) diff --git a/test/types/pino.ts b/test/types/pino.ts index ae7fc364d..af424863e 100644 --- a/test/types/pino.ts +++ b/test/types/pino.ts @@ -9,14 +9,14 @@ const destination = join( // Single const transport = pino.transport({ - target: '#pino/pretty', + target: 'pino-pretty', options: { some: 'options for', the: 'transport' } }) const logger = pino(transport) logger.info('test2') const transport2 = pino.transport({ - target: '#pino/pretty', + target: 'pino-pretty', }) const logger2 = pino(transport2) logger2.info('test2') @@ -27,12 +27,12 @@ logger2.info('test2') const transports = pino.transport({targets: [ { level: 'info', - target: '#pino/pretty', + target: 'pino-pretty', options: { some: 'options for', the: 'transport' } }, { level: 'trace', - target: '#pino/file', + target: 'pino/file', options: { destination } } ]})