Skip to content

Commit

Permalink
pino-pretty transport convertion (#1110)
Browse files Browse the repository at this point in the history
* partial transport convertion

* some code coverage

* fixed type tests

* use pino/file instead of #pino/file

* tiny fixes

* Fixed docs
  • Loading branch information
mcollina committed Sep 6, 2021
1 parent a201e2c commit f712a52
Show file tree
Hide file tree
Showing 13 changed files with 111 additions and 142 deletions.
52 changes: 5 additions & 47 deletions docs/api.md
Expand Up @@ -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' }
}]
})
Expand All @@ -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/<name>`.

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)
```

<a id="pino-final"></a>

### `pino.final(logger, [handler]) => Function | FinalLogger`
Expand Down
78 changes: 77 additions & 1 deletion docs/transports.md
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
6 changes: 4 additions & 2 deletions examples/transport.js
Expand Up @@ -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'
}]
})

Expand Down
File renamed without changes.
29 changes: 0 additions & 29 deletions lib/pretty-target.js

This file was deleted.

2 changes: 1 addition & 1 deletion lib/tools.js
Expand Up @@ -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) {
Expand Down
8 changes: 4 additions & 4 deletions lib/transport.js
Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions package.json
Expand Up @@ -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",
Expand All @@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/pretty/pretty-factory.js
Expand Up @@ -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')
8 changes: 4 additions & 4 deletions test/pretty.test.js
Expand Up @@ -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 }) => {
Expand All @@ -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
Expand Down
40 changes: 1 addition & 39 deletions test/targets.test.js
Expand Up @@ -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 })
Expand Down

0 comments on commit f712a52

Please sign in to comment.