Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pino.transport() #1003

Merged
merged 66 commits into from
Jul 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
a2840c8
Basic transport implemented on top of ThreadStream
mcollina Apr 7, 2021
b2404c9
Support module transports
mcollina Apr 7, 2021
6b7d7bb
Skip package test on win32
mcollina Apr 7, 2021
b3b537a
Ire-enabled failing test on windows
mcollina Apr 7, 2021
b3e4178
full cov report
mcollina Apr 7, 2021
ec34df7
Revert "full cov report"
mcollina Apr 7, 2021
ed8cfd5
ignore lines unreacheable on Windows
mcollina Apr 7, 2021
e1c72b9
Update lib/transport.js
mcollina Apr 7, 2021
44c150c
Update lib/transport.js
mcollina Apr 7, 2021
246b6f0
Revert "Revert "full cov report""
mcollina Apr 7, 2021
eeca496
try out different ignore strategies
mcollina Apr 7, 2021
661de3e
Revert "Revert "Revert "full cov report"""
mcollina Apr 7, 2021
d78a30b
Works on Windows
mcollina Apr 8, 2021
c54769e
Emit 'error' if thread exits abruptly
mcollina Apr 8, 2021
2f4170e
Merge branch 'next' into transport
mcollina Apr 10, 2021
4c01c89
Implement pass-through of WorkerThread options
mcollina Apr 10, 2021
463af9d
Esm support with transport
mcollina Apr 11, 2021
aa28076
Basic multitransport code
mcollina Apr 11, 2021
2f9649f
multitransport example
mcollina Apr 26, 2021
6dd0627
merged transport and multitransport
mcollina Apr 26, 2021
2ad77ce
Increased timeout
mcollina Apr 29, 2021
465ed17
Added node v16
mcollina Apr 30, 2021
446a581
print the destination
mcollina Apr 30, 2021
8e55539
file://
mcollina Apr 30, 2021
7e8f662
Update example
mcollina May 3, 2021
cdaa18c
Merge branch 'next' into transport
mcollina May 3, 2021
63c4f49
Transports
mcollina May 19, 2021
72a9ee0
rm transport local dep from package.json
davidmarkclements May 31, 2021
b46171d
bump sonic boom
davidmarkclements May 31, 2021
153969c
bump pino-std-serializers
davidmarkclements May 31, 2021
bd28024
transport package test working without local transport dep in package…
davidmarkclements May 31, 2021
6ee9e90
typo
davidmarkclements May 31, 2021
21abdac
api mods, extra test
davidmarkclements May 31, 2021
82578e9
Updated thread-stream
mcollina Jun 2, 2021
8e679ee
fixed node v12 support
mcollina Jun 2, 2021
7e6fb61
Skip failing test on windows
mcollina Jun 2, 2021
47f0df3
print coverage report
mcollina Jun 2, 2021
89fd700
Maybe fix v12
mcollina Jun 2, 2021
ad4ee96
print the platform for debugging sake
mcollina Jun 2, 2021
bec0c08
Pass windows test
mcollina Jun 3, 2021
35596e6
print coverage data
mcollina Jun 3, 2021
ad07d04
Maybe Windows
mcollina Jun 3, 2021
9f1c699
caller refactor
mcollina Jun 3, 2021
97e12a3
Updated API
mcollina Jun 3, 2021
f0f06fc
option validation
mcollina Jun 3, 2021
02a4b68
Support modules within destinations
mcollina Jun 4, 2021
58e2914
Use full names for the options
mcollina Jun 4, 2021
ca5d4c5
windows coverage
mcollina Jun 4, 2021
f879653
coverage report
mcollina Jun 4, 2021
a791067
fix coverage on windows
mcollina Jun 4, 2021
c924b92
docs first pass
davidmarkclements Jun 6, 2021
52d5e87
target and targets
mcollina Jun 6, 2021
2bbe8c5
restore istanbul ignore
mcollina Jun 7, 2021
2e307a8
Do not check the coverage in CI
mcollina Jun 7, 2021
21f0c40
--no-check-coverage
mcollina Jun 7, 2021
7fe66d6
Merge branch 'next' into transport
mcollina Jun 7, 2021
caaec82
removed TODO
mcollina Jun 7, 2021
82ebbbf
built in docs, other doc tweaks
davidmarkclements Jun 11, 2021
2474e29
Update test/transport.test.js
mcollina Jun 12, 2021
6c49f3e
Update test/transport.test.js
mcollina Jun 12, 2021
17dc8a9
Update docs/api.md
mcollina Jul 3, 2021
8ef4409
Update docs/api.md
mcollina Jul 3, 2021
8855a33
Update docs/api.md
mcollina Jul 3, 2021
224474e
Update docs/transports.md
mcollina Jul 3, 2021
55e8b61
Update docs/transports.md
mcollina Jul 3, 2021
45cb21c
PR reviews
mcollina Jul 3, 2021
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
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ format logs during development:

Due to Node's single-threaded event-loop, it's highly recommended that sending,
alert triggering, reformatting and all forms of log processing
is conducted in a separate process. In Pino parlance we call all log processors
"transports", and recommend that the transports be run as separate
processes, piping the stdout of the application to the stdin of the transport.
be conducted in a separate process or thread.

In Pino terminology we call all log processors "transports", and recommend that the
transports be run in a worker thread using our `pino.transport` API.

For more details see our [Transports⇗](docs/transports.md) document.

Expand Down
93 changes: 93 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* [logger.version](#version)
* [Statics](#statics)
* [pino.destination()](#pino-destination)
* [pino.transport()](#pino-transport)
* [pino.final()](#pino-final)
* [pino.multistream()](#pino-multistream)
* [pino.stdSerializers](#pino-stdserializers)
Expand Down Expand Up @@ -905,6 +906,98 @@ A `pino.destination` instance can also be used to reopen closed files
* See [Reopening log files](/docs/help.md#reopening)
* See [Asynchronous Logging ⇗](/docs/asynchronous.md)

<a id="pino-transport"></a>
### `pino.transport(options) => ThreadStream`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a callout here, and in the transports specific docs, to notify the reader about CPU core count. Ideally, a core for the main app + a core per worker thread (transport) should be available.


Create a a stream that routes logs to a worker thread that
wraps around a [Pino Transport](/docs/transports.md).

```js
const pino = require('pino')
const transport = pino.transport({
target: 'some-transport',
options: { some: 'options for', the: 'transport' }
})
pino(transport)
```

Multiple transports may also be defined, and specific levels can be logged to each transport:

```js
const pino = require('pino')
const transports = pino.transport([
{
level: 'info',
target: 'some-transport',
options: { some: 'options for', the: 'transport' }
},
{
level: 'trace',
target: '#pino/file',
options: { destination: '/path/to/store/logs' }
}
])
pino(transports)
```


For more on transports, how they work, and how to create them see the [`Transports documentation`](/docs/transports.md).

* See [`Transports`](/docs/transports.md)
* See [`thread-stream` ⇗](https://github.com/mcollina/thread-stream)

#### 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))
* `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
168 changes: 126 additions & 42 deletions docs/transports.md
Original file line number Diff line number Diff line change
@@ -1,79 +1,163 @@
# Transports

A "transport" for Pino is a supplementary tool which consumes Pino logs.
Pino transports can be used for both transmitting and transforming log output.

Consider the following example:
The way Pino generates logs:

```js
const split = require('split2')
const pump = require('pump')
const through = require('through2')
1. Reduces the impact of logging on an application to the absolute minimum.
2. Gives greater flexibility in how logs are processed and stored.

It is recommended that any log transformation or transmission is performed either
in a seperate thread or a seperate process.

Prior to Pino v7 transports would ideally operate in a seperate process - these are
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.

const myTransport = through.obj(function (chunk, enc, cb) {
// do the necessary
console.log(chunk)
cb()
})

pump(process.stdin, split(JSON.parse), myTransport)
[worker-thread]: https://nodejs.org/dist/latest-v14.x/docs/api/worker_threads.html

## v7+ Transports
davidmarkclements marked this conversation as resolved.
Show resolved Hide resolved

A transport is a module that exports a default function which returns a writable stream:

```js
import { Writable } from 'stream'
export default (options) => {
const myTransportStream = new Writable({
write (chunk, enc, cb) {
// apply a transform and send to stdout
console.log(chunk.toString().toUpperCase())
cb()
}
})
return myTransportStream
}
```

The above defines our "transport" as the file `my-transport-process.js`.
Let's imagine the above defines our "transport" as the file `my-transport.mjs`
(ESM files are supported even if the project is written in CJS).

Logs can now be consumed using shell piping:
We would set up our transport by creating a transport stream with `pino.transport`
and passing it to the `pino` function:

```sh
node my-app-which-logs-stuff-to-stdout.js | node my-transport-process.js
```js
const pino = require('pino')
const transport = pino.transport({
target: '/absolute/path/to/my-transport.mjs'
})
pino(transport)
```

Ideally, a transport should consume logs in a separate process to the application,
Using transports in the same process causes unnecessary load and slows down
Node's single threaded event loop.
The transport code will be executed in a separate worker thread. The main thread
will write logs to the worker thread, which will write them to the stream returned
from the function exported from the transport file/module.

## In-process transports
The exported function can also be async. Imagine the following transport:

```js
import fs from 'fs'
import { once } from('events')
export default async (options) => {
const stream = fs.createWriteStream(opts.destination)
await once(stream, 'open')
return stream
}
```

> **Pino *does not* natively support in-process transports.**
While initializing the stream we're able to use `await` to perform asynchronous operations. In this
case waiting for the write streams `open` event.

Pino does not support in-process transports because Node processes are
single threaded processes (ignoring some technical details). Given this
restriction, one of the methods Pino employs to achieve its speed is to
purposefully offload the handling of logs, and their ultimate destination, to
external processes so that the threading capabilities of the OS can be
used (or other CPUs).
Let's imagine the above was published to npm with the module name `some-file-transport`.

One consequence of this methodology is that "error" logs do not get written to
`stderr`. However, since Pino logs are in a parsable format, it is possible to
use tools like [pino-tee][pino-tee] or [jq][jq] to work with the logs. For
example, to view only logs marked as "error" logs:
The `options.destination` value can be set when the creating the transport stream with `pino.transport` like so:

```js
const pino = require('pino')
const transport = pino.transport({
target: 'some-file-transport',
options: { destination: '/dev/null' }
})
pino(transport)
```
$ node an-app.js | jq 'select(.level == 50)'

Note here we've specified a module by package rather than by relative path. The options object we provide
is serialized and injected into the transport worker thread, then passed to the module's exported function.
This means that the options object can only contain types that are supported by the
[Structured Clone Algorithm][sca] which is used to (de)serializing objects between threads.

What if we wanted to use both transports, but send only error logs to `some-file-transport` while
sending all logs to `my-transport.mjs`? We can use the `pino.transport` function's `destinations` option:

```js
const pino = require('pino')
const transport = pino.transport({
destinations: [
{ target: '/absolute/path/to/my-transport.mjs', level: 'error' },
{ target: 'some-file-transport', options: { destination: '/dev/null' }
]
})
pino(transport)
```

In short, the way Pino generates logs:
For more details on `pino.transport` see the [API docs for `pino.transport`][pino-transport].

1. Reduces the impact of logging on an application to the absolute minimum.
2. Gives greater flexibility in how logs are processed and stored.

Given all of the above, Pino recommends out-of-process log processing.
[pino-transport]: /docs/api.md#pino-transport
[sca]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm



## Legacy Transports

A legacy Pino "transport" is a supplementary tool which consumes Pino logs.

Consider the following example for creating a transport:

```js
const { pipeline, Writable } = require('stream')
const split = require('split2')

const myTransportStream = new Writable({
write (chunk, enc, cb) {
// apply a transform and send to stdout
console.log(chunk.toString().toUpperCase())
cb()
}
})

pipeline(process.stdin, split(JSON.parse), myTransportStream)
```

The above defines our "transport" as the file `my-transport-process.js`.

Logs can now be consumed using shell piping:

However, it is possible to wrap Pino and perform processing in-process.
For an example of this, see [pino-multi-stream][pinoms].
```sh
node my-app-which-logs-stuff-to-stdout.js | node my-transport-process.js
```

[pino-tee]: https://npm.im/pino-tee
[jq]: https://stedolan.github.io/jq/
[pinoms]: https://npm.im/pino-multi-stream
Ideally, a transport should consume logs in a separate process to the application,
Using transports in the same process causes unnecessary load and slows down
Node's single threaded event loop.

## Known Transports

PR's to this document are welcome for any new transports!

### Pino v7+ Compatible

+ [pino-elasticsearch](#pino-elasticsearch)

### Legacy

+ [pino-applicationinsights](#pino-applicationinsights)
+ [pino-azuretable](#pino-azuretable)
+ [pino-cloudwatch](#pino-cloudwatch)
+ [pino-couch](#pino-couch)
+ [pino-datadog](#pino-datadog)
+ [pino-elasticsearch](#pino-elasticsearch)
+ [pino-gelf](#pino-gelf)
+ [pino-http-send](#pino-http-send)
+ [pino-kafka](#pino-kafka)
Expand Down
2 changes: 1 addition & 1 deletion example.js → examples/basic.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

const pino = require('./')()
const pino = require('..')()

pino.info('hello world')
pino.error('this is at error level')
Expand Down
Loading